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

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

The relaxation pass now pre-scans for unsized instructions and keeps every
branch absolute instead of aborting.
2026-11-12 17:11:35 +00:00
arti 40f5520e3d tms34010: JR branches and relaxation 2026-11-12 16:49:49 +00:00
arti f1450ebe7e tms34010: native object path for M4 programs 2026-11-12 16:15:18 +00:00
arti d0d1d6e101 tms34010: fix MC review findings 2026-11-12 15:35:05 +00:00
arti a34b8a3fb7 tms34010: full MC assembler/encoder/disassembler 2026-11-12 15:13:41 +00:00
arti ea154fa696 tms34010: harden TI-COFF relocation writing 2026-11-12 14:30:05 +00:00
arti df6f6fa9d6 tms34010: linkable TI-COFF objects 2026-11-12 14:15:07 +00:00
arti 67bd4c7dda tms34010: cover both MOVI forms and fail loudly on unsupported objects 2026-11-12 13:43:32 +00:00
arti fd41299260 tms34010: add the minimal MC object path 2026-11-12 13:31:20 +00:00
arti de5a365596 tms34010: clarify M4 carry-forwards and hash wording
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
unprivileged-download-artifact/action.yml (push) Invalid workflow file
2026-11-12 12:23:01 +00:00
arti 61ee9bbc99 tms34010: M4 function pointers, XImage/events and termtris support
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
2026-11-12 11:49:30 +00:00
arti b08e57424a tms34010: reject narrow last-fixed varargs anchors
The contiguous-stack varargs walk depends on SelectionDAGBuilder's
argument-copy elision making &last_fixed the incoming stack slot.  The
elision requires the fixed object and the parameter alloca to have equal
size, so an i8/i16 anchor silently stays a private local and the walk
reads the wrong slot.  Reject a variadic definition unless its last fixed
argument is i32 or a pointer, and pin the i32 anchor's FP-64 slot and the
first variadic at one slot below in m4-varargs-anchor.ll.
2026-11-12 11:20:21 +00:00
arti 6261ac442a tms34010: M4 varargs, narrow arguments, and termtris scalar fixes
Support the contiguous-stack varargs form termtris' compat.c sprintf
needs, promote i8/i16 fixed arguments to one i32 slot, and select i1 and
any-extending narrow loads.  Recursively emit nested array initializers,
keep divide-by-constant on DIVS/DIVU (MULHS/SMUL_LOHI are unsupported),
and disable jump tables.  Add a vector setcc result type so O2's <3 x i32>
struct copy legalizes.  The termtris gate exposed all of these.
2026-11-12 10:51:48 +00:00
arti 3c810ec0be tms34010: fix high-bit target constants in compares 2026-11-12 07:19:39 +00:00
arti a49c934380 tms34010: add CALL Rs and function-pointer calls
Add the one-word indirect call CALL Rs (0000 1001 001R DDDD, 0x0920) and
extend LowerCall to accept a function-pointer callee alongside the existing
direct CALLA path.  The indirect callee is a 32-bit bit address: a constant or
non-function global becomes MOVI and any other value is copied into an
allocatable ARegs virtual register after the argument pushes, so the pushes
cannot clobber the target.  The old-STK save, argument order, FS0/FS1
re-establishment and A8 return are unchanged.

GSPA 2.00 accepts CALL A0 (not CALL $A0); dis34 prints it as call aN.  The new
m4-fnptr lit test covers a callback argument, a memory-loaded pointer, an
absolute ROM address and a no-argument callback.  The obsolete
m2-reject-call-indirect test is removed.
2026-11-12 06:40:09 +00:00
arti 4ff6e2df9d tms34010: select MPYS/MPYU/DIVS/DIVU for 32-bit arithmetic
Add the register-file multiply and divide encodings and restrict their tied
destination to the odd allocatable A registers, so the instruction uses the
single-register 32-bit form instead of a Rd:Rd+1 pair.  MPYS covers the low
32 bits of a 32x32 product, MPYU the masked 16x16 unsigned product, and
DIVS/DIVU the 32/32 quotients.  SREM/UREM expand to dividend - quotient *
divisor because the odd-destination divide does not return a remainder.

Sign-extension in register (which DAGCombine forms from ordinary short
arithmetic) selects SEXT8/SEXT16 and expands to the verified SLL/SRA pair.
2026-11-12 06:04:53 +00:00
arti 4b300a75e9 tms34010: pin the full signed/unsigned branch predicate map 2026-11-12 05:46:40 +00:00
arti df6a6128d2 tms34010: add M4 zero-extending loads and module-unique private labels 2026-11-12 05:38:20 +00:00
arti 73c85350a4 tms34010: correct M3 encodings and narrow-load docs
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
2026-11-11 20:30:31 +00:00
arti 1968ccd39b docs: correct M3 milestone wording
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
2026-11-11 20:06:55 +00:00
arti 8d088c40df docs: M3 branches and Xlib window
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
2026-11-11 19:56:16 +00:00
arti afb4ad2eef tms34010: allow misaligned loads/stores 2026-11-11 19:45:28 +00:00
arti 05762fceb8 tms34010: add integer comparisons, branches and ALU ops 2026-11-11 18:54:46 +00:00
arti bbc591b77c tms34010: add JAcc branches and GSPA-safe block labels 2026-11-11 18:10:40 +00:00
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
127 changed files with 10310 additions and 252 deletions

No files matched your search

+980 -40
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,17 @@
add_llvm_component_library(LLVMTMS34010AsmParser
../MCTargetDesc/TMS34010AsmParser.cpp
LINK_COMPONENTS
CodeGenTypes
MC
MCParser
TMS34010Desc
TMS34010Info
Support
DEPENDS
TMS34010CommonTableGen
ADD_TO_COMPONENT
TMS34010
)
+5
View File
@@ -2,9 +2,12 @@ add_llvm_component_group(TMS34010)
set(LLVM_TARGET_DEFINITIONS TMS34010.td)
tablegen(LLVM TMS34010GenAsmMatcher.inc -gen-asm-matcher)
tablegen(LLVM TMS34010GenAsmWriter.inc -gen-asm-writer)
tablegen(LLVM TMS34010GenDAGISel.inc -gen-dag-isel)
tablegen(LLVM TMS34010GenDisassemblerTables.inc -gen-disassembler)
tablegen(LLVM TMS34010GenInstrInfo.inc -gen-instr-info)
tablegen(LLVM TMS34010GenMCCodeEmitter.inc -gen-emitter)
tablegen(LLVM TMS34010GenRegisterInfo.inc -gen-register-info)
tablegen(LLVM TMS34010GenSDNodeInfo.inc -gen-sd-node-info)
tablegen(LLVM TMS34010GenSubtargetInfo.inc -gen-subtarget)
@@ -46,3 +49,5 @@ add_llvm_target(TMS34010CodeGen
add_subdirectory(MCTargetDesc)
add_subdirectory(TargetInfo)
add_subdirectory(AsmParser)
add_subdirectory(Disassembler)
@@ -0,0 +1,15 @@
add_llvm_component_library(LLVMTMS34010Disassembler
../MCTargetDesc/TMS34010Disassembler.cpp
LINK_COMPONENTS
MCDisassembler
MC
TMS34010Info
Support
DEPENDS
TMS34010CommonTableGen
ADD_TO_COMPONENT
TMS34010
)
@@ -1,6 +1,18 @@
# The assembler and disassembler sources live here next to the MC descriptions
# but are compiled into the standard AsmParser/Disassembler component libraries
# (see ../AsmParser and ../Disassembler), so they are intentionally not part of
# this library.
set(LLVM_OPTIONAL_SOURCES
TMS34010AsmParser.cpp
TMS34010Disassembler.cpp
)
add_llvm_component_library(LLVMTMS34010Desc
TMS34010InstPrinter.cpp
TMS34010MCAsmBackend.cpp
TMS34010MCAsmInfo.cpp
TMS34010MCCodeEmitter.cpp
TMS34010MCObjectWriter.cpp
TMS34010MCTargetDesc.cpp
LINK_COMPONENTS
@@ -0,0 +1,507 @@
//===-- TMS34010AsmParser.cpp - Parse TMS34010 assembly ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// GSPA 2.00 syntax is the source language. The parser intentionally emits a
// flat token/register/immediate stream and lets TableGen's asm matcher match
// the literal *, -, +, @ and parenthesis tokens that appear in the MC asm
// strings. The only instruction with a non-regular grammar is MMTM/MMFM,
// whose GSPA operand is a comma-separated register list that must be packed
// into the 16-bit mask operand.
//
//===----------------------------------------------------------------------===//
#include "TMS34010AsmParser.h"
#include "TMS34010MCTargetDesc.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCParser/AsmLexer.h"
#include "llvm/MC/MCParser/MCParsedAsmOperand.h"
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/SMLoc.h"
using namespace llvm;
namespace {
/// One parsed TMS34010 operand. The parser deliberately keeps punctuation as
/// token operands so that a single Matcher table can describe all of GSPA's
/// indirect forms.
class TMS34010Operand : public MCParsedAsmOperand {
enum KindTy { k_Token, k_Reg, k_Imm } Kind;
StringRef Token;
MCRegister Reg = TMS34010::NoRegister;
const MCExpr *Imm = nullptr;
SMLoc Start, End;
public:
TMS34010Operand(StringRef Tok, SMLoc S)
: Kind(k_Token), Token(Tok), Start(S), End(S) {}
TMS34010Operand(MCRegister R, SMLoc S, SMLoc E)
: Kind(k_Reg), Reg(R), Start(S), End(E) {}
TMS34010Operand(const MCExpr *V, SMLoc S, SMLoc E)
: Kind(k_Imm), Imm(V), Start(S), End(E) {}
bool isToken() const override { return Kind == k_Token; }
bool isReg() const override { return Kind == k_Reg; }
bool isImm() const override { return Kind == k_Imm; }
bool isMem() const override { return false; }
StringRef getToken() const {
assert(Kind == k_Token && "not a token");
return Token;
}
MCRegister getReg() const override {
assert(Kind == k_Reg && "not a register");
return Reg;
}
const MCExpr *getImm() const {
assert(Kind == k_Imm && "not an immediate");
return Imm;
}
SMLoc getStartLoc() const override { return Start; }
SMLoc getEndLoc() const override { return End; }
void addRegOperands(MCInst &Inst, unsigned N) const {
assert(Kind == k_Reg && N == 1 && "invalid register operand");
Inst.addOperand(MCOperand::createReg(Reg));
}
void addImmOperands(MCInst &Inst, unsigned N) const {
assert(Kind == k_Imm && N == 1 && "invalid immediate operand");
int64_t Value;
if (Imm->evaluateAsAbsolute(Value))
Inst.addOperand(MCOperand::createImm(Value));
else
Inst.addOperand(MCOperand::createExpr(Imm));
}
void print(raw_ostream &O, const MCAsmInfo &MAI) const override {
switch (Kind) {
case k_Token:
O << "Token " << Token;
break;
case k_Reg:
O << "Reg " << Reg.id();
break;
case k_Imm:
O << "Imm ";
MAI.printExpr(O, *Imm);
break;
}
}
static std::unique_ptr<TMS34010Operand> CreateToken(StringRef Str, SMLoc S) {
return std::make_unique<TMS34010Operand>(Str, S);
}
static std::unique_ptr<TMS34010Operand> CreateReg(MCRegister R, SMLoc S,
SMLoc E) {
return std::make_unique<TMS34010Operand>(R, S, E);
}
static std::unique_ptr<TMS34010Operand> CreateImm(const MCExpr *V, SMLoc S,
SMLoc E) {
return std::make_unique<TMS34010Operand>(V, S, E);
}
};
class TMS34010AsmParser : public MCTargetAsmParser {
MCAsmParser &Parser;
const MCRegisterInfo *MRI;
MCAsmParser &getParser() const { return Parser; }
AsmLexer &getLexer() const { return Parser.getLexer(); }
bool parseOneOperand(OperandVector &Operands);
bool parseIndirectOperand(OperandVector &Operands);
bool parseRegisterListInstruction(StringRef Name, SMLoc NameLoc,
OperandVector &Operands);
#define GET_ASSEMBLER_HEADER
#include "TMS34010GenAsmMatcher.inc"
public:
TMS34010AsmParser(const MCSubtargetInfo &STI, MCAsmParser &Parser,
const MCInstrInfo &MII)
: MCTargetAsmParser(STI, MII), Parser(Parser) {
MCAsmParserExtension::Initialize(Parser);
MRI = getContext().getRegisterInfo();
setAvailableFeatures(ComputeAvailableFeatures(STI.getFeatureBits()));
}
bool parseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) override;
ParseStatus tryParseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) override;
bool parseInstruction(ParseInstructionInfo &Info, StringRef Name,
SMLoc NameLoc, OperandVector &Operands) override;
bool matchAndEmitInstruction(SMLoc Loc, unsigned &Opcode,
OperandVector &Operands, MCStreamer &Out,
uint64_t &ErrorInfo,
bool MatchingInlineAsm) override;
ParseStatus parseDirective(AsmToken DirectiveID) override;
};
} // end anonymous namespace
// Defined by the generated matcher at the bottom of this file.
static MCRegister MatchRegisterName(StringRef Name);
static MCRegister MatchRegisterAltName(StringRef Name);
bool TMS34010AsmParser::parseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) {
ParseStatus Res = tryParseRegister(Reg, StartLoc, EndLoc);
if (Res.isSuccess())
return false;
if (Res.isNoMatch())
return true;
return Error(StartLoc, "invalid register name");
}
ParseStatus TMS34010AsmParser::tryParseRegister(MCRegister &Reg,
SMLoc &StartLoc,
SMLoc &EndLoc) {
if (getLexer().getKind() != AsmToken::Identifier)
return ParseStatus::NoMatch;
// Register AsmNames are uppercase (A0, B15); GSPA's SP alias is lowercase.
// Try the spelling as written, then both cases, so the assembler is
// case-insensitive like GSPA without changing the printer's canonical names.
StringRef Raw = getLexer().getTok().getIdentifier();
auto Lookup = [&](StringRef Name) {
Reg = MatchRegisterName(Name);
if (Reg == TMS34010::NoRegister)
Reg = MatchRegisterAltName(Name);
};
Lookup(Raw);
if (Reg == TMS34010::NoRegister)
Lookup(Raw.upper());
if (Reg == TMS34010::NoRegister)
Lookup(Raw.lower());
if (Reg == TMS34010::NoRegister)
return ParseStatus::NoMatch;
StartLoc = getLexer().getTok().getLoc();
EndLoc = getLexer().getTok().getEndLoc();
getLexer().Lex();
return ParseStatus::Success;
}
/// Parse a `*reg`, `*reg+`, `*reg(disp)` or `-*reg` operand after the leading
/// `*` has been consumed. Any punctuation is retained in the operand stream
/// because it is part of the TableGen asm string.
bool TMS34010AsmParser::parseIndirectOperand(OperandVector &Operands) {
SMLoc StarLoc = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), StarLoc));
getLexer().Lex(); // eat '*'
MCRegister Reg;
SMLoc StartLoc, EndLoc;
if (parseRegister(Reg, StartLoc, EndLoc))
return Error(StarLoc, "expected register after '*'");
Operands.push_back(TMS34010Operand::CreateReg(Reg, StartLoc, EndLoc));
if (getLexer().is(AsmToken::Plus)) {
SMLoc S = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), S));
getLexer().Lex();
return false;
}
if (getLexer().is(AsmToken::LParen)) {
SMLoc LS = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), LS));
getLexer().Lex();
const MCExpr *Disp;
SMLoc DispLoc = getLexer().getLoc();
if (getParser().parseExpression(Disp))
return Error(DispLoc, "expected displacement expression");
Operands.push_back(
TMS34010Operand::CreateImm(Disp, DispLoc, getLexer().getLoc()));
if (!getLexer().is(AsmToken::RParen))
return Error(getLexer().getLoc(), "expected ')'");
SMLoc RS = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), RS));
getLexer().Lex();
}
return false;
}
bool TMS34010AsmParser::parseOneOperand(OperandVector &Operands) {
switch (getLexer().getKind()) {
case AsmToken::Star:
return parseIndirectOperand(Operands);
case AsmToken::Minus: {
// `-*reg` is a predecrement memory operand; `-imm` is an expression.
AsmToken Minus = getLexer().getTok();
getLexer().Lex();
if (getLexer().is(AsmToken::Star)) {
Operands.push_back(
TMS34010Operand::CreateToken(Minus.getString(), Minus.getLoc()));
return parseIndirectOperand(Operands);
}
// Put the minus back and let the expression parser apply its normal
// precedence: ADDI -5+2 is (-5)+2, not -(5+2).
getLexer().UnLex(Minus);
const MCExpr *Value;
SMLoc StartLoc = Minus.getLoc();
if (getParser().parseExpression(Value))
return Error(StartLoc, "expected expression after '-'");
Operands.push_back(
TMS34010Operand::CreateImm(Value, StartLoc, getLexer().getLoc()));
return false;
}
case AsmToken::At: {
SMLoc S = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), S));
getLexer().Lex();
const MCExpr *Addr;
SMLoc AddrLoc = getLexer().getLoc();
if (getParser().parseExpression(Addr))
return Error(AddrLoc, "expected absolute address expression");
Operands.push_back(
TMS34010Operand::CreateImm(Addr, AddrLoc, getLexer().getLoc()));
return false;
}
case AsmToken::Identifier: {
MCRegister Reg;
SMLoc StartLoc, EndLoc;
ParseStatus Res = tryParseRegister(Reg, StartLoc, EndLoc);
if (Res.isSuccess()) {
Operands.push_back(TMS34010Operand::CreateReg(Reg, StartLoc, EndLoc));
return false;
}
if (Res.isFailure())
return Error(getLexer().getLoc(), "invalid register name");
[[fallthrough]];
}
case AsmToken::Integer:
case AsmToken::Plus:
case AsmToken::LParen:
case AsmToken::Hash:
default: {
const MCExpr *Value;
SMLoc StartLoc = getLexer().getLoc();
if (getParser().parseExpression(Value))
return Error(StartLoc, "expected expression operand");
Operands.push_back(
TMS34010Operand::CreateImm(Value, StartLoc, getLexer().getLoc()));
return false;
}
}
}
bool TMS34010AsmParser::parseRegisterListInstruction(
StringRef Name, SMLoc NameLoc, OperandVector &Operands) {
Operands.push_back(TMS34010Operand::CreateToken(Name, NameLoc));
MCRegister Dst;
SMLoc StartLoc, EndLoc;
if (parseRegister(Dst, StartLoc, EndLoc))
return Error(NameLoc, "expected a base register");
Operands.push_back(TMS34010Operand::CreateReg(Dst, StartLoc, EndLoc));
if (!parseOptionalToken(AsmToken::Comma))
return Error(getLexer().getLoc(), "expected a register list");
// The canonical form is a register list; a numeric mask is also accepted so
// that the encoding can be pinned directly in tests.
if (getLexer().is(AsmToken::Integer) || getLexer().is(AsmToken::Minus)) {
const MCExpr *Mask;
SMLoc MaskLoc = getLexer().getLoc();
if (getParser().parseExpression(Mask))
return Error(MaskLoc, "expected mask expression");
Operands.push_back(
TMS34010Operand::CreateImm(Mask, MaskLoc, getLexer().getLoc()));
} else {
uint16_t Mask = 0;
unsigned File = MRI->getEncodingValue(Dst) & 0x10;
bool Any = false;
while (true) {
MCRegister Reg;
SMLoc RS, RE;
if (parseRegister(Reg, RS, RE))
return Error(getLexer().getLoc(), "expected register in list");
unsigned Enc = MRI->getEncodingValue(Reg);
if ((Enc & 0x10) != File)
return Error(RS, "register list must use the same register file");
unsigned Low = Enc & 0xF;
if (Name.equals_insensitive("mmtm"))
Mask |= static_cast<uint16_t>(1u << (15 - Low));
else
Mask |= static_cast<uint16_t>(1u << Low);
Any = true;
if (!parseOptionalToken(AsmToken::Comma))
break;
if (getLexer().is(AsmToken::EndOfStatement))
break;
}
if (!Any)
return Error(NameLoc, "empty register list");
Operands.push_back(TMS34010Operand::CreateImm(
MCConstantExpr::create(Mask, getContext()), NameLoc,
getLexer().getLoc()));
}
if (getLexer().isNot(AsmToken::EndOfStatement)) {
SMLoc Loc = getLexer().getLoc();
getParser().eatToEndOfStatement();
return Error(Loc, "unexpected token after register list");
}
getParser().Lex();
return false;
}
bool TMS34010AsmParser::parseInstruction(ParseInstructionInfo &Info,
StringRef Name, SMLoc NameLoc,
OperandVector &Operands) {
if (Name.equals_insensitive("mmtm") || Name.equals_insensitive("mmfm"))
return parseRegisterListInstruction(Name, NameLoc, Operands);
Operands.push_back(TMS34010Operand::CreateToken(Name, NameLoc));
if (getLexer().is(AsmToken::EndOfStatement))
return false;
while (true) {
if (parseOneOperand(Operands))
return true;
if (!parseOptionalToken(AsmToken::Comma))
break;
}
if (getLexer().isNot(AsmToken::EndOfStatement)) {
SMLoc Loc = getLexer().getLoc();
getParser().eatToEndOfStatement();
return Error(Loc, "unexpected token");
}
getParser().Lex();
return false;
}
ParseStatus TMS34010AsmParser::parseDirective(AsmToken DirectiveID) {
return ParseStatus::NoMatch;
}
static bool isShortJR(unsigned Opcode) {
switch (Opcode) {
case TMS34010::MC_JR:
case TMS34010::MC_JRP:
case TMS34010::MC_JRLS:
case TMS34010::MC_JRHI:
case TMS34010::MC_JRLT:
case TMS34010::MC_JRGE:
case TMS34010::MC_JRLE:
case TMS34010::MC_JRGT:
case TMS34010::MC_JRLO:
case TMS34010::MC_JRHS:
case TMS34010::MC_JREQ:
case TMS34010::MC_JRNE:
case TMS34010::MC_JRV:
case TMS34010::MC_JRNV:
case TMS34010::MC_JRN:
case TMS34010::MC_JRNN:
return true;
default:
return false;
}
}
bool TMS34010AsmParser::matchAndEmitInstruction(
SMLoc Loc, unsigned &Opcode, OperandVector &Operands, MCStreamer &Out,
uint64_t &ErrorInfo, bool MatchingInlineAsm) {
MCInst Inst;
unsigned MatchResult =
MatchInstructionImpl(Operands, Inst, ErrorInfo, MatchingInlineAsm);
switch (MatchResult) {
case Match_Success:
Inst.setLoc(Loc);
// The MC JR/JRcc encodings take the raw 8-bit word displacement. Zero
// would be the 16-bit displacement form and 0x80 is the absolute-JA
// marker, so neither belongs in the two-byte encoding.
if (isShortJR(Inst.getOpcode()) && Inst.getNumOperands() == 1 &&
Inst.getOperand(0).isImm()) {
int64_t Disp = Inst.getOperand(0).getImm();
if (Disp == 0 || Disp < -127 || Disp > 127)
return Error(Loc, "8-bit JR displacement must be in [-127,-1] or "
"[1,127]");
}
// All same-file instructions share bit 4 of the instruction word. The
// assembler must reject a pair in different files instead of silently
// encoding the source register number in the destination file. The only
// cross-file instruction is MOVE register-to-register.
if (Inst.getOpcode() != TMS34010::MC_MOVE) {
unsigned File = ~0u;
for (const MCOperand &MO : Inst) {
if (!MO.isReg())
continue;
unsigned F = MRI->getEncodingValue(MO.getReg()) & 0x10;
if (File == ~0u)
File = F;
else if (File != F)
return Error(Loc, "source and destination registers must be in the "
"same register file");
}
}
Out.emitInstruction(Inst, *STI);
return false;
case Match_MnemonicFail:
return Error(Loc, "invalid instruction mnemonic");
case Match_InvalidOperand: {
SMLoc ErrorLoc = Loc;
if (ErrorInfo != ~0U) {
if (ErrorInfo >= Operands.size())
return Error(ErrorLoc, "too few operands for instruction");
ErrorLoc = Operands[ErrorInfo]->getStartLoc();
if (ErrorLoc == SMLoc())
ErrorLoc = Loc;
}
return Error(ErrorLoc, "invalid operand for instruction");
}
default:
return true;
}
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010AsmParser() {
RegisterMCAsmParser<TMS34010AsmParser> X(getTheTMS34010Target());
}
#define GET_REGISTER_MATCHER
#define GET_MATCHER_IMPLEMENTATION
#include "TMS34010GenAsmMatcher.inc"
@@ -0,0 +1,18 @@
//===-- TMS34010AsmParser.h - TMS34010 assembly parser ----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The parser implementation is private to TMS34010AsmParser.cpp; it is
// registered through LLVMInitializeTMS34010AsmParser in that file. This
// header exists so the component follows the target layout.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010ASMPARSER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010ASMPARSER_H
#endif
@@ -0,0 +1,386 @@
//===-- TMS34010Disassembler.cpp - TMS34010 disassembler --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// TMS34010 instructions are one to three 16-bit words. The generated decoder
// tables contain the fixed instruction bits; this file supplies the instruction
// length, the register-file decoders and a few post-decode fixups for encodings
// whose register file bit is shared between two operands.
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCTargetDesc.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDecoder.h"
#include "llvm/MC/MCDecoderOps.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
using namespace llvm::MCD;
#define DEBUG_TYPE "tms34010-disassembler"
typedef MCDisassembler::DecodeStatus DecodeStatus;
namespace {
class TMS34010Disassembler : public MCDisassembler {
public:
TMS34010Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx)
: MCDisassembler(STI, Ctx) {}
DecodeStatus getInstruction(MCInst &MI, uint64_t &Size,
ArrayRef<uint8_t> Bytes, uint64_t Address,
raw_ostream &CStream) const override;
};
} // end anonymous namespace
static MCDisassembler *createTMS34010Disassembler(const Target &T,
const MCSubtargetInfo &STI,
MCContext &Ctx) {
return new TMS34010Disassembler(STI, Ctx);
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010Disassembler() {
TargetRegistry::RegisterMCDisassembler(getTheTMS34010Target(),
createTMS34010Disassembler);
}
static const MCRegister GPRDecoderTable[] = {
TMS34010::A0, TMS34010::A1, TMS34010::A2, TMS34010::A3,
TMS34010::A4, TMS34010::A5, TMS34010::A6, TMS34010::A7,
TMS34010::A8, TMS34010::A9, TMS34010::A10, TMS34010::A11,
TMS34010::A12, TMS34010::A13, TMS34010::A14, TMS34010::A15,
TMS34010::B0, TMS34010::B1, TMS34010::B2, TMS34010::B3,
TMS34010::B4, TMS34010::B5, TMS34010::B6, TMS34010::B7,
TMS34010::B8, TMS34010::B9, TMS34010::B10, TMS34010::B11,
TMS34010::B12, TMS34010::B13, TMS34010::B14, TMS34010::B15};
static DecodeStatus DecodeGPRRegisterClass(MCInst &MI, uint64_t RegNo,
uint64_t Address,
const MCDisassembler *Decoder) {
if (RegNo >= std::size(GPRDecoderTable))
return MCDisassembler::Fail;
MI.addOperand(MCOperand::createReg(GPRDecoderTable[RegNo]));
return MCDisassembler::Success;
}
static DecodeStatus DecodeSETFFs(MCInst &MI, uint64_t Field, uint64_t Address,
const MCDisassembler *Decoder) {
MI.addOperand(MCOperand::createImm(Field == 0 ? 32 : Field));
return MCDisassembler::Success;
}
static DecodeStatus DecodeInvImm(MCInst &MI, uint64_t Field, uint64_t Address,
const MCDisassembler *Decoder) {
MI.addOperand(
MCOperand::createImm(static_cast<uint32_t>(~Field)));
return MCDisassembler::Success;
}
static DecodeStatus DecodeNegK(MCInst &MI, uint64_t Field, uint64_t Address,
const MCDisassembler *Decoder) {
// SRA/SRL store (-K) & 31; zero represents 32.
unsigned K = Field == 0 ? 32 : 32 - Field;
MI.addOperand(MCOperand::createImm(K));
return MCDisassembler::Success;
}
#include "TMS34010GenDisassemblerTables.inc"
/// Length of the instruction at the start of Bytes, in bytes. This mirrors
/// the operands' fixed widths in the .td file; the generated decoder verifies
/// the actual bits. 0 means the input is too short or the opcode is not a
/// supported TMS34010 encoding.
static unsigned instructionLength(uint16_t Op, ArrayRef<uint8_t> Bytes) {
unsigned Len = 2;
uint16_t Subop = Op & 0x01e0;
switch (Op & 0xfe00) {
case 0x0000:
break;
case 0x0200:
if (Subop == 0x0140)
Len = 10; // MOVB @a1, @a2 (not currently decoded)
break;
case 0x0400:
case 0x0600:
if (Subop == 0x0180 || Subop == 0x01a0 || Subop == 0x01e0)
Len = 6;
else if (Subop == 0x01c0)
Len = 10; // MOVE @a1, @a2 (not currently decoded)
break;
case 0x0800:
if (Subop == 0x0180 || Subop == 0x01a0 || Subop == 0x01c0)
Len = 4;
else if (Subop == 0x01e0)
Len = 6;
break;
case 0x0a00:
if (Subop == 0x0100 || Subop == 0x0140 || Subop == 0x01e0)
Len = 4;
else if (Subop == 0x0120 || Subop == 0x0160 || Subop == 0x0180 ||
Subop == 0x01a0 || Subop == 0x01c0)
Len = 6;
break;
case 0x0c00:
if (Subop == 0x0100 || Subop == 0x0140)
Len = 6;
else if (Subop == 0x0120 || Subop == 0x0180 || Subop == 0x01a0 ||
Subop == 0x01c0)
Len = 4;
break;
case 0x0e00:
break;
case 0x1000: case 0x1200: case 0x1400: case 0x1600:
case 0x1800: case 0x1a00: case 0x1c00: case 0x1e00:
case 0x2000: case 0x2200: case 0x2400: case 0x2600:
case 0x2800: case 0x2a00: case 0x2c00: case 0x2e00:
case 0x3000: case 0x3200: case 0x3800: case 0x3a00:
case 0x3c00: case 0x3e00:
case 0x4000: case 0x4200: case 0x4400: case 0x4600:
case 0x4800: case 0x4a00: case 0x4c00: case 0x4e00:
case 0x5000: case 0x5200: case 0x5400: case 0x5600:
case 0x5800: case 0x5a00: case 0x5c00: case 0x5e00:
case 0x6000: case 0x6200: case 0x6400: case 0x6600:
case 0x6800: case 0x6a00: case 0x6c00: case 0x6e00:
break;
case 0x8000: case 0x8200: case 0x8400: case 0x8600:
case 0x8800: case 0x8a00: case 0x8c00: case 0x8e00:
case 0x9000: case 0x9200: case 0x9400: case 0x9600:
case 0x9800: case 0x9a00: case 0x9c00:
case 0xa000: case 0xa200: case 0xa400: case 0xa600:
case 0xa800: case 0xaa00:
break;
case 0xac00: case 0xae00: case 0xb000: case 0xb200:
case 0xb400: case 0xb600:
Len = 4;
break;
case 0xb800: case 0xba00: case 0xbc00:
Len = 6;
break;
case 0xc000: case 0xc200: case 0xc400: case 0xc600:
case 0xc800: case 0xca00: case 0xcc00: case 0xce00: {
uint8_t Lo8 = Op & 0xff;
if (Lo8 == 0x80)
Len = 6;
else if (Lo8 == 0x00)
Len = 4;
break;
}
case 0xd000: case 0xd200:
Len = 4;
break;
case 0xd400: case 0xd600:
if (Subop == 0x0000)
Len = 6;
break;
case 0xde00:
break;
default:
break;
}
if (Bytes.size() < Len)
return 0;
return Len;
}
static bool isBRegister(MCRegister Reg) {
return Reg >= TMS34010::B0 && Reg <= TMS34010::B15;
}
/// Many TMS34010 encodings share bit 4 between the two register operands.
/// TableGen decodes the four-bit source as an A register; recover the real
/// file from the five-bit destination.
static void fixSameFile(MCInst &MI, unsigned SrcIdx, unsigned DstIdx) {
if (SrcIdx >= MI.getNumOperands() || DstIdx >= MI.getNumOperands())
return;
if (!MI.getOperand(SrcIdx).isReg() || !MI.getOperand(DstIdx).isReg())
return;
MCRegister Dst = MI.getOperand(DstIdx).getReg();
if (!isBRegister(Dst))
return;
MCRegister Src = MI.getOperand(SrcIdx).getReg();
unsigned Low = Src >= TMS34010::A0 ? Src - TMS34010::A0 : 0;
if (Low > 15)
return;
MI.getOperand(SrcIdx).setReg(GPRDecoderTable[16 + Low]);
}
/// MOVE register-to-register stores the source file in bit 4 and the
/// cross-file bit in bit 9. The destination file is their xor.
static void fixMoveRegisterFile(MCInst &MI, uint16_t Op) {
if (MI.getNumOperands() < 2)
return;
unsigned SrcLow = (Op >> 5) & 0xf;
unsigned DstLow = Op & 0xf;
bool SrcB = (Op & 0x10) != 0;
bool DstB = SrcB ^ ((Op & 0x0200) != 0);
// MC_MOVE's operand order is (outs dst, ins src).
MI.getOperand(0).setReg(GPRDecoderTable[DstLow + (DstB ? 16 : 0)]);
MI.getOperand(1).setReg(GPRDecoderTable[SrcLow + (SrcB ? 16 : 0)]);
}
DecodeStatus TMS34010Disassembler::getInstruction(MCInst &MI, uint64_t &Size,
ArrayRef<uint8_t> Bytes,
uint64_t Address,
raw_ostream &CStream) const {
if (Bytes.size() < 2) {
Size = 0;
return MCDisassembler::Fail;
}
uint16_t Word0 = support::endian::read16le(Bytes.data());
unsigned Len = instructionLength(Word0, Bytes);
if (!Len) {
Size = 0;
return MCDisassembler::Fail;
}
uint64_t Insn = Word0;
if (Len >= 4)
Insn |= static_cast<uint64_t>(
support::endian::read16le(Bytes.data() + 2))
<< 16;
if (Len >= 6)
Insn |= static_cast<uint64_t>(
support::endian::read16le(Bytes.data() + 4))
<< 32;
// The short ADDI/MOVI/CMPI/SUBI forms are selected by the emitter and are
// not separate .td encodings (they would be ambiguous in the asm matcher),
// so synthesise their MCInsts here.
if (Len == 4) {
uint16_t Base = Word0 & 0xffe0;
if (Base == 0x0b00 || Base == 0x09c0 || Base == 0x0b40 ||
Base == 0x0be0) {
uint16_t Raw = support::endian::read16le(Bytes.data() + 2);
unsigned Opc;
int64_t Value;
switch (Base) {
case 0x0b00:
Opc = TMS34010::MC_ADDI;
Value = static_cast<int16_t>(Raw);
break;
case 0x09c0:
Opc = TMS34010::MC_MOVI;
Value = static_cast<int16_t>(Raw);
break;
case 0x0b40:
Opc = TMS34010::MC_CMPI;
Value = static_cast<uint16_t>(~Raw);
break;
default:
Opc = TMS34010::MC_SUBI;
Value = static_cast<uint16_t>(~Raw);
break;
}
MI.clear();
MI.setOpcode(Opc);
MI.addOperand(MCOperand::createReg(GPRDecoderTable[Word0 & 0x1f]));
MI.addOperand(MCOperand::createImm(Value));
Size = 4;
return MCDisassembler::Success;
}
}
const uint8_t *Table = nullptr;
switch (Len) {
case 2:
Table = DecoderTable16;
break;
case 4:
Table = DecoderTable32;
break;
case 6:
Table = DecoderTable48;
break;
default:
Size = 0;
return MCDisassembler::Fail;
}
MI.clear();
DecodeStatus Result =
decodeInstruction(Table, MI, Insn, Address, this, STI);
if (Result == MCDisassembler::Fail) {
Size = 0;
return Result;
}
// Repair the register-file bit for the encodings that share it.
switch (MI.getOpcode()) {
case TMS34010::MC_MOVE:
fixMoveRegisterFile(MI, Word0);
break;
case TMS34010::MC_ADD:
case TMS34010::MC_SUB:
case TMS34010::MC_CMP:
case TMS34010::MC_AND:
case TMS34010::MC_OR:
case TMS34010::MC_XOR:
case TMS34010::MC_MPYS:
case TMS34010::MC_MPYU:
case TMS34010::MC_DIVS:
case TMS34010::MC_DIVU:
case TMS34010::MC_SLA:
case TMS34010::MC_SLL:
case TMS34010::MC_SRA:
case TMS34010::MC_SRL:
case TMS34010::MC_RL:
case TMS34010::MC_MOVE_STORE:
case TMS34010::MC_MOVE_STORE_POST:
case TMS34010::MC_MOVE_STORE_PRE:
case TMS34010::MC_MOVE_BOTH:
case TMS34010::MC_MOVB_BOTH:
case TMS34010::MC_MOVE_BOTH_POST:
case TMS34010::MC_MOVE_BOTH_PRE:
case TMS34010::MC_MOVE_STORE_DISP:
case TMS34010::MC_MOVB_STORE:
case TMS34010::MC_MOVB_STORE_DISP:
fixSameFile(MI, 0, 1);
break;
case TMS34010::MC_MOVE_LOAD:
case TMS34010::MC_MOVE_LOAD_POST:
case TMS34010::MC_MOVE_LOAD_PRE:
case TMS34010::MC_MOVE_LOAD_DISP:
case TMS34010::MC_MOVB_LOAD:
case TMS34010::MC_MOVB_LOAD_DISP:
fixSameFile(MI, 1, 0);
break;
case TMS34010::MC_MOVE_BOTH_DISP:
case TMS34010::MC_MOVE_DISP_POST:
case TMS34010::MC_MOVB_BOTH_DISP:
fixSameFile(MI, 0, 2);
break;
default:
break;
}
// CLR and XOR share an encoding: XOR Rd, Rd is CLR. CLR is
// isAsmParserOnly so the decoder table contains only XOR; rewrite it after
// the register-file repair, otherwise B-file CLR cannot compare equal.
if (MI.getOpcode() == TMS34010::MC_XOR && MI.getNumOperands() == 2 &&
MI.getOperand(0).isReg() && MI.getOperand(1).isReg() &&
MI.getOperand(0).getReg() == MI.getOperand(1).getReg()) {
MCRegister Dst = MI.getOperand(1).getReg();
MI.clear();
MI.setOpcode(TMS34010::MC_CLR);
MI.addOperand(MCOperand::createReg(Dst));
}
Size = Len;
return Result;
}
@@ -0,0 +1,18 @@
//===-- TMS34010Disassembler.h - TMS34010 disassembler ----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The decoder is private to TMS34010Disassembler.cpp; it is registered through
// LLVMInitializeTMS34010Disassembler in that file. This header exists so the
// component follows the target layout.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010DISASSEMBLER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010DISASSEMBLER_H
#endif
@@ -7,15 +7,36 @@
//===----------------------------------------------------------------------===//
#include "TMS34010InstPrinter.h"
#include "TMS34010MCTargetDesc.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
#include "TMS34010GenAsmWriter.inc"
namespace {
const MCRegister GPRTable[] = {
TMS34010::A0, TMS34010::A1, TMS34010::A2, TMS34010::A3,
TMS34010::A4, TMS34010::A5, TMS34010::A6, TMS34010::A7,
TMS34010::A8, TMS34010::A9, TMS34010::A10, TMS34010::A11,
TMS34010::A12, TMS34010::A13, TMS34010::A14, TMS34010::A15,
TMS34010::B0, TMS34010::B1, TMS34010::B2, TMS34010::B3,
TMS34010::B4, TMS34010::B5, TMS34010::B6, TMS34010::B7,
TMS34010::B8, TMS34010::B9, TMS34010::B10, TMS34010::B11,
TMS34010::B12, TMS34010::B13, TMS34010::B14, TMS34010::B15};
} // end anonymous namespace
void TMS34010InstPrinter::printRegName(raw_ostream &O, MCRegister Reg) {
// GSPA spells register 15 of the A file "SP" and rejects "A15".
if (Reg == TMS34010::A15) {
O << "SP";
return;
}
O << getRegisterName(Reg);
}
@@ -23,6 +44,29 @@ void TMS34010InstPrinter::printInst(const MCInst *MI, uint64_t Address,
StringRef Annot,
const MCSubtargetInfo &STI,
raw_ostream &O) {
const unsigned Opcode = MI->getOpcode();
if ((Opcode == TMS34010::MC_MMTM || Opcode == TMS34010::MC_MMFM) &&
MI->getNumOperands() == 2 && MI->getOperand(0).isReg() &&
MI->getOperand(1).isImm()) {
O << "\t" << (Opcode == TMS34010::MC_MMTM ? "MMTM " : "MMFM ");
printRegName(O, MI->getOperand(0).getReg());
unsigned Mask = static_cast<unsigned>(MI->getOperand(1).getImm()) & 0xffff;
unsigned File = (MI->getOperand(0).getReg() >= TMS34010::B0 &&
MI->getOperand(0).getReg() <= TMS34010::B15)
? 16
: 0;
for (unsigned I = 0; I < 16; ++I) {
unsigned Bit = Opcode == TMS34010::MC_MMTM ? (Mask >> (15 - I)) & 1
: (Mask >> I) & 1;
if (!Bit)
continue;
O << ", ";
printRegName(O, GPRTable[File + I]);
}
printAnnotation(O, Annot);
return;
}
printInstruction(MI, Address, O);
printAnnotation(O, Annot);
}
@@ -31,7 +75,7 @@ void TMS34010InstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
const MCOperand &Op = MI->getOperand(OpNo);
if (Op.isReg()) {
O << getRegisterName(Op.getReg());
printRegName(O, Op.getReg());
} else if (Op.isImm()) {
O << Op.getImm();
} else {
@@ -0,0 +1,188 @@
//===-- TMS34010MCAsmBackend.cpp - TMS34010 assembler backend -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCAsmBackend.h"
#include "TMS34010MCObjectWriter.h"
#include "TMS34010MCTargetDesc.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCSection.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
using namespace llvm;
namespace {
/// `llvm-mc -triple=tms34010` defaults to the ELF object format (the triple
/// has no OS component), while the CodeGen path uses the target's TI-COFF
/// writer directly. This tiny ELF target writer keeps the generic llvm-mc
/// object path self-consistent; the MC round-trip only needs the .text bytes.
class TMS34010ELFObjectTargetWriter : public MCELFObjectTargetWriter {
public:
TMS34010ELFObjectTargetWriter()
: MCELFObjectTargetWriter(/*Is64Bit=*/false, ELF::ELFOSABI_NONE,
ELF::EM_NONE, /*HasRelocationAddend=*/false) {}
unsigned getRelocType(const MCFixup &Fixup, const MCValue &Target,
bool IsPCRel) const override {
// The MC tests use absolute numeric operands, so this path is not
// exercised; TI-COFF relocations are handled by TMS34010COFFObjectWriter.
return 0;
}
};
/// TI-COFF relocations store the addend in the relocated field and use bit
/// addresses. A defined symbol contributes its section-relative bit offset;
/// the expression constant is already a bit offset (tms-bitaddr --scale
/// scaled address expressions, and --normalize keeps late GEP offsets in
/// bits).
uint64_t tms34010BitAddend(const MCAssembler &Asm, const MCValue &Target) {
int64_t Addend = Target.getConstant();
if (const MCSymbol *Add = Target.getAddSym())
if (Add->isDefined() && Add->isInSection())
Addend += static_cast<int64_t>(Asm.getSymbolOffset(*Add)) * 8;
return static_cast<uint64_t>(Addend);
}
} // end anonymous namespace
TMS34010MCAsmBackend::TMS34010MCAsmBackend(const Target &T)
: MCAsmBackend(llvm::endianness::little) {}
MCFixupKindInfo
TMS34010MCAsmBackend::getFixupKindInfo(MCFixupKind Kind) const {
if (Kind == TMS34010::FK_JR8)
return {"FK_TMS34010_JR8", 0, 8, 0};
// The built-in data fixups are the only other kinds the object path produces.
return MCAsmBackend::getFixupKindInfo(Kind);
}
/// Decode the backend's PC-relative byte value into the ISA's signed word
/// displacement. The fixup sits on the instruction word (the displacement is
/// bits 7-0, the low byte in memory), so the ISA's reference point (PC+16) is
/// two bytes past it:
/// disp = (target - (pc + 2)) / 2 == (Value - 2) / 2.
static std::optional<int64_t> getJRDisplacement(uint64_t Value) {
int64_t Delta = static_cast<int64_t>(Value) - 2;
if (Delta % 2)
return std::nullopt;
return Delta / 2;
}
bool TMS34010MCAsmBackend::mayNeedRelaxation(
unsigned Opcode, ArrayRef<MCOperand> Operands,
const MCSubtargetInfo &STI) const {
// Only a symbolic target can be out of range. The assembler's JR/JRcc take a
// numeric displacement and encode exactly what the source asked for.
return TMS34010::isShortBranch(Opcode) && !Operands.empty() &&
Operands[0].isExpr();
}
bool TMS34010MCAsmBackend::fixupNeedsRelaxationAdvanced(
const MCFragment &F, const MCFixup &Fixup, const MCValue &Target,
uint64_t Value, bool Resolved) const {
if (Fixup.getKind() != TMS34010::FK_JR8)
return false;
// A cross-section or undefined target cannot be resolved to a displacement.
if (!Resolved)
return true;
std::optional<int64_t> Disp = getJRDisplacement(Value);
// Zero is the 16-bit relative form and -128 is the absolute-JA marker, so
// neither is encodable in one word. Anything else outside [-127,127] needs
// the three-word absolute form.
return !Disp || *Disp == 0 || *Disp < -127 || *Disp > 127;
}
void TMS34010MCAsmBackend::relaxInstruction(
MCInst &Inst, const MCSubtargetInfo &STI) const {
unsigned Long = TMS34010::getLongBranchOpcode(Inst.getOpcode());
if (!Long)
llvm_unreachable("TMS34010: no wider form for this branch");
Inst.setOpcode(Long);
}
void TMS34010MCAsmBackend::applyFixup(const MCFragment &F,
const MCFixup &Fixup,
const MCValue &Target, uint8_t *Data,
uint64_t Value, bool IsResolved) {
if (Fixup.getKind() == TMS34010::FK_JR8) {
if (!IsResolved)
report_fatal_error(
"TMS34010: an unresolved JR displacement must be relaxed");
std::optional<int64_t> Disp = getJRDisplacement(Value);
if (!Disp || *Disp == 0 || *Disp < -127 || *Disp > 127)
report_fatal_error(
"TMS34010: JR displacement is out of range after relaxation");
Data[0] = static_cast<uint8_t>(static_cast<int8_t>(*Disp));
return;
}
if (Target.getSubSym())
report_fatal_error(
"TMS34010: symbol differences are not supported in TI-COFF objects");
if (!IsResolved && Fixup.getKind() != FK_Data_4)
report_fatal_error(
"TMS34010: only 32-bit symbol relocations are supported");
// An unresolved symbol reference becomes an R-RELLONG relocation. The
// relocated field holds the addend (a bit offset), as GSPA 2.00 does.
if (!IsResolved)
maybeAddReloc(F, Fixup, Target, Value, /*IsResolved=*/false);
uint64_t Field = IsResolved ? Value : tms34010BitAddend(*Asm, Target);
switch (Fixup.getKind()) {
case FK_Data_1:
Data[0] = static_cast<uint8_t>(Field);
break;
case FK_Data_2:
Data[0] = static_cast<uint8_t>(Field);
Data[1] = static_cast<uint8_t>(Field >> 8);
break;
case FK_Data_4:
Data[0] = static_cast<uint8_t>(Field);
Data[1] = static_cast<uint8_t>(Field >> 8);
Data[2] = static_cast<uint8_t>(Field >> 16);
Data[3] = static_cast<uint8_t>(Field >> 24);
break;
default:
report_fatal_error(Twine("TMS34010: unsupported fixup kind ") +
Twine(Fixup.getKind()));
}
}
bool TMS34010MCAsmBackend::writeNopData(raw_ostream &OS, uint64_t Count,
const MCSubtargetInfo *STI) const {
if (Count % 2)
return false;
// NOP is 0x0300, stored little-endian.
for (uint64_t I = 0; I < Count; I += 2)
OS << '\x00' << '\x03';
return true;
}
std::unique_ptr<MCObjectTargetWriter>
TMS34010MCAsmBackend::createObjectTargetWriter() const {
return std::make_unique<TMS34010ELFObjectTargetWriter>();
}
std::unique_ptr<MCObjectWriter>
TMS34010MCAsmBackend::createObjectWriter(
raw_pwrite_stream &OS, std::shared_ptr<TMS34010GlobalSymbols> Globals) const {
return std::make_unique<TMS34010COFFObjectWriter>(OS, std::move(Globals));
}
@@ -0,0 +1,62 @@
//===-- TMS34010MCAsmBackend.h - TMS34010 assembler backend -----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
#include "TMS34010MCObjectWriter.h"
#include "llvm/MC/MCAsmBackend.h"
namespace llvm {
class MCObjectTargetWriter;
class MCObjectWriter;
class MCRegisterInfo;
class MCSubtargetInfo;
class MCTargetOptions;
class MCInst;
class MCOperand;
class Target;
class raw_pwrite_stream;
class TMS34010MCAsmBackend : public MCAsmBackend {
public:
TMS34010MCAsmBackend(const Target &T);
MCFixupKindInfo getFixupKindInfo(MCFixupKind Kind) const override;
void applyFixup(const MCFragment &F, const MCFixup &Fixup,
const MCValue &Target, uint8_t *Data, uint64_t Value,
bool IsResolved) override;
bool mayNeedRelaxation(unsigned Opcode, ArrayRef<MCOperand> Operands,
const MCSubtargetInfo &STI) const override;
bool fixupNeedsRelaxationAdvanced(const MCFragment &F, const MCFixup &Fixup,
const MCValue &Target, uint64_t Value,
bool Resolved) const override;
void relaxInstruction(MCInst &Inst,
const MCSubtargetInfo &STI) const override;
bool writeNopData(raw_ostream &OS, uint64_t Count,
const MCSubtargetInfo *STI) const override;
std::unique_ptr<MCObjectTargetWriter> createObjectTargetWriter() const override;
/// Build the TI-COFF writer used by `llc -filetype=obj`. The generic
/// MCAsmBackend::createObjectWriter only knows LLVM's built-in object
/// formats, so the target machine calls this directly.
std::unique_ptr<MCObjectWriter>
createObjectWriter(raw_pwrite_stream &OS,
std::shared_ptr<TMS34010GlobalSymbols> Globals) const;
};
} // namespace llvm
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
@@ -19,7 +19,17 @@ TMS34010MCAsmInfo::TMS34010MCAsmInfo(const Triple &TT,
CalleeSaveStackSlotSize = 4;
CommentString = ";";
SeparatorString = ",";
// A comma separates operands, not statements. GSPA statements end at the
// newline, so the lexer must not turn a comma into EndOfStatement.
SeparatorString = "\n";
// GSPA 2.00 identifiers accept only alphanumerics and `_`, so block labels
// must be dot-free. Set both prefixes explicitly rather than relying on the
// generic defaults: InternalSymbolPrefix names basic-block labels (LBB0_1),
// and LinkerPrivateGlobalPrefix covers jump tables and linker-private
// symbols, keeping every generated symbol GSPA-valid and stable.
InternalSymbolPrefix = "L";
LinkerPrivateGlobalPrefix = "L";
// GSPA directives express alignment and section sizes in bits.
AlignmentIsInBytes = false;
@@ -0,0 +1,310 @@
//===-- TMS34010MCCodeEmitter.cpp - TMS34010 code emitter -----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// TMS34010 instructions are one to three 16-bit words, stored little-endian.
// TableGen supplies the base opcode and operand placement; this file fixes the
// two immediate forms GSPA 2.00 selects between.
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCCodeEmitter.h"
#include "TMS34010MCTargetDesc.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
using namespace llvm;
namespace {
class TMS34010MCCodeEmitter : public MCCodeEmitter {
MCContext &Ctx;
const MCInstrInfo &MCII;
/// TableGen'erated: the fixed opcode bits with every named operand merged
/// into its field.
uint64_t getBinaryCodeForInstr(const MCInst &MI,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// Return the encoding of one operand, recording a fixup when the operand
/// is an unresolved expression.
unsigned getMachineOpValue(const MCInst &MI, const MCOperand &MO,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// SETF encodes fs == 32 as zero.
unsigned getSETFFsValue(const MCInst &MI, unsigned OpNo,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// CMPI/SUBI/ANDI store the one's complement of the immediate.
unsigned getInvImmValue(const MCInst &MI, unsigned OpNo,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// SRA/SRL store (-K) & 31.
unsigned getNegKValue(const MCInst &MI, unsigned OpNo,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
public:
TMS34010MCCodeEmitter(MCContext &Ctx, const MCInstrInfo &MCII)
: Ctx(Ctx), MCII(MCII) {}
void encodeInstruction(const MCInst &MI, SmallVectorImpl<char> &CB,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const override;
};
} // end anonymous namespace
unsigned TMS34010MCCodeEmitter::getMachineOpValue(
const MCInst &MI, const MCOperand &MO, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
if (MO.isReg())
return Ctx.getRegisterInfo()->getEncodingValue(MO.getReg());
if (MO.isImm())
return static_cast<unsigned>(MO.getImm());
if (MO.isExpr()) {
// The instruction word carries a placeholder; encodeInstruction records
// an FK_Data_4 fixup over the 32-bit field so the backend can write the
// bit-address addend and the object writer can emit an R-RELLONG
// relocation. The operand itself is encoded below as zero.
return 0;
}
llvm_unreachable("Unsupported TMS34010 operand");
}
unsigned TMS34010MCCodeEmitter::getSETFFsValue(
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
const MCOperand &MO = MI.getOperand(OpNo);
if (!MO.isImm())
report_fatal_error("TMS34010: SETF field size must be a constant");
int64_t Value = MO.getImm();
// The ISA stores fs == 32 as zero; 0 is not a legal source spelling in
// GSPA 2.00, which warns about it.
if (Value == 32)
return 0;
if (Value < 0 || Value > 31)
report_fatal_error("TMS34010: SETF field size must be 1..32");
return static_cast<unsigned>(Value);
}
unsigned TMS34010MCCodeEmitter::getInvImmValue(
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
const MCOperand &MO = MI.getOperand(OpNo);
if (!MO.isImm())
return 0;
return static_cast<uint32_t>(~static_cast<uint64_t>(MO.getImm()));
}
unsigned TMS34010MCCodeEmitter::getNegKValue(
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
const MCOperand &MO = MI.getOperand(OpNo);
if (!MO.isImm())
report_fatal_error("TMS34010: shift count must be a constant");
return static_cast<unsigned>((-MO.getImm()) & 0x1F);
}
static void writeWord(SmallVectorImpl<char> &CB, uint16_t Value) {
support::endian::write<uint16_t>(CB, Value, llvm::endianness::little);
}
void TMS34010MCCodeEmitter::encodeInstruction(
const MCInst &MI, SmallVectorImpl<char> &CB,
SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {
const unsigned Opcode = MI.getOpcode();
// The one-word relative branches take a symbolic target in CodeGen. Their
// operand is not an absolute address field: record an 8-bit PC-relative fixup
// on the displacement byte; the backend subtracts PC+16 and converts the
// result to the ISA's signed word displacement. A numeric operand (the
// assembler's JR/JRcc) needs no fixup and falls through to the generated
// encoder below.
if (TMS34010::isShortBranch(Opcode) && MI.getNumOperands() == 1 &&
MI.getOperand(0).isExpr()) {
uint16_t Bits =
static_cast<uint16_t>(getBinaryCodeForInstr(MI, Fixups, STI));
writeWord(CB, Bits);
Fixups.push_back(MCFixup::create(0, MI.getOperand(0).getExpr(),
TMS34010::FK_JR8, /*PCRel=*/true));
return;
}
// SRAK/SRLK (the CodeGen instructions) store (-K) & 31 in bits 9..5, but
// TableGen drops that negated field expression for these non-MC defs. The
// MC_SRAK/MC_SRLK assembler defs use TMS34010NegK and are emitted by the
// generated table. Encode the CodeGen form explicitly so `llc -filetype=obj`
// matches the `llc -filetype=asm` text (GSPA) encoding.
if (Opcode == TMS34010::SRAK || Opcode == TMS34010::SRLK) {
unsigned Dst =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
const MCOperand &MO = MI.getOperand(2);
if (!MO.isImm())
report_fatal_error("TMS34010: shift count must be a constant");
uint16_t Bits =
static_cast<uint16_t>(Opcode == TMS34010::SRAK ? 0x2800 : 0x2C00);
Bits |= static_cast<uint16_t>(((-MO.getImm()) & 0x1F) << 5);
Bits |= static_cast<uint16_t>(Dst & 0x1F);
writeWord(CB, Bits);
return;
}
// MOVE register-to-register has a cross-file bit (bit 9) that is not a
// straight operand field: it is src{4} xor dst{4}. TableGen's fixed-field
// emitter cannot express that, so encode this one instruction manually.
if (Opcode == TMS34010::MC_MOVE) {
unsigned Dst =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
unsigned Src =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(1).getReg());
uint16_t Bits = 0x4C00;
Bits |= static_cast<uint16_t>(((Src >> 4) ^ (Dst >> 4)) << 9);
Bits |= static_cast<uint16_t>((Src & 0xF) << 5);
Bits |= static_cast<uint16_t>(Src & 0x10);
Bits |= static_cast<uint16_t>(Dst & 0xF);
writeWord(CB, Bits);
return;
}
// GSPA 2.00 encodes ADDI, MOVI, CMPI and SUBI with the short 16-bit
// immediate form when the value fits in a signed 16-bit field, and with the
// long 32-bit form otherwise. For ADDI, MOVI and CMPI the long encoding is
// the .td encoding and the short form clears bit 5; SUBI has a separate
// short opcode (0x0BE0) and stores the one's complement. A symbol operand
// is always long and needs a relocation.
bool ShortImm = false;
unsigned ImmOp = 0;
switch (Opcode) {
case TMS34010::ADDI: ImmOp = 2; ShortImm = true; break;
case TMS34010::ADDSTK: ImmOp = 0; ShortImm = true; break;
case TMS34010::MOVI: ImmOp = 1; ShortImm = true; break;
case TMS34010::CMPI: ImmOp = 0; ShortImm = true; break;
case TMS34010::MC_ADDI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_SUBI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_CMPI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_MOVI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_ANDI:
case TMS34010::MC_ORI:
case TMS34010::MC_XORI:
break;
default:
break;
}
if (ShortImm) {
const MCOperand &MO = MI.getOperand(ImmOp);
if (MO.isImm() && isInt<16>(MO.getImm())) {
if (Opcode == TMS34010::MC_SUBI) {
// SUBI's short form is a different opcode word, not a bit in the long
// form. Operand 0 is the destination (MC operand order is outs, ins).
uint64_t Dst =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
writeWord(CB, static_cast<uint16_t>(0x0BE0 | (Dst & 0x1F)));
writeWord(CB, static_cast<uint16_t>(~MO.getImm()));
return;
}
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
writeWord(CB, static_cast<uint16_t>(Bits) & ~uint16_t{0x0020});
bool Invert = Opcode == TMS34010::MC_CMPI || Opcode == TMS34010::CMPI;
writeWord(CB, static_cast<uint16_t>(Invert ? ~MO.getImm()
: MO.getImm()));
return;
}
}
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
unsigned Size = MCII.get(Opcode).getSize();
for (unsigned Word = 0; Word < Size / 2; ++Word) {
writeWord(CB, static_cast<uint16_t>(Bits));
Bits >>= 16;
}
// Every TMS34010 absolute-address operand occupies the 32-bit word(s)
// following the opcode. Record an FK_Data_4 fixup over that field; the
// backend turns an unresolved symbol reference into a bit-addend TI-COFF
// relocation. The operand index differs per instruction because TableGen
// orders MCInst operands as outs then ins.
int AddrOp = -1;
switch (Opcode) {
case TMS34010::MOVI:
case TMS34010::MC_MOVI:
// CMPI, SUBI and ANDI store the one's complement of the operand; a symbol
// addend would have to be complemented too, which GSPA does not express.
case TMS34010::MC_ADDI:
case TMS34010::MC_ORI:
case TMS34010::MC_XORI:
AddrOp = 1;
break;
case TMS34010::MC_MOVE_ABS_STORE:
case TMS34010::MC_MOVE_ABS_LOAD:
case TMS34010::MC_MOVB_ABS_STORE:
case TMS34010::MC_MOVB_ABS_LOAD:
AddrOp = 1;
break;
case TMS34010::MC_MOVE_ABS_POST:
case TMS34010::CALLA:
case TMS34010::MC_CALLA:
case TMS34010::JUncond:
AddrOp = 0;
break;
default:
if (MCII.get(Opcode).isBranch())
AddrOp = 0;
break;
}
bool SawExpr = false;
if (AddrOp >= 0 && AddrOp < static_cast<int>(MI.getNumOperands())) {
const MCOperand &MO = MI.getOperand(AddrOp);
if (MO.isExpr()) {
// The fixup covers the opcode word plus a 32-bit absolute field. A
// future shorter branch must not silently acquire a misplaced
// relocation.
if (Size < 6)
report_fatal_error(
"TMS34010: symbol operand field is not a 32-bit absolute field");
Fixups.push_back(MCFixup::create(2, MO.getExpr(), FK_Data_4));
SawExpr = true;
}
}
// Any other instruction with an unresolved operand would silently encode
// zero; refuse instead of producing a wrong object. A 16-bit displacement
// or a field-size operand must be resolved by the assembler.
if (!SawExpr) {
for (unsigned I = 0, E = MI.getNumOperands(); I != E; ++I)
if (MI.getOperand(I).isExpr())
report_fatal_error(
"TMS34010: symbol operands are only supported for absolute "
"address/immediate fields");
}
}
MCCodeEmitter *llvm::createTMS34010MCCodeEmitter(const MCInstrInfo &MCII,
MCContext &Ctx) {
return new TMS34010MCCodeEmitter(Ctx, MCII);
}
#include "TMS34010GenMCCodeEmitter.inc"
@@ -0,0 +1,25 @@
//===-- TMS34010MCCodeEmitter.h - TMS34010 code emitter ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
#include "llvm/MC/MCCodeEmitter.h"
namespace llvm {
class MCContext;
class MCInstrInfo;
/// Create the TMS34010 machine code emitter.
MCCodeEmitter *createTMS34010MCCodeEmitter(const MCInstrInfo &MII,
MCContext &Ctx);
} // namespace llvm
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
@@ -0,0 +1,439 @@
//===-- TMS34010MCObjectWriter.cpp - TI-COFF object writer ---------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The TI-COFF dialect is the one GSPA 2.00 and GSPLNK 2.00 use (see
// build/m3/m3.coff and vendor/vt1200/dwt/loader/coff.py):
//
// file header: machine 0x0090, optional-header size 0, flags 0x0100
// section: name, paddr/vaddr 0, size in bits, raw/relocation file
// offsets in bytes, flags 0x20 (.text) / 0x40 (.data) /
// 0x80 (.bss)
// symbol: section symbols first, class C_STAT (3) with one 18-byte aux
// record; globals class C_EXT (2) with a bit-address value
// relocation: 10 bytes {bit address, symbol index, type}; type 0x11 is
// R-RELLONG and its field holds the bit-address addend
//
// A same-section reference uses symbol index 0xFFFF and the field holds the
// section-relative bit offset, exactly as GSPA emits. A cross-section
// reference uses the target section symbol and the field holds the target
// symbol's section-relative bit offset. An undefined reference uses its
// C_EXT symbol index and the field holds the expression constant.
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCObjectWriter.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCSection.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <string>
using namespace llvm;
namespace {
constexpr uint16_t MachineTMS34010 = 0x0090;
constexpr uint16_t FileFlagsAR32WR = 0x0100;
constexpr uint16_t SectionFlagText = 0x20;
constexpr uint16_t SectionFlagData = 0x40;
constexpr uint16_t SectionFlagBss = 0x80;
constexpr uint16_t RelocRellong = 0x11;
constexpr uint8_t ClassExternal = 2; // C_EXT
constexpr uint8_t ClassStatic = 3; // C_STAT (section symbol)
void append16(SmallVectorImpl<char> &V, uint16_t Value) {
support::endian::write<uint16_t>(V, Value, llvm::endianness::little);
}
void append32(SmallVectorImpl<char> &V, uint32_t Value) {
support::endian::write<uint32_t>(V, Value, llvm::endianness::little);
}
void write16(raw_pwrite_stream &OS, uint16_t Value) {
support::endian::write<uint16_t>(OS, Value, llvm::endianness::little);
}
void write32(raw_pwrite_stream &OS, uint32_t Value) {
support::endian::write<uint32_t>(OS, Value, llvm::endianness::little);
}
uint16_t flagsForSectionName(StringRef Name) {
if (Name == ".text")
return SectionFlagText;
if (Name == ".data")
return SectionFlagData;
if (Name == ".bss")
return SectionFlagBss;
llvm_unreachable("not a TI-COFF section");
}
} // end anonymous namespace
bool TMS34010MCStreamer::emitSymbolAttribute(MCSymbol *Symbol,
MCSymbolAttr Attribute) {
switch (Attribute) {
case MCSA_Global:
case MCSA_Weak:
case MCSA_WeakDefinition:
Globals->insert(Symbol);
break;
case MCSA_Local:
Globals->erase(Symbol);
break;
default:
// Visibility and type attributes do not change GSPA's flat symbol table.
break;
}
return true;
}
void TMS34010MCStreamer::emitCommonSymbol(MCSymbol *Symbol, uint64_t Size,
Align ByteAlignment) {
// Common symbols are writable zero-initialized storage. GSPA has no .comm
// directive, so place them in the virtual .bss section like an ordinary
// zero-initialized definition. Every .bss allocation is word-granular:
// GSPA and the VT1200 loader require a section size that is a whole number
// of 16-bit words.
unsigned AlignBytes = std::max<unsigned>(ByteAlignment.value(), 2);
Globals->insert(Symbol);
switchSection(getContext().getObjectFileInfo()->getBSSSection());
emitValueToAlignment(Align(AlignBytes));
emitLabel(Symbol);
emitZeros(Size);
emitValueToAlignment(Align(2));
}
void TMS34010COFFObjectWriter::recordRelocation(const MCFragment &F,
const MCFixup &Fixup,
MCValue Target,
uint64_t &FixedValue) {
if (Target.getSubSym())
report_fatal_error(
"TMS34010: symbol differences are not supported in TI-COFF objects");
const MCSymbol *Symbol = Target.getAddSym();
if (!Symbol)
report_fatal_error("TMS34010: relocation without a symbol");
// The backend has already written the bit-address addend into the relocated
// field; the writer only needs the target symbol and its section here.
Relocations.push_back(
{F.getParent(), Asm->getFragmentOffset(F) + Fixup.getOffset(), Symbol});
}
uint64_t TMS34010COFFObjectWriter::writeObject() {
const uint64_t Start = OS.tell();
struct SectionInfo {
StringRef Name;
const MCSection *Sec;
uint16_t Flags;
uint16_t SecNo;
SmallString<256> Data;
uint32_t RawPtr = 0;
uint32_t RelPtr = 0;
uint32_t SizeBits = 0;
uint16_t NReloc = 0;
};
// GSPA always writes .text, .data and .bss, in that order, even when a
// section is empty. Match that so the linker sees the same shape.
static constexpr StringLiteral Names[] = {".text", ".data", ".bss"};
SmallVector<SectionInfo, 3> Sections;
for (StringLiteral Name : Names) {
const MCSection *Found = nullptr;
for (const MCSection &Sec : *Asm)
if (Sec.getName() == Name) {
Found = &Sec;
break;
}
Sections.push_back({Name, Found, flagsForSectionName(Name),
static_cast<uint16_t>(Sections.size() + 1), {}, 0, 0,
0, 0});
}
// A registered non-empty section that is not one of the three TI-COFF
// sections would be silently lost; fail loudly instead.
for (const MCSection &Sec : *Asm) {
bool Known = false;
for (const SectionInfo &S : Sections)
if (S.Name == Sec.getName())
Known = true;
if (!Known && Sec.begin() != Sec.end())
report_fatal_error(Twine("TMS34010: section '") + Sec.getName() +
"' has no TI-COFF representation");
}
DenseMap<const MCSection *, unsigned> SectionIndex;
for (const SectionInfo &S : Sections)
if (S.Sec)
SectionIndex[S.Sec] = S.SecNo - 1;
// Buffer each section's relocated bytes. .bss is virtual and has no file
// bytes, so only its bit size is recorded.
for (SectionInfo &S : Sections) {
if (!S.Sec)
continue;
if (S.Flags == SectionFlagBss) {
S.SizeBits = static_cast<uint32_t>(Asm->getSectionAddressSize(*S.Sec) * 8);
} else {
raw_svector_ostream DataOS(S.Data);
Asm->writeSectionData(DataOS, S.Sec);
S.SizeBits = static_cast<uint32_t>(S.Data.size() * 8);
}
}
const SectionInfo *Text = &Sections[0];
if (!Text->Sec || Text->Data.empty())
report_fatal_error("TMS34010: object has no .text contents");
// Group relocations by section and remember which undefined symbols they
// reference. GSPA emits a C_EXT entry for every undefined symbol that a
// relocation mentions.
for (SectionInfo &S : Sections) {
if (S.Flags == SectionFlagBss)
continue;
for (const PendingReloc &R : Relocations)
if (R.Section == S.Sec)
++S.NReloc;
}
struct SymbolRecord {
std::string Name;
uint32_t Value;
int16_t Section;
uint8_t Class;
uint8_t NumAux;
uint32_t AuxSizeBits;
uint16_t AuxNReloc;
};
SmallVector<SymbolRecord, 16> Symbols;
DenseMap<const MCSymbol *, uint32_t> SymbolIndex;
// Section symbols first: one record plus one aux record each. The aux
// record repeats the section size in bits, as GSPA writes it.
for (const SectionInfo &S : Sections)
Symbols.push_back({S.Name.str(), 0, static_cast<int16_t>(S.SecNo),
ClassStatic, 1, S.SizeBits, S.NReloc});
// Section symbols occupy two records each (record + aux). Globals follow
// them and have no aux records, so record indices advance by one.
uint32_t NextRecordIndex = 2u * static_cast<uint32_t>(Sections.size());
// Defined globals. Their values are section-relative bit addresses.
for (const MCSymbol &Sym : Asm->symbols()) {
if (Sym.isVariable() || Sym.isTemporary())
continue;
if (!Sym.isDefined())
continue;
if (!Globals->contains(&Sym))
continue;
StringRef Name = Sym.getName();
if (Name.empty() || Name.starts_with("."))
continue;
if (!Sym.isInSection())
report_fatal_error(Twine("TMS34010: absolute global '") + Name +
"' has no TI-COFF representation");
const MCSection &Sec = Sym.getSection();
auto It = SectionIndex.find(&Sec);
if (It == SectionIndex.end())
report_fatal_error(Twine("TMS34010: global '") + Name +
"' is in section '" + Sec.getName() +
"' with no TI-COFF representation");
SymbolIndex[&Sym] = NextRecordIndex++;
Symbols.push_back({Name.str(),
static_cast<uint32_t>(Asm->getSymbolOffset(Sym) * 8),
static_cast<int16_t>(It->second + 1), ClassExternal, 0,
0, 0});
}
// Undefined symbols referenced by a relocation, in symbol-table order.
DenseMap<const MCSymbol *, bool> UndefinedReferenced;
for (const PendingReloc &R : Relocations)
if (R.Symbol->isUndefined())
UndefinedReferenced[R.Symbol] = true;
for (const MCSymbol &Sym : Asm->symbols()) {
if (Sym.isVariable() || Sym.isTemporary())
continue;
if (!Sym.isUndefined() || !UndefinedReferenced.contains(&Sym))
continue;
StringRef Name = Sym.getName();
if (Name.empty() || Name.starts_with("."))
continue;
SymbolIndex[&Sym] = NextRecordIndex++;
Symbols.push_back({Name.str(), 0, 0, ClassExternal, 0, 0, 0});
}
// Resolve relocation targets to symbol-table indices. A target section
// symbol is always at record index 2 * (section number - 1) because each
// section symbol is followed by exactly one aux record.
struct FinalReloc {
uint32_t AddrBits;
uint32_t Symbol;
};
DenseMap<const MCSection *, SmallVector<FinalReloc, 4>> RelocsBySection;
for (const PendingReloc &R : Relocations) {
uint32_t SymbolIndexValue;
const MCSymbol *Target = R.Symbol;
if (Target->isDefined() && Target->isInSection()) {
const MCSection &TargetSec = Target->getSection();
auto It = SectionIndex.find(&TargetSec);
if (It == SectionIndex.end())
report_fatal_error(Twine("TMS34010: relocation target '") +
Target->getName() + "' is in section '" +
TargetSec.getName() +
"' with no TI-COFF representation");
if (&TargetSec == R.Section) {
SymbolIndexValue = 0xFFFF; // same section: section-relative addend
} else {
SymbolIndexValue = It->second * 2; // the target section symbol
}
} else if (Target->isUndefined()) {
auto It = SymbolIndex.find(Target);
if (It == SymbolIndex.end())
report_fatal_error(Twine("TMS34010: relocation symbol '") +
Target->getName() + "' has no symbol-table entry");
SymbolIndexValue = It->second;
} else {
report_fatal_error(Twine("TMS34010: absolute relocation target '") +
Target->getName() + "' has no TI-COFF representation");
}
RelocsBySection[R.Section].push_back(
{static_cast<uint32_t>(R.Offset * 8), SymbolIndexValue});
}
// File layout: headers, raw section bytes, relocation blocks, symbols,
// string table.
uint32_t Cursor = 20 + 40 * static_cast<uint32_t>(Sections.size());
for (SectionInfo &S : Sections) {
if (S.Flags == SectionFlagBss) {
S.RawPtr = 0;
continue;
}
S.RawPtr = Cursor;
Cursor += static_cast<uint32_t>(S.Data.size());
}
for (SectionInfo &S : Sections) {
if (S.Flags == SectionFlagBss) {
S.RelPtr = 0;
continue;
}
S.RelPtr = Cursor;
Cursor += 10u * S.NReloc;
}
const uint32_t SymPtr = Cursor;
// Build the 18-byte symbol records (plus aux records) and the string table.
SmallString<256> SymbolTable;
SmallString<256> StringTable;
uint32_t NumSymbolEntries = 0;
auto AddSymbol = [&](const SymbolRecord &S) {
if (S.Name.size() <= 8) {
SmallString<8> ShortName(S.Name);
ShortName.resize(8, '\0');
SymbolTable.append(ShortName.begin(), ShortName.end());
} else {
append32(SymbolTable, 0);
append32(SymbolTable, static_cast<uint32_t>(4 + StringTable.size()));
StringTable.append(S.Name.begin(), S.Name.end());
StringTable.push_back('\0');
}
append32(SymbolTable, S.Value);
append16(SymbolTable, static_cast<uint16_t>(S.Section));
append16(SymbolTable, 0); // type
SymbolTable.push_back(static_cast<char>(S.Class));
SymbolTable.push_back(static_cast<char>(S.NumAux));
++NumSymbolEntries;
if (S.NumAux) {
// Section auxiliary record: bit size, relocation count, line count,
// then zeroed checksum/number/selection fields.
append32(SymbolTable, S.AuxSizeBits);
append16(SymbolTable, S.AuxNReloc);
append16(SymbolTable, 0);
SymbolTable.append(10, '\0');
NumSymbolEntries += S.NumAux;
}
};
for (const SymbolRecord &S : Symbols)
AddSymbol(S);
// File header (20 bytes).
write16(OS, MachineTMS34010);
write16(OS, static_cast<uint16_t>(Sections.size()));
write32(OS, 0); // TimeDateStamp: deterministic
write32(OS, SymPtr);
write32(OS, NumSymbolEntries);
write16(OS, 0); // SizeOfOptionalHeader
write16(OS, FileFlagsAR32WR);
// Section headers (40 bytes each).
for (const SectionInfo &S : Sections) {
SmallString<8> Name(S.Name);
Name.resize(8, '\0');
OS.write(Name.data(), Name.size());
write32(OS, 0); // PhysicalAddress
write32(OS, 0); // VirtualAddress
write32(OS, S.SizeBits); // SizeOfRawData, in bits
write32(OS, S.RawPtr); // PointerToRawData
write32(OS, S.RelPtr); // PointerToRelocations
write32(OS, 0); // PointerToLineNumbers
write16(OS, S.NReloc);
write16(OS, 0); // NumberOfLineNumbers
write32(OS, S.Flags);
}
// Raw data. .bss has no file bytes.
for (const SectionInfo &S : Sections)
if (S.Flags != SectionFlagBss)
OS.write(S.Data.data(), S.Data.size());
// Relocations. The address is a bit address within the section. .bss has
// no file bytes and must never carry a relocation; a future fixup in .bss is
// a writer bug, not something to drop silently.
for (const SectionInfo &S : Sections) {
auto It = RelocsBySection.find(S.Sec);
if (S.Flags == SectionFlagBss) {
if (It != RelocsBySection.end() && !It->second.empty())
report_fatal_error("TMS34010: .bss relocations are not supported");
continue;
}
if (It == RelocsBySection.end())
continue;
for (const FinalReloc &R : It->second) {
write32(OS, R.AddrBits);
write32(OS, R.Symbol);
write16(OS, RelocRellong);
}
}
// Symbol table and string table.
OS.write(SymbolTable.data(), SymbolTable.size());
write32(OS, static_cast<uint32_t>(4 + StringTable.size()));
OS.write(StringTable.data(), StringTable.size());
return OS.tell() - Start;
}
@@ -0,0 +1,92 @@
//===-- TMS34010MCObjectWriter.h - TI-COFF object writer -------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCObjectStreamer.h"
#include "llvm/MC/MCObjectWriter.h"
#include <memory>
#include <utility>
namespace llvm {
class MCAsmBackend;
class MCCodeEmitter;
class MCContext;
class MCFixup;
class MCFragment;
class MCSection;
class MCSymbol;
class MCValue;
class raw_pwrite_stream;
/// Symbols that the assembler printer marked `.globl`. GSPA 2.00 only puts a
/// defined symbol in the object symbol table when it is global; local labels
/// are reached through their section symbol plus a bit offset.
using TMS34010GlobalSymbols = DenseSet<const MCSymbol *>;
/// TI-COFF object writer: `.text`/`.data`/`.bss` sections, a symbol table with
/// section symbols and external symbols, and R-RELLONG relocations whose
/// relocated field is a bit-address addend. The conventions match GSPA 2.00:
///
/// file header: machine 0x0090, optional-header size 0, flags 0x0100
/// section: size in bits, flags 0x20/0x40/0x80, paddr/vaddr 0
/// symbol: section symbols C_STAT (3) with one aux record; globals
/// C_EXT (2) with a bit value and section number
/// relocation: 10 bytes, address in bits, type 0x11 (R-RELLONG)
class TMS34010COFFObjectWriter : public MCObjectWriter {
raw_pwrite_stream &OS;
std::shared_ptr<TMS34010GlobalSymbols> Globals;
struct PendingReloc {
const MCSection *Section;
uint64_t Offset; // byte offset within the section
const MCSymbol *Symbol;
};
SmallVector<PendingReloc, 16> Relocations;
public:
TMS34010COFFObjectWriter(raw_pwrite_stream &OS,
std::shared_ptr<TMS34010GlobalSymbols> Globals)
: OS(OS), Globals(std::move(Globals)) {}
void recordRelocation(const MCFragment &F, const MCFixup &Fixup,
MCValue Target, uint64_t &FixedValue) override;
uint64_t writeObject() override;
};
/// The object streamer for the TI-COFF writer. It tracks `.globl` so the
/// writer can distinguish global definitions from local labels, and it backs
/// the `.bss` path with the target's virtual BSS section.
class TMS34010MCStreamer : public MCObjectStreamer {
std::shared_ptr<TMS34010GlobalSymbols> Globals;
public:
TMS34010MCStreamer(MCContext &Ctx, std::unique_ptr<MCAsmBackend> TAB,
std::unique_ptr<MCObjectWriter> OW,
std::unique_ptr<MCCodeEmitter> Emitter,
std::shared_ptr<TMS34010GlobalSymbols> Globals)
: MCObjectStreamer(Ctx, std::move(TAB), std::move(OW), std::move(Emitter)),
Globals(std::move(Globals)) {}
bool emitSymbolAttribute(MCSymbol *Symbol,
MCSymbolAttr Attribute) override;
void emitCommonSymbol(MCSymbol *Symbol, uint64_t Size,
Align ByteAlignment) override;
};
} // namespace llvm
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
@@ -8,7 +8,9 @@
#include "TMS34010MCTargetDesc.h"
#include "TMS34010InstPrinter.h"
#include "TMS34010MCAsmBackend.h"
#include "TMS34010MCAsmInfo.h"
#include "TMS34010MCCodeEmitter.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
@@ -27,6 +29,92 @@ using namespace llvm;
#define GET_REGINFO_MC_DESC
#include "TMS34010GenRegisterInfo.inc"
namespace llvm {
namespace TMS34010 {
unsigned getLongBranchOpcode(unsigned Opcode) {
switch (Opcode) {
case JR: return JUncond;
case JREQ: return JAEQ;
case JRNE: return JANE;
case JRLT: return JALT;
case JRLE: return JALE;
case JRGT: return JAGT;
case JRGE: return JAGE;
case JRLO: return JALO;
case JRLS: return JALS;
case JRHI: return JAHI;
case JRHS: return JAHS;
case JRP: return JAP;
case JRN: return JAN;
case JRV: return JAV;
case JRNV: return JANV;
case JRNN: return JANN;
case MC_JR: return MC_JAUC;
case MC_JREQ: return MC_JAEQ;
case MC_JRNE: return MC_JANE;
case MC_JRLT: return MC_JALT;
case MC_JRLE: return MC_JALE;
case MC_JRGT: return MC_JAGT;
case MC_JRGE: return MC_JAGE;
case MC_JRLO: return MC_JALO;
case MC_JRLS: return MC_JALS;
case MC_JRHI: return MC_JAHI;
case MC_JRHS: return MC_JAHS;
case MC_JRP: return MC_JAP;
case MC_JRN: return MC_JAN;
case MC_JRV: return MC_JAV;
case MC_JRNV: return MC_JANV;
case MC_JRNN: return MC_JANN;
default:
return 0;
}
}
unsigned getShortBranchOpcode(unsigned Opcode) {
switch (Opcode) {
case JUncond: return JR;
case JAEQ: return JREQ;
case JANE: return JRNE;
case JALT: return JRLT;
case JALE: return JRLE;
case JAGT: return JRGT;
case JAGE: return JRGE;
case JALO: return JRLO;
case JALS: return JRLS;
case JAHI: return JRHI;
case JAHS: return JRHS;
case JAP: return JRP;
case JAN: return JRN;
case JAV: return JRV;
case JANV: return JRNV;
case JANN: return JRNN;
case MC_JAUC: return MC_JR;
case MC_JAEQ: return MC_JREQ;
case MC_JANE: return MC_JRNE;
case MC_JALT: return MC_JRLT;
case MC_JALE: return MC_JRLE;
case MC_JAGT: return MC_JRGT;
case MC_JAGE: return MC_JRGE;
case MC_JALO: return MC_JRLO;
case MC_JALS: return MC_JRLS;
case MC_JAHI: return MC_JRHI;
case MC_JAHS: return MC_JRHS;
case MC_JAP: return MC_JRP;
case MC_JAN: return MC_JRN;
case MC_JAV: return MC_JRV;
case MC_JANV: return MC_JRNV;
case MC_JANN: return MC_JRNN;
default:
return 0;
}
}
bool isShortBranch(unsigned Opcode) { return getLongBranchOpcode(Opcode) != 0; }
} // namespace TMS34010
} // namespace llvm
static MCInstrInfo *createTMS34010MCInstrInfo() {
MCInstrInfo *X = new MCInstrInfo();
InitTMS34010MCInstrInfo(X);
@@ -60,6 +148,13 @@ static MCInstPrinter *createTMS34010MCInstPrinter(const Triple &T,
return nullptr;
}
static MCAsmBackend *createTMS34010MCAsmBackend(const Target &T,
const MCSubtargetInfo &STI,
const MCRegisterInfo &MRI,
const MCTargetOptions &Options) {
return new TMS34010MCAsmBackend(T);
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010TargetMC() {
Target &T = getTheTMS34010Target();
@@ -69,10 +164,6 @@ LLVMInitializeTMS34010TargetMC() {
TargetRegistry::RegisterMCRegInfo(T, createTMS34010MCRegisterInfo);
TargetRegistry::RegisterMCSubtargetInfo(T, createTMS34010MCSubtargetInfo);
TargetRegistry::RegisterMCInstPrinter(T, createTMS34010MCInstPrinter);
TargetRegistry::RegisterMCCodeEmitter(T, createTMS34010MCCodeEmitter);
TargetRegistry::RegisterMCAsmBackend(T, createTMS34010MCAsmBackend);
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010AsmParser() {}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010Disassembler() {}
@@ -9,8 +9,34 @@
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCTARGETDESC_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCTARGETDESC_H
#include "llvm/MC/MCFixup.h"
namespace llvm {
class Target;
namespace TMS34010 {
/// Target fixup kinds. FK_JR8 covers the displacement byte of the one-word
/// relative branches: a signed word count from PC+16, i.e.
/// `disp = (target_bit - (pc_bit + 16)) / 16`. It is PC-relative in bytes;
/// the backend converts the byte offset to the ISA's word displacement.
enum FixupKind {
FK_JR8 = FirstTargetFixupKind,
LastTargetFixupKind,
};
/// True for the one-word relative JR/JR<cc> opcodes (CodeGen and MC spellings).
bool isShortBranch(unsigned Opcode);
/// The absolute JA* counterpart of a relative branch, or 0 when \p Opcode is
/// not a relative branch.
unsigned getLongBranchOpcode(unsigned Opcode);
/// The relative JR* counterpart of an absolute branch, or 0 when \p Opcode is
/// not an absolute branch.
unsigned getShortBranchOpcode(unsigned Opcode);
} // namespace TMS34010
} // namespace llvm
#define GET_REGINFO_ENUM
+18
View File
@@ -13,6 +13,7 @@
include "llvm/Target/Target.td"
include "TMS34010RegisterInfo.td"
include "TMS34010CallingConv.td"
include "TMS34010InstrInfo.td"
defm : RemapAllTargetPseudoPointerOperands<ARegs>;
@@ -26,7 +27,24 @@ def TMS34010AsmWriter : AsmWriter {
bit isMCAsmWriter = 1;
}
// The operand grammar uses * - + @ and parentheses as literal tokens in the
// MC asm strings. The default tokenizer only isolates []*!, so add the
// punctuation that follows (or precedes) an operand reference.
def TMS34010AsmParserVariant : AsmParserVariant {
int Variant = 0;
string Name = "tms34010";
string TokenizingCharacters = "[]*!()+";
}
// GSPA spells A15 "SP"; emit the alternative-register matcher so the parser
// accepts it. The canonical register name remains A15.
def TMS34010AsmParser : AsmParser {
let ShouldEmitMatchRegisterAltName = 1;
}
def TMS34010 : Target {
let InstructionSet = TMS34010InstrInfo;
let AssemblyWriters = [TMS34010AsmWriter];
let AssemblyParsers = [TMS34010AsmParser];
let AssemblyParserVariants = [TMS34010AsmParserVariant];
}
+547 -23
View File
@@ -9,17 +9,27 @@
#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/MCExpr.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 +42,101 @@ 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);
/// Object-mode counterparts: emit real MC data and fixups so the TI-COFF
/// writer can produce relocations instead of GSPA source text.
void emitDataInitializerObject(const Constant *Init);
void emitPointerInitializerObject(const Constant *Init);
void emitPointerReferenceObject(const GlobalValue *GV, int64_t Addend);
void emitByteLinesObject(ArrayRef<uint64_t> Bytes);
static char ID;
};
} // 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 +145,462 @@ 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();
// A nested array (for example game.c's `uint16_t tiles[][2]`) recurses
// element by element. Each element is a first-class initializer handled
// by one of the arms below, and array elements have no inter-element
// padding, so the flattened width is exact.
if (ElemTy->isArrayTy()) {
for (const Use &U : CA->operands())
emitDataInitializer(cast<Constant>(U.get()));
return;
}
if (ElemTy->isIntegerTy(8)) {
SmallVector<uint64_t, 32> Bytes;
for (const Use &U : CA->operands()) {
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();
}
void TMS34010AsmPrinter::emitPointerReferenceObject(const GlobalValue *GV,
int64_t Addend) {
const MCExpr *Expr = MCSymbolRefExpr::create(getSymbol(GV), OutContext);
if (Addend != 0)
Expr = MCBinaryExpr::createAdd(
Expr, MCConstantExpr::create(Addend, OutContext), OutContext);
OutStreamer->emitValue(Expr, 4);
}
void TMS34010AsmPrinter::emitPointerInitializerObject(const Constant *Init) {
if (Init->isNullValue()) {
OutStreamer->emitIntValue(0, 4);
return;
}
const Value *V = Init->stripPointerCasts();
if (const auto *GV = dyn_cast<GlobalValue>(V)) {
emitPointerReferenceObject(GV, 0);
return;
}
if (const auto *GEP = dyn_cast<GEPOperator>(V)) {
const auto *BaseGV =
dyn_cast<GlobalValue>(GEP->getPointerOperand()->stripPointerCasts());
if (!BaseGV || GEP->getNumOperands() != 2)
reportUnsupportedDataInitializer();
const auto *Idx = dyn_cast<ConstantInt>(GEP->getOperand(1));
if (!Idx)
reportUnsupportedDataInitializer();
emitPointerReferenceObject(BaseGV, Idx->getSExtValue());
return;
}
if (const auto *CE = dyn_cast<ConstantExpr>(V)) {
if (CE->getOpcode() == Instruction::IntToPtr) {
const GlobalValue *BaseGV = nullptr;
int64_t Addend = 0;
if (decomposePointerInt(CE->getOperand(0), BaseGV, Addend)) {
emitPointerReferenceObject(BaseGV, Addend);
return;
}
}
}
reportUnsupportedDataInitializer();
}
void TMS34010AsmPrinter::emitByteLinesObject(ArrayRef<uint64_t> Bytes) {
SmallString<64> Data;
for (uint64_t Byte : Bytes)
Data.push_back(static_cast<char>(Byte & 0xFF));
OutStreamer->emitBytes(Data);
}
void TMS34010AsmPrinter::emitDataInitializerObject(const Constant *Init) {
if (const auto *CI = dyn_cast<ConstantInt>(Init)) {
unsigned Bits = CI->getType()->getIntegerBitWidth();
uint64_t Value = CI->getZExtValue();
if (Bits <= 8)
OutStreamer->emitIntValue(Value & 0xFF, 1);
else if (Bits <= 16)
OutStreamer->emitIntValue(Value & 0xFFFF, 2);
else if (Bits <= 32)
OutStreamer->emitIntValue(Value & 0xFFFFFFFF, 4);
else
reportUnsupportedDataInitializer();
return;
}
if (Init->getType()->isPointerTy()) {
emitPointerInitializerObject(Init);
return;
}
if (const auto *CDA = dyn_cast<ConstantDataArray>(Init)) {
Type *ElemTy = CDA->getElementType();
if (ElemTy->isIntegerTy(8)) {
SmallVector<uint64_t, 32> Bytes;
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
Bytes.push_back(CDA->getElementAsInteger(I) & 0xFF);
emitByteLinesObject(Bytes);
return;
}
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
ElemTy->isPointerTy()) {
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
emitDataInitializerObject(CDA->getElementAsConstant(I));
return;
}
reportUnsupportedDataInitializer();
}
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
Type *ElemTy = CA->getType()->getElementType();
if (ElemTy->isArrayTy()) {
for (const Use &U : CA->operands())
emitDataInitializerObject(cast<Constant>(U.get()));
return;
}
if (ElemTy->isIntegerTy(8)) {
SmallVector<uint64_t, 32> Bytes;
for (const Use &U : CA->operands()) {
const auto *CI = dyn_cast<ConstantInt>(U.get());
if (!CI)
reportUnsupportedDataInitializer();
Bytes.push_back(CI->getZExtValue() & 0xFF);
}
emitByteLinesObject(Bytes);
return;
}
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
ElemTy->isPointerTy()) {
for (const Use &U : CA->operands())
emitDataInitializerObject(cast<Constant>(U.get()));
return;
}
reportUnsupportedDataInitializer();
}
if (isa<ConstantAggregateZero>(Init)) {
Type *Ty = Init->getType();
if (Ty->isIntegerTy()) {
unsigned Bits = Ty->getIntegerBitWidth();
if (Bits <= 8)
OutStreamer->emitIntValue(0, 1);
else if (Bits <= 16)
OutStreamer->emitIntValue(0, 2);
else if (Bits <= 32)
OutStreamer->emitIntValue(0, 4);
else
reportUnsupportedDataInitializer();
return;
}
if (Ty->isPointerTy()) {
OutStreamer->emitIntValue(0, 4);
return;
}
if (Ty->isArrayTy()) {
uint64_t Size = getDataLayout().getTypeAllocSize(Ty).getFixedValue();
OutStreamer->emitZeros(Size);
return;
}
reportUnsupportedDataInitializer();
}
reportUnsupportedDataInitializer();
}
bool TMS34010AsmPrinter::doInitialization(Module &M) {
AsmPrinter::doInitialization(M);
// 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());
const DataLayout &DL = GV->getParent()->getDataLayout();
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
if (!OutStreamer->hasRawTextSupport()) {
// Object mode: emit real bytes and fixups. The alignment must precede the
// label so the symbol value and the emitted data agree.
if (Alignment.value() >= 2)
OutStreamer->emitValueToAlignment(Alignment);
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitLabel(getSymbol(GV));
emitDataInitializerObject(Init);
// TI-COFF sections are word-granular. An odd-length byte array (for
// example a 7-byte string literal) would leave .data on an odd byte and
// the VT1200 loader rejects the section (status 4). Pad each object to a
// whole word; the padding belongs to the section, not the symbol.
if (DL.getTypeAllocSize(Init->getType()).getFixedValue() % 2)
OutStreamer->emitIntValue(0, 1);
return;
}
// GSPA implicitly aligns `.word`/`.long` to a 16-bit boundary but does not
// move the label that precedes the directive. After an odd-length object
// (for example the 5-byte "0::0" string) the label would point one byte
// before its value. Emit the alignment explicitly so the label and the
// emitted bytes agree.
if (Alignment.value() >= 2)
OutStreamer->emitRawText("\t.even\n");
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitRawText(getSymbol(GV)->getName() + ":\n");
emitDataInitializer(Init);
// Keep the object's .data a whole number of 16-bit words. An odd byte
// array would otherwise leave the section odd; GSPLNK concatenates input
// sections without realigning them, and the loader requires word sizes.
if (DL.getTypeAllocSize(Init->getType()).getFixedValue() % 2)
OutStreamer->emitRawText("\t.byte 0\n");
}
void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
// 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 SizeBytes =
DL.getTypeAllocSize(GV->getValueType()).getFixedValue();
if (!OutStreamer->hasRawTextSupport()) {
OutStreamer->switchSection(getObjFileLowering().getBSSSection());
// A TI-COFF .bss allocation is word-granular. GSPA advances `.bss`
// allocations to a 16-bit boundary (even when the object is a char or i1),
// and the VT1200 loader rejects a section whose bit size is not a whole
// number of words. Use the IR alignment but never less than two bytes,
// then pad the allocation to a word.
Align BSSAlign(std::max<unsigned>(Alignment.value(), 2));
OutStreamer->emitValueToAlignment(BSSAlign);
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitLabel(getSymbol(GV));
OutStreamer->emitZeros(SizeBytes);
OutStreamer->emitValueToAlignment(Align(2));
return;
}
uint64_t SizeBits = ((SizeBytes + 1) & ~1ULL) * 8;
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitRawText(Twine("\t.bss\t") + getSymbol(GV)->getName() + "," +
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,141 @@
// 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. FP is one push and the callee-save set
// is one MMTM register-list push; the matching epilogue is one MMFM followed
// by the FP pop. MMTM walks the list low-to-high and predecrements SP for each
// selected register, so the resulting slots are the same as one push per
// register in ascending order.
//
//===----------------------------------------------------------------------===//
#include "TMS34010FrameLowering.h"
#include "llvm/Support/ErrorHandling.h"
#include "TMS34010.h"
#include "TMS34010InstrInfo.h"
#include "TMS34010RegisterInfo.h"
#include "TMS34010Subtarget.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
using namespace llvm;
namespace {
/// Encode the callee-save set as an MMTM/MMFM register list. The two
/// directions number the A file from opposite ends: MMTM's mask has A0 in bit
/// 15 and A15 in bit 0, MMFM's mask has A0 in bit 0. The list therefore
/// remains ascending in both cases.
uint16_t getCalleeSaveMask(const MachineFrameInfo &MFI,
const TMS34010RegisterInfo &TRI, bool Restore) {
uint16_t Mask = 0;
for (const CalleeSavedInfo &CSI : MFI.getCalleeSavedInfo()) {
unsigned Enc = TRI.getEncodingValue(CSI.getReg());
assert(Enc < 16 && "the callee-save set is in the A register file");
Mask |= static_cast<uint16_t>(1u << (Restore ? Enc : 15 - Enc));
}
return Mask;
}
} // end anonymous namespace
StackOffset
TMS34010FrameLowering::getFrameIndexReference(const MachineFunction &MF, int FI,
Register &FrameReg) const {
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 sign-extending keeps a later `short` load/store
// correct even if the caller left it at 32.
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF16));
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF32));
// Save the caller's frame pointer, then the used callee-saved registers in
// one register-list push. The FP slot stays where it was: MMTM pushes the
// list below it, exactly like one push per register.
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::PUSH32)).addReg(TMS34010::A13);
const std::vector<CalleeSavedInfo> &CSI = MFI.getCalleeSavedInfo();
if (!CSI.empty()) {
const auto *TRI = STI.getRegisterInfo();
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MMTM))
.addReg(TMS34010::A15)
.addImm(getCalleeSaveMask(MFI, *TRI, /*Restore=*/false));
}
// FP = STK, then allocate the local frame.
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETFP));
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 if (MFI.hasVarSizedObjects()) {
// A no-argument function has no saved old STK to reload, and A14 points
// somewhere above the fixed frame after a dynamic alloca. Rebuild it from
// the frame pointer (FP + fixed frame size), discarding the dynamic area.
int64_t FrameBits = MFI.getStackSize() * 8;
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MOVE), TMS34010::A14)
.addReg(TMS34010::A13);
if (FrameBits)
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(FrameBits);
} else {
int64_t FrameBits = MFI.getStackSize() * 8;
if (FrameBits)
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(-FrameBits);
}
// Restore the callee-saved register list in one MMFM, then the old FP.
if (!CSI.empty()) {
const auto *TRI = STI.getRegisterInfo();
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MMFM))
.addReg(TMS34010::A15)
.addImm(getCalleeSaveMask(MFI, *TRI, /*Restore=*/true));
}
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::POP32), TMS34010::A13);
}
@@ -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);
}
+835 -49
View File
@@ -8,13 +8,24 @@
#include "TMS34010ISelLowering.h"
#include "TMS34010.h"
#include "TMS34010InstrInfo.h"
#include "TMS34010Subtarget.h"
#include "TMS34010TargetMachine.h"
#include "llvm/CodeGen/CallingConvLower.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include <iterator>
#include <optional>
#include <utility>
using namespace llvm;
@@ -23,8 +34,12 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
: TargetLowering(TM, STI) {
addRegisterClass(MVT::i32, &TMS34010::ARegsRegClass);
// The backend has no SEXT-as-field-width path that suits every width, so
// sign extension in register selects the SEXT8/SEXT16 pseudo and expands to
// a shift pair after instruction selection (see EmitInstrWithCustomInserter).
// DAGCombine forms these nodes from ordinary `short` shift pairs.
// MOVB is an 8-bit sign-extending load and an 8-bit truncating store.
// M1 only needs signed char, so a zero-extending byte load has no pattern.
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i8, Legal);
setTruncStoreAction(MVT::i32, MVT::i8, Legal);
@@ -32,6 +47,63 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i16, Legal);
setTruncStoreAction(MVT::i32, MVT::i16, Legal);
// There is no zero-extending load instruction. Lower a zero-extending
// byte/short load as the sign-extending load followed by an ANDI mask that
// clears the sign-extension bits. i1 loads occupy a byte in memory (the
// same rule as the data layout's i8 minimum) and mask to one bit. An
// anyext load may choose any high bits, so zero-extending it is valid and
// avoids an unselectable load when the DAG shares one narrow load between a
// truncating and a widening use.
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i1, Custom);
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i8, Custom);
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i16, Custom);
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i1, Custom);
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i8, Custom);
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i16, Custom);
// Dynamic allocas grow the upward-growing program stack (A14). They are
// lowered explicitly (LowerOperation) rather than by the generic expander:
// the generic path derives the direction from the frame lowering, while the
// TMS34010 frame keeps LLVM's "objects below FP" convention and moves A14
// above the fixed frame. The explicit lowering also lets STACKSAVE/
// STACKRESTORE use A14 as their pointer.
setOperationAction(ISD::DYNAMIC_STACKALLOC, MVT::i32, Custom);
setOperationAction(ISD::STACKSAVE, MVT::Other, Expand);
setOperationAction(ISD::STACKRESTORE, MVT::Other, Expand);
setStackPointerRegisterToSaveRestore(TMS34010::A14);
// Multiply and divide are single register-register instructions (MPYS/DIVS/
// DIVU) with an odd tied destination; see TMS34010InstrInfo.td. The
// remainder has no direct 32-bit form, so it is expanded from the quotient
// and a multiply in LowerOperation.
setOperationAction(ISD::MUL, MVT::i32, Legal);
setOperationAction(ISD::SDIV, MVT::i32, Legal);
setOperationAction(ISD::UDIV, MVT::i32, Legal);
setOperationAction(ISD::SREM, MVT::i32, Custom);
setOperationAction(ISD::UREM, MVT::i32, Custom);
// There is no high-half multiply. If MULHS/MULHU were legal (or custom),
// the target-independent divide/remainder-by-constant combine would turn
// `x / c` into a MULHS magic-number sequence the target cannot select. Keep
// them expanding, which also makes isOperationLegalOrCustom false so the
// combine leaves the real DIVS/DIVU in place.
setOperationAction(ISD::MULHS, MVT::i32, Expand);
setOperationAction(ISD::MULHU, MVT::i32, Expand);
setOperationAction(ISD::SMUL_LOHI, MVT::i32, Expand);
setOperationAction(ISD::UMUL_LOHI, MVT::i32, Expand);
// The backend has no jump table lowering (no BR_JT). Force switches through
// the ordinary compare/branch path even for clang's five-case event switch.
setMinimumJumpTableEntries(~0u);
// There is no conditional move or dedicated setcc result; let the generic
// legalizer turn setcc into a select_cc over 1/0 and select into a
// select_cc, then lower the compare and branch ourselves.
setOperationAction(ISD::SETCC, MVT::i32, Expand);
setOperationAction(ISD::SELECT, MVT::i32, Expand);
setOperationAction(ISD::SELECT_CC, MVT::i32, Custom);
setOperationAction(ISD::BRCOND, MVT::Other, Custom);
setOperationAction(ISD::BR_CC, MVT::i32, Custom);
computeRegisterProperties(STI.getRegisterInfo());
setBooleanContents(ZeroOrOneBooleanContent);
@@ -41,58 +113,177 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
setMaxAtomicSizeInBitsSupported(0);
}
// MOVE, MOVB and the FS0/FS1 field-size forms have no alignment requirement:
// the TMS34010 performs unaligned field accesses (TMS34010 User's Guide,
// section 7.5). Packed/bit-addressed struct fields and the bit-address
// normalizer's align-1 loads and stores must therefore select directly rather
// than being split into byte-sized accesses, which only have a sign-extending
// MOVB load here.
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
EVT VT, unsigned AddrSpace, Align Alignment,
MachineMemOperand::Flags Flags, unsigned *Fast) const {
(void)Alignment;
(void)Flags;
if (AddrSpace != 0)
return false;
if (!VT.isInteger() || VT.getSizeInBits() > 32)
return false;
if (Fast)
*Fast = 1;
return true;
}
EVT TMS34010TargetLowering::getSetCCResultType(const DataLayout &DL,
LLVMContext &Context,
EVT VT) const {
(void)DL;
// There is no vector register class; a vector value is split into i32
// scalars by the type legalizer. The default implementation asserts for
// vectors, which turns an otherwise legalizable `<N x i32>` struct copy into
// a crash. Match the element result type so vector legalization can split
// it, and keep the scalar result at i32.
if (VT.isVector())
return EVT::getVectorVT(Context, MVT::i32, VT.getVectorElementCount());
return MVT::i32;
}
bool TMS34010TargetLowering::findOptimalMemOpLowering(
LLVMContext &Context, std::vector<EVT> &MemOps, unsigned Limit,
const MemOp &Op, unsigned DstAS, unsigned SrcAS,
const AttributeList &FuncAttributes, EVT *LargestVT) const {
(void)Context;
(void)MemOps;
(void)Limit;
(void)Op;
(void)DstAS;
(void)SrcAS;
(void)FuncAttributes;
(void)LargestVT;
// The generic expansion forms byte-offset loads/stores on the pointer
// values, but TMS34010 pointers are bit addresses. TMS34010SelectionDAGInfo
// emits the bit-scaled sequence instead.
return false;
}
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
LLT VT, unsigned AddrSpace, Align Alignment,
MachineMemOperand::Flags Flags, unsigned *Fast) const {
(void)Alignment;
(void)Flags;
if (AddrSpace != 0)
return false;
if (!VT.isPointer() && !(VT.isScalar() && VT.getSizeInBits() <= 32))
return false;
if (Fast)
*Fast = 1;
return true;
}
SDValue TMS34010TargetLowering::LowerFormalArguments(
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL,
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);
// several i32 pieces and be silently accepted (and miscompiled). Integers
// narrower than i32 are promoted to one i32 stack slot; the SelectionDAG
// caller truncates the slot back to the declared width (and applies the
// signext/zeroext attribute). Only the contiguous-stack form of varargs is
// supported: there is no va_start/va_arg/va_list, and a callee that needs
// its variadic arguments takes the address of its last fixed argument and
// walks down (see m4-varargs.ll and compat.c's sprintf). That walk needs
// an anchor, so a variadic definition with no fixed argument is rejected:
// its epilogue also could not tell whether the caller saved STK.
//
// The anchor is only the incoming stack slot when SelectionDAGBuilder's
// argument-copy elision can replace the parameter's alloca with the fixed
// object. That elision requires the fixed object and the alloca to have
// the same size (SelectionDAGBuilder.cpp, tryToElideArgumentCopy). A
// narrow (i8/i16) last fixed parameter would have a one-byte alloca, so the
// elision is skipped: `&last_fixed` would be a private local, the walk
// would read the wrong slot, and no diagnostic would be emitted. Restrict
// the supported anchor to one 32-bit slot.
(void)IsVarArg;
if (F.isVarArg()) {
if (F.arg_size() == 0)
report_fatal_error(
"TMS34010 varargs requires at least one fixed argument", false);
Type *LastTy = F.getArg(F.arg_size() - 1)->getType();
if (!LastTy->isPointerTy() && !LastTy->isIntegerTy(32))
report_fatal_error(
"TMS34010 varargs requires an i32 or pointer last fixed argument",
false);
}
for (const Argument &Arg : F.args()) {
Type *Ty = Arg.getType();
if (!Ty->isIntegerTy(32) && !Ty->isIntegerTy(16) &&
!Ty->isIntegerTy(8) && !Ty->isPointerTy())
if (!Ty->isPointerTy() &&
!(Ty->isIntegerTy() && Ty->getIntegerBitWidth() <= 32))
report_fatal_error(
"M1 supports only i32, i8, i16 and pointer arguments", false);
"TMS34010 supports only integer (<=32 bit) 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. The caller pushes
// the arguments last-first, so argument 0 occupies the topmost slot directly
// below FP and argument i starts at FP minus the total size of arguments
// 0..i. A plain argument occupies one 32-bit slot; a byval argument
// occupies its object size rounded up to 16 bits (the TI rule) and its
// incoming value is the address of the copy, not a load from that address.
SmallVector<int64_t, 8> ArgOffsets(Ins.size(), 0);
int64_t Offset = 0;
for (unsigned I = 0; I != Ins.size(); ++I) {
const ISD::InputArg &In = Ins[I];
// `returned` is an optimization hint, not an ABI feature: the argument is
// still passed in its normal stack slot, and the callee already returns
// through the ABI's A8. The optimizer infers it for ordinary C such as
// `int first(int a, int b) { return a; }`, so rejecting it made valid -O2
// input uncompilable. The ABI-affecting flags stay rejected.
if (In.Flags.isSRet() || In.Flags.isInAlloca() ||
In.Flags.isPreallocated() || In.Flags.isInReg() || In.Flags.isNest())
report_fatal_error(
"M2 supports only plain arguments "
"(no sret/inalloca/preallocated/inreg/nest)",
false);
if (In.Flags.isByVal()) {
uint64_t Size = In.Flags.getByValSize();
if (!Size)
report_fatal_error("TMS34010 byval argument has zero size", false);
Offset += alignTo(Size, 2);
} else {
Offset += 4;
}
ArgOffsets[I] = -Offset;
}
for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
const ISD::InputArg &In = Ins[i];
if (In.Flags.isSRet() || In.Flags.isByVal() || In.Flags.isInReg() ||
In.Flags.isNest() || In.Flags.isReturned())
if (In.VT != MVT::i32)
report_fatal_error(
"M1 supports only plain register arguments "
"(no sret/byval/inreg/nest/returned)",
"TMS34010 supports only integer (<=32 bit) and pointer arguments",
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));
}
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()));
int64_t ArgOffset = ArgOffsets[i];
if (In.Flags.isByVal()) {
uint64_t Size = In.Flags.getByValSize();
int FI = MFI.CreateFixedObject(Size, ArgOffset, true);
InVals.push_back(DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout())));
continue;
}
int FI = MFI.CreateFixedObject(4, ArgOffset, true);
SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
SDValue Load =
DAG.getLoad(MVT::i32, DL, Chain, FIN,
MachinePointerInfo::getFixedStack(MF, FI), Align(2));
Chain = Load.getValue(1);
InVals.push_back(Load);
}
return Chain;
@@ -101,7 +292,202 @@ 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;
// A statepoint call is lowered without CALLSEQ_START/CALLSEQ_END: this
// target pushes arguments and calls directly. StatepointLowering expects
// the standard call sequence and asserts on it, so reject the feature with
// a diagnostic instead of letting the generic code crash. Stackmap and
// patchpoint calls do not reach LowerCall, so only the statepoint intrinsic
// is scanned for here. The scan is per call, but only functions with a GC
// strategy pay for it; ordinary TMS34010 code has none. A statepoint in a
// function without a GC strategy is invalid IR and fails earlier in
// upstream GC lowering (Function::getGC), before target lowering runs.
if (MF.getFunction().hasGC()) {
for (const BasicBlock &BB : MF.getFunction())
for (const Instruction &I : BB)
if (const auto *CB = dyn_cast<CallBase>(&I))
if (CB->getIntrinsicID() == Intrinsic::experimental_gc_statepoint)
report_fatal_error(
"TMS34010 does not support gc.statepoint calls", false);
}
if (CLI.CB && CLI.CB->hasOperandBundles())
report_fatal_error(
"TMS34010 does not support calls with operand bundles", false);
// A direct call names a global function. Anything else is an indirect call
// through a 32-bit bit address (a function pointer, a plain integer, or the
// inttoptr of an absolute ROM entry). External-symbol (asm label) callees
// stay outside the ABI.
const GlobalAddressSDNode *CalleeGA =
dyn_cast<GlobalAddressSDNode>(CLI.Callee);
bool IsIndirect = false;
if (CalleeGA && isa<Function>(CalleeGA->getGlobal())) {
// Direct call to a named function.
} else if (isa<ExternalSymbolSDNode>(CLI.Callee)) {
report_fatal_error("M2 supports only direct calls to global functions "
"and indirect calls through a function pointer",
false);
} else {
IsIndirect = true;
}
// Variadic calls need no special lowering: CLI.Outs already holds every
// actual argument, and the push loop below stores them last-first in the
// same contiguous program-stack slots as the fixed arguments. That is the
// only varargs form supported (no va_start/va_arg/va_list); the callee can
// take the address of its last fixed argument and step down one 32-bit slot
// per variadic argument, which is exactly what compat.c's sprintf does.
// Check the original IR types: legalization may promote an i8 or split an
// aggregate before the arguments reach Outs/OutVals. A narrow return is
// promoted to an i32 in A8; the caller's getCopyFromParts applies the
// zeroext/signext assertion and truncation.
if (!CLI.RetTy->isVoidTy() &&
!(CLI.RetTy->isIntegerTy() && CLI.RetTy->getIntegerBitWidth() <= 32) &&
!CLI.RetTy->isPointerTy())
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
false);
for (const TargetLowering::ArgListEntry &Arg : CLI.Args) {
if (Arg.IsSRet || Arg.IsInReg || Arg.IsNest || Arg.IsInAlloca ||
Arg.IsPreallocated)
report_fatal_error(
"M2 supports only plain and byval call arguments "
"(no sret/inreg/nest/inalloca/preallocated)",
false);
Type *Ty = Arg.OrigTy;
if (!Ty->isPointerTy() &&
!(Ty->isIntegerTy() && Ty->getIntegerBitWidth() <= 32))
report_fatal_error(
"TMS34010 supports only integer (<=32 bit) and pointer call "
"arguments",
false);
}
for (const ISD::OutputArg &Out : CLI.Outs)
if (Out.VT != MVT::i32)
report_fatal_error(
"TMS34010 supports only integer (<=32 bit) 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. A byval
// argument is copied into the argument area at the current top of the
// program stack and the area is advanced by its slot size. The callee gets
// the address of that copy.
for (unsigned I = CLI.Outs.size(); I != 0; --I) {
SDValue Arg = CLI.OutVals[I - 1];
const ISD::OutputArg &Out = CLI.Outs[I - 1];
if (Out.Flags.isByVal()) {
uint64_t Size = Out.Flags.getByValSize();
if (!Size)
report_fatal_error("TMS34010 byval argument has zero size", false);
uint64_t SlotSize = alignTo(Size, 2);
SDValue SP = DAG.getCopyFromReg(Chain, DL, TMS34010::A14, MVT::i32);
Chain = SP.getValue(1);
Chain = DAG.getMemcpy(
Chain, DL, SP, Arg, DAG.getConstant(Size, DL, MVT::i32), Align(2),
Align(2), /*isVolatile=*/false, /*AlwaysInline=*/true,
/*CI=*/nullptr, std::nullopt,
MachinePointerInfo::getUnknownStack(MF),
MachinePointerInfo::getUnknownStack(MF));
// ADDSTK and the frame adjustments are in bits; SlotSize is in bytes.
SDValue SlotVal =
DAG.getSignedTargetConstant(SlotSize * 8, DL, MVT::i32);
Chain = SDValue(
DAG.getMachineNode(TMS34010::ADDSTK, DL, MVT::Other, SlotVal, Chain),
0);
continue;
}
Chain = SDValue(
DAG.getMachineNode(TMS34010::PUSHARG, DL, MVT::Other, Arg, Chain), 0);
}
// A direct call references the symbol through CALLA. A TargetGlobalAddress
// operand is left for the asm printer as a relocation; a plain GlobalAddress
// would be selected to a MOVI and the call would lose its symbol.
//
// An indirect call carries the callee as a 32-bit bit address. Materialise
// or copy it in an allocatable A register *after* the argument pushes, so
// the argument setup cannot clobber the target: A13/A14/A15 are outside
// ARegs and PUSHARG only advances A14. A constant or a non-function global
// address becomes MOVI; any other value (a formal-argument load, a memory
// load, a CopyFromReg) is copied into a fresh ARegs virtual register.
SDValue Target;
unsigned CallOpc;
if (!IsIndirect) {
Target = DAG.getTargetGlobalAddress(CalleeGA->getGlobal(), DL,
getPointerTy(DAG.getDataLayout()),
CalleeGA->getOffset(),
CalleeGA->getTargetFlags());
CallOpc = TMS34010::CALLA;
} else {
if (const auto *C = dyn_cast<ConstantSDNode>(CLI.Callee)) {
// getSignedTargetConstant keeps the low 32 bits of a bit-address whose
// top bit is set; getTargetConstant would sign-extend it to 64 bits and
// assert when building the i32 APInt.
SDValue Imm =
DAG.getSignedTargetConstant(C->getSExtValue(), DL, MVT::i32);
Target =
SDValue(DAG.getMachineNode(TMS34010::MOVI, DL, MVT::i32, Imm), 0);
} else if (CalleeGA) {
SDValue Sym = DAG.getTargetGlobalAddress(
CalleeGA->getGlobal(), DL, getPointerTy(DAG.getDataLayout()),
CalleeGA->getOffset(), CalleeGA->getTargetFlags());
Target =
SDValue(DAG.getMachineNode(TMS34010::MOVI, DL, MVT::i32, Sym), 0);
} else {
Register TargetReg =
MF.getRegInfo().createVirtualRegister(&TMS34010::ARegsRegClass);
Chain = DAG.getCopyToReg(Chain, DL, TargetReg, CLI.Callee);
Target = DAG.getRegister(TargetReg, MVT::i32);
}
CallOpc = TMS34010::CALL;
}
const TargetRegisterInfo *TRI = DAG.getSubtarget().getRegisterInfo();
const uint32_t *Mask = TRI->getCallPreservedMask(MF, CLI.CallConv);
assert(Mask && "M2 requires a call-preserved register mask");
SDValue RegMask = DAG.getRegisterMask(Mask);
Chain = SDValue(
DAG.getMachineNode(CallOpc, DL, MVT::Other, Target, RegMask, Chain), 0);
// ROM/Xlib code clobbers the field-size state, so re-establish both fields
// before any further access: FS0 = 16 sign-extending 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 +495,15 @@ 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
// lower either. Reject unsupported returns loudly instead.
if (RetTy->isVoidTy() || RetTy->isIntegerTy(32) || RetTy->isPointerTy())
// sret pointer, silently changing the signature to something M2 cannot
// lower either. Reject unsupported returns loudly instead. Narrow integer
// returns are promoted to i32 in A8 and the zeroext/signext attribute is
// applied by the caller's getCopyFromParts, so i8/i16 are accepted.
if (RetTy->isVoidTy() || RetTy->isPointerTy() ||
(RetTy->isIntegerTy() && RetTy->getIntegerBitWidth() <= 32))
return true;
report_fatal_error("M1 supports only void/i32/pointer returns", false);
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
false);
}
SDValue TMS34010TargetLowering::LowerReturn(
@@ -121,18 +511,414 @@ 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. The type legalizer has promoted a
// narrow integer return to i32 and applied zeroext/signext already; if an
// i8/i16 ever reaches here, extend it explicitly according to the return
// attribute so A8 still holds the ABI-promoted value.
if (!Outs.empty()) {
if (Outs.size() != 1 || !Outs[0].VT.isInteger() ||
Outs[0].VT.getSizeInBits() > 32)
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
false);
SDValue Val = OutVals[0];
if (Val.getValueType() != MVT::i32) {
unsigned ExtOpc = Outs[0].Flags.isSExt() ? ISD::SIGN_EXTEND
: ISD::ZERO_EXTEND;
Val = DAG.getNode(ExtOpc, DL, MVT::i32, Val);
}
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A8, Val);
}
SmallVector<SDValue, 3> RetOps(1, Chain);
RetOps.push_back(DAG.getRegister(TMS34010::A0, MVT::i32));
RetOps.push_back(Glue);
return DAG.getNode(TMS34010ISD::RET_GLUE, DL, MVT::Other, RetOps);
// RETS must pop the caller's argument words: 2 when the function has
// explicit arguments, 0 otherwise. The value depends on the signature, so
// emit RETS as a machine node instead of a pattern with a fixed immediate.
unsigned N = MF.getFunction().arg_size() != 0 ? 2 : 0;
SDValue NVal = DAG.getTargetConstant(N, DL, MVT::i32);
return SDValue(
DAG.getMachineNode(TMS34010::RETS, DL, MVT::Other, NVal, Chain), 0);
}
/// Map an integer condition code onto the JAcc opcode that tests it. The
/// mapping is the dis34 condition order: signed compares use LT/GE/LE/GT,
/// unsigned compares use LO(LT), HS(GE), LS(LE) and HI(GT), and equality uses
/// EQ/NE. SETTRUE/SETFALSE have no flag test and must be lowered before this
/// point (see emitCmpAndBranch and LowerSELECT_CC).
static unsigned getBranchOpcode(ISD::CondCode CC) {
switch (CC) {
case ISD::SETEQ:
case ISD::SETUEQ:
return TMS34010::JAEQ;
case ISD::SETNE:
case ISD::SETUNE:
return TMS34010::JANE;
case ISD::SETLT:
return TMS34010::JALT;
case ISD::SETGE:
return TMS34010::JAGE;
case ISD::SETLE:
return TMS34010::JALE;
case ISD::SETGT:
return TMS34010::JAGT;
case ISD::SETULT:
return TMS34010::JALO;
case ISD::SETUGE:
return TMS34010::JAHS;
case ISD::SETULE:
return TMS34010::JALS;
case ISD::SETUGT:
return TMS34010::JAHI;
case ISD::SETTRUE:
case ISD::SETFALSE:
report_fatal_error("TMS34010: constant branch condition was not lowered",
false);
default:
report_fatal_error("TMS34010: unsupported integer comparison", false);
}
}
/// Emit a CMPBR/CMPIBR pseudo for `LHS - RHS` and the conditional branch that
/// implements `CC`, targeting \p Dest. The flags always represent
/// `LHS - RHS`, so the compare takes RHS as its source and LHS as its
/// destination. The pseudo stays a single terminator until the post-regalloc
/// pass splits it into adjacent CMP/CMPI and Jcc instructions, so no
/// flag-writing instruction can be placed between them.
static SDValue emitCmpAndBranch(SDValue Chain, SDValue LHS, SDValue RHS,
ISD::CondCode CC, SDValue Dest,
SelectionDAG &DAG, const SDLoc &DL) {
// A constant-true branch needs no compare. There is no way to name the
// fallthrough block of a constant-false BR_CC here, so fail loudly rather
// than silently dropping one edge of the CFG.
if (CC == ISD::SETTRUE)
return SDValue(
DAG.getMachineNode(TMS34010::JUncond, DL, MVT::Other, Dest, Chain), 0);
if (CC == ISD::SETFALSE)
report_fatal_error(
"TMS34010: constant-false branch condition is not supported", false);
// Keep a constant on the right so it can become the CMPI immediate.
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
std::swap(LHS, RHS);
CC = ISD::getSetCCSwappedOperands(CC);
}
unsigned BranchOpc = getBranchOpcode(CC);
SDValue Br = DAG.getTargetConstant(BranchOpc, DL, MVT::i32);
// Keep the compare and branch as one terminator until the post-regalloc
// pass. The ST flags live in a physical register and a MOVE between the
// pair would overwrite them; gluing CMP to Jcc is not enough because a PHI
// copy can be inserted between the two during register allocation or
// scheduling.
if (const auto *C = dyn_cast<ConstantSDNode>(RHS)) {
// Signed form: a 32-bit immediate with the top bit set (or a negative
// constant) must truncate to i32, not sign-extend to uint64_t.
SDValue Imm =
DAG.getSignedTargetConstant(C->getSExtValue(), DL, MVT::i32);
return SDValue(DAG.getMachineNode(TMS34010::CMPIBR, DL, MVT::Other,
{Dest, LHS, Imm, Br, Chain}),
0);
}
return SDValue(DAG.getMachineNode(TMS34010::CMPBR, DL, MVT::Other,
{Dest, LHS, RHS, Br, Chain}),
0);
}
SDValue TMS34010TargetLowering::LowerBRCOND(SDValue Op,
SelectionDAG &DAG) const {
SDValue Chain = Op.getOperand(0);
SDValue Cond = Op.getOperand(1);
SDValue Dest = Op.getOperand(2);
SDLoc DL(Op);
// An icmp feeding a branch is fused into CMP + Jcc.
if (Cond.getOpcode() == ISD::SETCC) {
ISD::CondCode CC = cast<CondCodeSDNode>(Cond.getOperand(2))->get();
return emitCmpAndBranch(Chain, Cond.getOperand(0), Cond.getOperand(1), CC,
Dest, DAG, DL);
}
// A general boolean value: compare it with zero and branch when nonzero.
SDValue Zero = DAG.getConstant(0, DL, MVT::i32);
return emitCmpAndBranch(Chain, Cond, Zero, ISD::SETNE, Dest, DAG, DL);
}
SDValue TMS34010TargetLowering::LowerBR_CC(SDValue Op,
SelectionDAG &DAG) const {
SDValue Chain = Op.getOperand(0);
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(1))->get();
SDValue LHS = Op.getOperand(2);
SDValue RHS = Op.getOperand(3);
SDValue Dest = Op.getOperand(4);
return emitCmpAndBranch(Chain, LHS, RHS, CC, Dest, DAG, SDLoc(Op));
}
SDValue TMS34010TargetLowering::LowerSELECT_CC(SDValue Op,
SelectionDAG &DAG) const {
SDValue LHS = Op.getOperand(0);
SDValue RHS = Op.getOperand(1);
SDValue TrueVal = Op.getOperand(2);
SDValue FalseVal = Op.getOperand(3);
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(4))->get();
SDLoc DL(Op);
// Constant conditions do not need a compare or a branch at all.
if (CC == ISD::SETTRUE)
return TrueVal;
if (CC == ISD::SETFALSE)
return FalseVal;
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
std::swap(LHS, RHS);
CC = ISD::getSetCCSwappedOperands(CC);
}
// Target constants stay immediate operands of the pseudo instead of becoming
// separate MOVI instructions ahead of the compare. The custom inserter then
// materialises each value in its own block.
auto AsTargetConstant = [&](SDValue V) -> SDValue {
if (const auto *C = dyn_cast<ConstantSDNode>(V))
return DAG.getSignedTargetConstant(C->getSExtValue(), DL,
V.getValueType());
return V;
};
TrueVal = AsTargetConstant(TrueVal);
FalseVal = AsTargetConstant(FalseVal);
RHS = AsTargetConstant(RHS);
// The pseudo carries both values and the operands to compare; the custom
// inserter materialises each value in its own block and merges with a PHI.
SDValue BranchOpc =
DAG.getTargetConstant(getBranchOpcode(CC), DL, MVT::i32);
SDValue Ops[] = {TrueVal, FalseVal, LHS, RHS, BranchOpc};
return SDValue(
DAG.getMachineNode(TMS34010::SELECTCC, DL, Op.getValueType(), Ops), 0);
}
SDValue TMS34010TargetLowering::LowerOperation(SDValue Op,
SelectionDAG &DAG) const {
switch (Op.getOpcode()) {
case ISD::DYNAMIC_STACKALLOC: {
// The TMS34010 program stack grows upward: A14 points one past the fixed
// frame, so the new object starts at A14 and A14 is advanced past it. The
// returned pointer is the old (possibly alignment-adjusted) A14. A call
// saves this A14 on the system stack and restores it, so the dynamic area
// stays live across calls; the epilogue restores A14 from FP, discarding
// the whole dynamic area.
SDLoc DL(Op);
SDValue Chain = Op.getOperand(0);
SDValue Size = Op.getOperand(1);
Align Alignment = cast<ConstantSDNode>(Op.getOperand(2))->getAlignValue();
// SelectionDAGBuilder::visitAlloca gives a byte count, but A14 and every
// other TMS34010 pointer are bit addresses. Keep the alignment test in
// bytes, but convert both the size and the alignment mask to bits.
assert(Size.getValueType() == MVT::i32 &&
"dynamic alloca size is an i32 byte count");
SDValue SizeBits = DAG.getNode(ISD::SHL, DL, MVT::i32, Size,
DAG.getConstant(3, DL, MVT::i32));
assert(SizeBits.getOpcode() == ISD::SHL &&
isa<ConstantSDNode>(SizeBits.getOperand(1)) &&
cast<ConstantSDNode>(SizeBits.getOperand(1))->getZExtValue() == 3 &&
"dynamic alloca size must be scaled by 3 bits per byte");
SDValue SP = DAG.getCopyFromReg(Chain, DL, TMS34010::A14, MVT::i32);
Chain = SP.getValue(1);
SDValue Ptr = SP;
if (Alignment > Align(2)) {
uint64_t AlignBits = Alignment.value() * 8;
SDValue Bias = DAG.getConstant(AlignBits - 1, DL, MVT::i32);
SDValue Mask =
DAG.getSignedConstant(-static_cast<int64_t>(AlignBits), DL,
MVT::i32);
Ptr = DAG.getNode(ISD::ADD, DL, MVT::i32, Ptr, Bias);
Ptr = DAG.getNode(ISD::AND, DL, MVT::i32, Ptr, Mask);
}
SDValue NewSP = DAG.getNode(ISD::ADD, DL, MVT::i32, Ptr, SizeBits);
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A14, NewSP);
SDValue Ops[] = {Ptr, Chain};
return DAG.getMergeValues(Ops, DL);
}
case ISD::LOAD:
// A zero-extending (or any-extending) narrow load becomes the legal
// sign-extending load plus an ANDI with the width mask. Any extension may
// choose zero high bits, so the same masking is valid for both. Returning
// merged values keeps the load's chain in the result.
if (auto *LD = dyn_cast<LoadSDNode>(Op.getNode())) {
if (LD->getExtensionType() == ISD::ZEXTLOAD ||
LD->getExtensionType() == ISD::EXTLOAD) {
SDLoc DL(Op);
EVT MemVT = LD->getMemoryVT();
unsigned Bits = MemVT.getSizeInBits();
assert((Bits == 1 || Bits == 8 || Bits == 16) &&
"unexpected zero-extending load width");
// An i1 object occupies a byte, so read a byte rather than asking the
// DAG for a non-byte-sized memory load.
SDValue Ext =
Bits == 1
? DAG.getExtLoad(ISD::SEXTLOAD, DL, MVT::i32,
LD->getChain(), LD->getBasePtr(), MVT::i8,
LD->getMemOperand())
: DAG.getExtLoad(ISD::SEXTLOAD, DL, MVT::i32,
LD->getChain(), LD->getBasePtr(), MemVT,
LD->getMemOperand());
SDValue Mask =
DAG.getConstant(Bits == 32 ? 0xFFFFFFFFu : ((1u << Bits) - 1u), DL,
MVT::i32);
SDValue Zeroed = DAG.getNode(ISD::AND, DL, MVT::i32, Ext, Mask);
SDValue Ops[] = {Zeroed, Ext.getValue(1)};
return DAG.getMergeValues(Ops, DL);
}
}
return SDValue();
case ISD::BRCOND:
return LowerBRCOND(Op, DAG);
case ISD::BR_CC:
return LowerBR_CC(Op, DAG);
case ISD::SELECT_CC:
return LowerSELECT_CC(Op, DAG);
case ISD::SREM:
case ISD::UREM: {
// The odd-destination DIVS/DIVU leaves only the quotient. Reconstruct the
// remainder in the C semantics (which truncates the quotient toward zero):
// rem = dividend - quotient * divisor
// signed and unsigned remainder both use the corresponding divide and the
// same low-32-bit product, so one expansion covers both.
SDLoc DL(Op);
SDValue LHS = Op.getOperand(0);
SDValue RHS = Op.getOperand(1);
unsigned DivOpc = Op.getOpcode() == ISD::SREM ? ISD::SDIV : ISD::UDIV;
SDValue Q = DAG.getNode(DivOpc, DL, MVT::i32, LHS, RHS);
SDValue P = DAG.getNode(ISD::MUL, DL, MVT::i32, Q, RHS);
return DAG.getNode(ISD::SUB, DL, MVT::i32, LHS, P);
}
default:
llvm_unreachable("unexpected TMS34010 custom lowering operation");
}
}
MachineBasicBlock *TMS34010TargetLowering::EmitInstrWithCustomInserter(
MachineInstr &MI, MachineBasicBlock *MBB) const {
switch (MI.getOpcode()) {
case TMS34010::SELECTCC:
return emitSelectCC(MI, MBB);
case TMS34010::SEXT8:
case TMS34010::SEXT16: {
// dst = (src << K) >> K, arithmetic, with K = 32 - width.
MachineFunction *MF = MBB->getParent();
MachineRegisterInfo &MRI = MF->getRegInfo();
const TargetInstrInfo &TII = *MF->getSubtarget().getInstrInfo();
const TargetRegisterClass *RC = &TMS34010::ARegsRegClass;
const DebugLoc &DL = MI.getDebugLoc();
Register Dst = MI.getOperand(0).getReg();
Register Src = MI.getOperand(1).getReg();
int64_t Shift = MI.getOpcode() == TMS34010::SEXT8 ? 24 : 16;
// A machine virtual register has one definition before SSA is destroyed,
// so each step produces a fresh one.
Register T1 = MRI.createVirtualRegister(RC);
Register T2 = MRI.createVirtualRegister(RC);
BuildMI(*MBB, MI, DL, TII.get(TargetOpcode::COPY), T1).addReg(Src);
BuildMI(*MBB, MI, DL, TII.get(TMS34010::SLLK), T2)
.addReg(T1)
.addImm(Shift);
BuildMI(*MBB, MI, DL, TII.get(TMS34010::SRAK), Dst)
.addReg(T2)
.addImm(Shift);
MI.eraseFromParent();
return MBB;
}
default:
return nullptr;
}
}
MachineBasicBlock *TMS34010TargetLowering::emitSelectCC(
MachineInstr &MI, MachineBasicBlock *ThisMBB) const {
MachineFunction *MF = ThisMBB->getParent();
MachineRegisterInfo &MRI = MF->getRegInfo();
const TargetInstrInfo &TII = *MF->getSubtarget().getInstrInfo();
const TargetRegisterClass *RC = &TMS34010::ARegsRegClass;
const BasicBlock *LLVM_BB = ThisMBB->getBasicBlock();
const DebugLoc &DL = MI.getDebugLoc();
// Operands: 0 = destination, 1 = true value, 2 = false value, 3 = lhs,
// 4 = rhs, 5 = conditional branch opcode.
Register DstReg = MI.getOperand(0).getReg();
MachineOperand TrueMO = MI.getOperand(1);
MachineOperand FalseMO = MI.getOperand(2);
MachineOperand LHSMO = MI.getOperand(3);
MachineOperand RHSMO = MI.getOperand(4);
unsigned BranchOpc = MI.getOperand(5).getImm();
// Build: ThisMBB: CMPBR/CMPIBR TrueMBB
// IfFalseMBB: false value; JAUC SinkMBB
// TrueMBB: true value
// SinkMBB: PHI
// The false block is laid out directly after the branch so its value is
// materialised on the fallthrough path.
MachineFunction::iterator It = std::next(ThisMBB->getIterator());
MachineBasicBlock *IfFalseMBB = MF->CreateMachineBasicBlock(LLVM_BB);
MachineBasicBlock *TrueMBB = MF->CreateMachineBasicBlock(LLVM_BB);
MachineBasicBlock *SinkMBB = MF->CreateMachineBasicBlock(LLVM_BB);
MF->insert(It, IfFalseMBB);
MF->insert(It, TrueMBB);
MF->insert(It, SinkMBB);
SinkMBB->splice(SinkMBB->begin(), ThisMBB,
std::next(MachineBasicBlock::iterator(MI)), ThisMBB->end());
SinkMBB->transferSuccessorsAndUpdatePHIs(ThisMBB);
ThisMBB->addSuccessor(TrueMBB);
ThisMBB->addSuccessor(IfFalseMBB);
IfFalseMBB->addSuccessor(SinkMBB);
TrueMBB->addSuccessor(SinkMBB);
MachineBasicBlock::iterator InsertPt = MI;
if (RHSMO.isImm()) {
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMPIBR))
.addMBB(TrueMBB)
.add(LHSMO)
.add(RHSMO)
.addImm(BranchOpc);
} else {
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMPBR))
.addMBB(TrueMBB)
.add(LHSMO)
.add(RHSMO)
.addImm(BranchOpc);
}
Register TrueReg = MRI.createVirtualRegister(RC);
Register FalseReg = MRI.createVirtualRegister(RC);
auto EmitValue = [&](const MachineOperand &MO, MachineBasicBlock *BB,
Register Dst) {
if (MO.isReg()) {
BuildMI(*BB, BB->end(), DL, TII.get(TargetOpcode::COPY), Dst)
.addReg(MO.getReg());
} else if (MO.isImm()) {
BuildMI(*BB, BB->end(), DL, TII.get(TMS34010::MOVI), Dst)
.addImm(MO.getImm());
} else {
llvm_unreachable("unsupported select operand");
}
};
EmitValue(FalseMO, IfFalseMBB, FalseReg);
BuildMI(*IfFalseMBB, IfFalseMBB->end(), DL, TII.get(TMS34010::JUncond))
.addMBB(SinkMBB);
EmitValue(TrueMO, TrueMBB, TrueReg);
BuildMI(*SinkMBB, SinkMBB->begin(), DL, TII.get(TargetOpcode::PHI), DstReg)
.addReg(TrueReg)
.addMBB(TrueMBB)
.addReg(FalseReg)
.addMBB(IfFalseMBB);
MI.eraseFromParent();
return SinkMBB;
}
@@ -40,6 +40,46 @@ public:
const SmallVectorImpl<ISD::OutputArg> &Outs,
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
SelectionDAG &DAG) const override;
SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const override;
MachineBasicBlock *
EmitInstrWithCustomInserter(MachineInstr &MI,
MachineBasicBlock *MBB) const override;
bool shouldFoldConstantShiftPairToMask(const SDNode *N) const override {
// The TMS34010 has immediate shift instructions, so keeping a shift pair
// as SLL/SRL/SRA is cheaper than materialising a mask and ANDing.
return false;
}
bool allowsMisalignedMemoryAccesses(
EVT VT, unsigned AddrSpace = 0, Align Alignment = Align(1),
MachineMemOperand::Flags Flags = MachineMemOperand::MONone,
unsigned *Fast = nullptr) const override;
bool allowsMisalignedMemoryAccesses(
LLT VT, unsigned AddrSpace = 0, Align Alignment = Align(1),
MachineMemOperand::Flags Flags = MachineMemOperand::MONone,
unsigned *Fast = nullptr) const override;
EVT getSetCCResultType(const DataLayout &DL, LLVMContext &Context,
EVT VT) const override;
// Pointers are bit addresses; the generic memory-op expansion uses byte
// offsets. Refuse it so TMS34010SelectionDAGInfo emits the bit-scaled copy.
bool findOptimalMemOpLowering(LLVMContext &Context, std::vector<EVT> &MemOps,
unsigned Limit, const MemOp &Op, unsigned DstAS,
unsigned SrcAS,
const AttributeList &FuncAttributes,
EVT *LargestVT = nullptr) const override;
private:
SDValue LowerBRCOND(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerBR_CC(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerSELECT_CC(SDValue Op, SelectionDAG &DAG) const;
MachineBasicBlock *emitSelectCC(MachineInstr &MI,
MachineBasicBlock *MBB) const;
};
} // namespace llvm
+494 -3
View File
@@ -7,10 +7,17 @@
//===----------------------------------------------------------------------===//
#include "TMS34010InstrInfo.h"
#include "MCTargetDesc/TMS34010MCTargetDesc.h"
#include "TMS34010.h"
#include "TMS34010Subtarget.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
using namespace llvm;
@@ -20,13 +27,246 @@ using namespace llvm;
TMS34010InstrInfo::TMS34010InstrInfo(const TMS34010Subtarget &STI)
: TMS34010GenInstrInfo(STI, RI), RI() {}
/// True for every conditional branch defined in the .td file, absolute or
/// relative. Instruction selection emits the absolute form; the pre-emit
/// branch-relaxation pass may rewrite a local one to the relative form.
static bool isConditionalBranch(unsigned Opcode) {
switch (Opcode) {
case TMS34010::JAEQ:
case TMS34010::JANE:
case TMS34010::JALT:
case TMS34010::JALE:
case TMS34010::JAGT:
case TMS34010::JAGE:
case TMS34010::JALO:
case TMS34010::JALS:
case TMS34010::JAHI:
case TMS34010::JAHS:
case TMS34010::JAP:
case TMS34010::JAN:
case TMS34010::JAV:
case TMS34010::JANV:
case TMS34010::JANN:
case TMS34010::JREQ:
case TMS34010::JRNE:
case TMS34010::JRLT:
case TMS34010::JRLE:
case TMS34010::JRGT:
case TMS34010::JRGE:
case TMS34010::JRLO:
case TMS34010::JRLS:
case TMS34010::JRHI:
case TMS34010::JRHS:
case TMS34010::JRP:
case TMS34010::JRN:
case TMS34010::JRV:
case TMS34010::JRNV:
case TMS34010::JRNN:
return true;
default:
return false;
}
}
/// True for the unconditional branch in either form.
static bool isUnconditionalBranch(unsigned Opcode) {
return Opcode == TMS34010::JUncond || Opcode == TMS34010::JR;
}
/// True for the fused compare-and-branch pseudo. This instruction stays a
/// terminator until TMS34010ExpandCmpBr splits it after register allocation.
static bool isFusedConditionalBranch(unsigned Opcode) {
return Opcode == TMS34010::CMPBR || Opcode == TMS34010::CMPIBR;
}
/// Return the real conditional-branch opcode carried by a CMPBR/CMPIBR.
static unsigned getFusedBranchOpcode(const MachineInstr &MI) {
return MI.getOperand(3).getImm();
}
/// Describe a fused compare-and-branch as a conditional branch for
/// analyzeBranch. Cond[0] is the real JAcc opcode (what a normal conditional
/// branch reports); the remaining operands let insertBranch rebuild the fused
/// pseudo if a pass removes and re-inserts the branch before
/// TMS34010ExpandCmpBr runs.
static void appendFusedCond(SmallVectorImpl<MachineOperand> &Cond,
const MachineInstr &MI) {
Cond.push_back(MachineOperand::CreateImm(getFusedBranchOpcode(MI)));
Cond.push_back(MachineOperand::CreateImm(MI.getOpcode()));
Cond.push_back(MI.getOperand(1));
Cond.push_back(MI.getOperand(2));
}
bool TMS34010InstrInfo::analyzeBranch(MachineBasicBlock &MBB,
MachineBasicBlock *&TBB,
MachineBasicBlock *&FBB,
SmallVectorImpl<MachineOperand> &Cond,
bool AllowModify) const {
TBB = nullptr;
FBB = nullptr;
Cond.clear();
(void)AllowModify;
MachineBasicBlock::iterator I = MBB.getLastNonDebugInstr();
if (I == MBB.end())
return false; // Empty block: it just falls through.
// A single unconditional branch.
if (isUnconditionalBranch(I->getOpcode())) {
TBB = I->getOperand(0).getMBB();
// A conditional branch immediately before the unconditional branch is the
// two-way form: the conditional target is TBB and the unconditional target
// is FBB. A fused compare-and-branch contributes the real JAcc opcode to
// Cond plus the operands needed to rebuild the pseudo.
if (I != MBB.begin()) {
MachineBasicBlock::iterator P = std::prev(I);
while (P != MBB.begin() && P->isDebugInstr())
--P;
if (!P->isDebugInstr() && isConditionalBranch(P->getOpcode())) {
FBB = TBB;
TBB = P->getOperand(0).getMBB();
Cond.push_back(MachineOperand::CreateImm(P->getOpcode()));
} else if (!P->isDebugInstr() &&
isFusedConditionalBranch(P->getOpcode())) {
FBB = TBB;
TBB = P->getOperand(0).getMBB();
appendFusedCond(Cond, *P);
}
}
return false;
}
// A single conditional branch with the false edge as the fallthrough.
if (isConditionalBranch(I->getOpcode())) {
TBB = I->getOperand(0).getMBB();
Cond.push_back(MachineOperand::CreateImm(I->getOpcode()));
return false;
}
// A fused compare-and-branch before TMS34010ExpandCmpBr splits it.
if (isFusedConditionalBranch(I->getOpcode())) {
TBB = I->getOperand(0).getMBB();
appendFusedCond(Cond, *I);
return false;
}
// RETS and any other terminator cannot be analyzed.
return true;
}
unsigned TMS34010InstrInfo::removeBranch(MachineBasicBlock &MBB,
int *BytesRemoved) const {
if (BytesRemoved)
*BytesRemoved = 0;
MachineBasicBlock::iterator I = MBB.end();
unsigned Count = 0;
while (I != MBB.begin()) {
--I;
if (I->isDebugInstr())
continue;
if (I->getOpcode() != TMS34010::JUncond &&
I->getOpcode() != TMS34010::JR &&
!isConditionalBranch(I->getOpcode()) &&
!isFusedConditionalBranch(I->getOpcode()))
break;
if (BytesRemoved)
*BytesRemoved += getInstSizeInBytes(*I);
I->eraseFromParent();
I = MBB.end();
++Count;
}
return Count;
}
unsigned TMS34010InstrInfo::insertBranch(MachineBasicBlock &MBB,
MachineBasicBlock *TBB,
MachineBasicBlock *FBB,
ArrayRef<MachineOperand> Cond,
const DebugLoc &DL,
int *BytesAdded) const {
if (BytesAdded)
*BytesAdded = 0;
assert(TBB && "insertBranch must not be told to insert a fallthrough");
// An empty condition is an unconditional branch. A false target has no
// representation in that form, so fail loudly even in release builds instead
// of silently dropping the edge.
if (Cond.empty()) {
if (FBB)
report_fatal_error(
"TMS34010: cannot insert an unconditional branch with a false target",
false);
auto &MI = *BuildMI(&MBB, DL, get(TMS34010::JUncond)).addMBB(TBB);
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(MI);
return 1;
}
// A fused compare-and-branch is rebuilt as the same pseudo. Cond[0] is the
// real JAcc opcode; Cond[1..3] are the pseudo opcode, LHS and RHS/immediate.
if (Cond.size() == 4) {
unsigned BranchOpc = Cond[0].getImm();
unsigned PseudoOpc = Cond[1].getImm();
assert(isConditionalBranch(BranchOpc) &&
"expected a real conditional branch opcode");
assert(isFusedConditionalBranch(PseudoOpc) &&
"expected a fused compare-and-branch pseudo");
MachineOperand LHS = Cond[2];
MachineOperand RHS = Cond[3];
// The operands were captured from an instruction that has been removed;
// do not carry stale kill flags into the rebuilt pseudo.
if (LHS.isReg())
LHS.setIsKill(false);
if (RHS.isReg())
RHS.setIsKill(false);
auto &MI = *BuildMI(&MBB, DL, get(PseudoOpc))
.addMBB(TBB)
.add(LHS)
.add(RHS)
.addImm(BranchOpc);
unsigned Count = 1;
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(MI);
if (FBB) {
auto &Uncond = *BuildMI(&MBB, DL, get(TMS34010::JUncond)).addMBB(FBB);
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(Uncond);
++Count;
}
return Count;
}
// Cond[0] is the conditional branch opcode; the compare is a separate CMP
// emitted by TMS34010ExpandCmpBr immediately before it.
assert(Cond.size() == 1 && "TMS34010 branch conditions have one component");
unsigned Opcode = Cond[0].getImm();
assert(isConditionalBranch(Opcode) && "expected a conditional branch");
auto &CondMI = *BuildMI(&MBB, DL, get(Opcode)).addMBB(TBB);
unsigned Count = 1;
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(CondMI);
// A two-way branch needs an explicit unconditional jump to the false target.
if (FBB) {
auto &MI = *BuildMI(&MBB, DL, get(TMS34010::JUncond)).addMBB(FBB);
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(MI);
++Count;
}
return Count;
}
void TMS34010InstrInfo::copyPhysReg(MachineBasicBlock &MBB,
MachineBasicBlock::iterator I,
const DebugLoc &DL, Register DestReg,
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 +277,263 @@ 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;
}
namespace {
/// Split a compare-and-branch pseudo into adjacent CMP/CMPI and JAcc.
///
/// The pseudo exists because ST is a physical register and the register-to-
/// register MOVE (like almost every ALU instruction) writes it. A PHI copy
/// inserted before register allocation can therefore land between a separate
/// CMP and Jcc, and the branch would test the copy's flags. Keeping the pair
/// as one terminator through scheduling and register allocation avoids that;
/// this pass expands it after register allocation, so nothing can be inserted
/// between the two real instructions.
class TMS34010ExpandCmpBr : public MachineFunctionPass {
public:
static char ID;
TMS34010ExpandCmpBr() : MachineFunctionPass(ID) {}
bool runOnMachineFunction(MachineFunction &MF) override;
};
char TMS34010ExpandCmpBr::ID = 0;
} // end anonymous namespace
bool TMS34010ExpandCmpBr::runOnMachineFunction(MachineFunction &MF) {
const TMS34010InstrInfo &TII =
*MF.getSubtarget<TMS34010Subtarget>().getInstrInfo();
bool Changed = false;
for (MachineBasicBlock &MBB : MF) {
for (MachineInstr &MI : llvm::make_early_inc_range(MBB)) {
const unsigned Opc = MI.getOpcode();
if (Opc != TMS34010::CMPBR && Opc != TMS34010::CMPIBR)
continue;
DebugLoc DL = MI.getDebugLoc();
const MachineOperand Dest = MI.getOperand(0);
const MachineOperand LHS = MI.getOperand(1);
const MachineOperand RHS = MI.getOperand(2);
const unsigned BranchOpc = MI.getOperand(3).getImm();
if (Opc == TMS34010::CMPIBR)
BuildMI(MBB, MI, DL, TII.get(TMS34010::CMPI))
.addImm(RHS.getImm())
.add(LHS);
else
BuildMI(MBB, MI, DL, TII.get(TMS34010::CMP)).add(RHS).add(LHS);
BuildMI(MBB, MI, DL, TII.get(BranchOpc)).add(Dest);
MI.eraseFromParent();
Changed = true;
}
}
return Changed;
}
FunctionPass *llvm::createTMS34010ExpandCmpBrPass() {
return new TMS34010ExpandCmpBr();
}
namespace {
/// Rewrite in-range absolute branches to the one-word relative form.
///
/// The pass runs after prologue/epilogue insertion and block placement, when
/// instruction sizes and block order are final. It starts by assuming the
/// smallest possible layout (every local branch in the one-word form) and then
/// grows back to the absolute form every branch whose displacement does not
/// fit. Growing an instruction can only push a branch further out of range,
/// never bring one back in, so the layout converges and the final encoding is
/// self-consistent.
///
/// Zero is not an encodable displacement (the ISA reserves it for the 16-bit
/// relative form), so a branch to the instruction immediately after it keeps
/// the absolute form.
class TMS34010BranchRelaxation : public MachineFunctionPass {
public:
static char ID;
TMS34010BranchRelaxation() : MachineFunctionPass(ID) {}
bool runOnMachineFunction(MachineFunction &MF) override;
};
char TMS34010BranchRelaxation::ID = 0;
} // end anonymous namespace
unsigned TMS34010InstrInfo::getInstSizeInBytes(const MachineInstr &MI) const {
// ADDI, MOVI, CMPI, SUBI and ADDSTK are emitted in the 16-bit immediate form
// when the value fits a signed 16-bit field. The .td describes only the long
// form (the generic encoder uses MCII.get(Opcode).getSize() for it), so the
// machine-level layout must account for the 4-byte form explicitly. The
// operand indices mirror TMS34010MCCodeEmitter::encodeInstruction.
unsigned ImmOp;
switch (MI.getOpcode()) {
case TMS34010::ADDI:
ImmOp = 2;
break;
case TMS34010::ADDSTK:
case TMS34010::CMPI:
ImmOp = 0;
break;
case TMS34010::MOVI:
case TMS34010::MC_ADDI:
case TMS34010::MC_SUBI:
case TMS34010::MC_CMPI:
case TMS34010::MC_MOVI:
ImmOp = 1;
break;
default:
return MI.getDesc().getSize();
}
if (ImmOp >= MI.getNumOperands() || !MI.getOperand(ImmOp).isImm())
return MI.getDesc().getSize();
return isInt<16>(MI.getOperand(ImmOp).getImm()) ? 4 : 6;
}
bool TMS34010BranchRelaxation::runOnMachineFunction(MachineFunction &MF) {
const TMS34010InstrInfo &TII =
*MF.getSubtarget<TMS34010Subtarget>().getInstrInfo();
// A non-meta instruction with no size would make the layout meaningless.
// Keep every branch absolute instead of guessing (and instead of aborting on
// a function this pass cannot measure).
for (const MachineBasicBlock &MBB : MF)
for (const MachineInstr &MI : MBB)
if (!MI.isDebugInstr() && !MI.isMetaInstruction() &&
TII.getInstSizeInBytes(MI) == 0)
return false;
// Tentatively select the one-word form for every branch whose target is a
// block in this function. The layout below then grows every branch that
// does not fit. Starting from the smallest possible layout is what makes the
// iteration monotone: growing an instruction can only push a branch further
// out of range, never bring one back in, so the loop terminates and the
// result is self-consistent.
bool EverChanged = false;
for (MachineBasicBlock &MBB : MF)
for (MachineInstr &MI : MBB) {
if (MI.isDebugInstr() || MI.isMetaInstruction())
continue;
unsigned ShortOpc = TMS34010::getShortBranchOpcode(MI.getOpcode());
if (!ShortOpc || MI.getNumOperands() == 0 || !MI.getOperand(0).isMBB())
continue;
MI.setDesc(TII.get(ShortOpc));
EverChanged = true;
}
bool Changed = true;
while (Changed) {
Changed = false;
// Byte offset of every block from the start of the function.
DenseMap<const MachineBasicBlock *, int64_t> BlockOffset;
int64_t Offset = 0;
for (const MachineBasicBlock &MBB : MF) {
BlockOffset[&MBB] = Offset;
for (const MachineInstr &MI : MBB) {
if (MI.isDebugInstr() || MI.isMetaInstruction())
continue;
unsigned Size = TII.getInstSizeInBytes(MI);
assert(Size && "unsized instruction survived the pre-scan");
Offset += Size;
}
}
for (MachineBasicBlock &MBB : MF) {
int64_t InstrOffset = BlockOffset.lookup(&MBB);
for (MachineInstr &MI : MBB) {
if (MI.isDebugInstr() || MI.isMetaInstruction())
continue;
unsigned Size = TII.getInstSizeInBytes(MI);
assert(Size && "unsized instruction survived the pre-scan");
unsigned LongOpc = TMS34010::getLongBranchOpcode(MI.getOpcode());
if (LongOpc && MI.getNumOperands() != 0 &&
MI.getOperand(0).isMBB()) {
const MachineBasicBlock *Target = MI.getOperand(0).getMBB();
auto It = BlockOffset.find(Target);
bool Fits = false;
if (It != BlockOffset.end()) {
// One-word instruction: PC after it is InstrOffset + 2 bytes, and
// the ISA displacement counts 16-bit words from PC+16.
int64_t Delta = It->second - (InstrOffset + 2);
int64_t Disp = Delta / 2;
// Zero is the 16-bit relative form, so it is not encodable here.
Fits = Delta % 2 == 0 && Disp != 0 && Disp >= -127 && Disp <= 127;
}
if (!Fits) {
MI.setDesc(TII.get(LongOpc));
Changed = true;
EverChanged = true;
Size = TII.getInstSizeInBytes(MI);
}
}
InstrOffset += Size;
}
}
}
return EverChanged;
}
FunctionPass *llvm::createTMS34010BranchRelaxationPass() {
return new TMS34010BranchRelaxation();
}
@@ -43,8 +43,37 @@ public:
Register VReg, unsigned SubReg = 0,
MachineInstr::MIFlag Flags = MachineInstr::NoFlags)
const override;
bool expandPostRAPseudo(MachineInstr &MI) const override;
bool analyzeBranch(MachineBasicBlock &MBB, MachineBasicBlock *&TBB,
MachineBasicBlock *&FBB,
SmallVectorImpl<MachineOperand> &Cond,
bool AllowModify = false) const override;
unsigned removeBranch(MachineBasicBlock &MBB,
int *BytesRemoved = nullptr) const override;
unsigned insertBranch(MachineBasicBlock &MBB, MachineBasicBlock *TBB,
MachineBasicBlock *FBB, ArrayRef<MachineOperand> Cond,
const DebugLoc &DL,
int *BytesAdded = nullptr) const override;
unsigned getInstSizeInBytes(const MachineInstr &MI) const override;
};
/// Shrink local absolute branches to the one-word relative form when the final
/// machine layout proves the displacement fits. Runs after prologue/epilogue
/// insertion and block placement; the MC backend repeats the range check for
/// symbolic operands as a safety net.
FunctionPass *createTMS34010BranchRelaxationPass();
/// Split the compare-and-branch pseudo (CMPBR/CMPIBR) into adjacent CMP/CMPI
/// and JAcc instructions. This runs after register allocation (post-RA), when
/// the PHI copies and the pre-RA schedule are final, so no copy can land
/// between the compare and the branch and overwrite the ST flags.
FunctionPass *createTMS34010ExpandCmpBrPass();
} // namespace llvm
#endif
File diff suppressed because it is too large. Load diff
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "TMS34010MCInstLower.h"
#include "TMS34010.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineInstr.h"
@@ -44,7 +45,16 @@ MCOperand TMS34010MCInstLower::LowerSymbolOperand(const MachineOperand &MO,
}
void TMS34010MCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const {
OutMI.setOpcode(MI->getOpcode());
unsigned Opcode = MI->getOpcode();
// The frame lowering uses CodeGen MMTM/MMFM instructions; the assembler and
// encoder use the MC descriptions, whose operands and encoding are the same.
// Remap here so the printer emits the register-list spelling.
if (Opcode == TMS34010::MMTM)
Opcode = TMS34010::MC_MMTM;
else if (Opcode == TMS34010::MMFM)
Opcode = TMS34010::MC_MMFM;
OutMI.setOpcode(Opcode);
for (const MachineOperand &MO : MI->operands()) {
MCOperand MCOp;
@@ -12,6 +12,7 @@
#include "TMS34010Subtarget.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/TargetFrameLowering.h"
using namespace llvm;
@@ -22,13 +23,40 @@ TMS34010RegisterInfo::TMS34010RegisterInfo() : TMS34010GenRegisterInfo(TMS34010:
const MCPhysReg *
TMS34010RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
static const MCPhysReg Empty[] = {0};
return Empty;
// A13 is reserved by the ABI, so PEI cannot list it here; the prologue and
// epilogue save and restore it explicitly.
return CSR_TMS34010_SaveList;
}
const uint32_t *TMS34010RegisterInfo::getCallPreservedMask(
const MachineFunction &MF, CallingConv::ID CC) const {
// CC_TMS34010 is documented in TMS34010CallingConv.td and lowered directly;
// like the C convention it preserves only A0-A7 and A9-A12 across a call.
(void)MF;
(void)CC;
return CSR_TMS34010_RegMask;
}
BitVector
TMS34010RegisterInfo::getReservedRegs(const MachineFunction &MF) const {
return BitVector(getNumRegs());
BitVector Reserved(getNumRegs());
// A8 is the return-value register, A13 the frame pointer, A14 the program
// stack pointer and A15 the system stack pointer.
Reserved.set(TMS34010::A8);
Reserved.set(TMS34010::A13);
Reserved.set(TMS34010::A14);
Reserved.set(TMS34010::A15);
// The status register is defined by CMP/CMPI and read by the conditional
// branches; it must never be allocated.
Reserved.set(TMS34010::ST);
// The B file is reserved for a later task.
for (unsigned Reg = TMS34010::B0; Reg <= TMS34010::B15; ++Reg)
Reserved.set(Reg);
return Reserved;
}
const TargetRegisterClass *
@@ -39,14 +67,27 @@ TMS34010RegisterInfo::getPointerRegClass(unsigned Kind) const {
bool TMS34010RegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
int SPAdj, unsigned FIOperandNum,
RegScavenger *RS) const {
MachineFunction &MF = *II->getParent()->getParent();
MachineInstr &MI = *II;
MachineFunction &MF = *MI.getParent()->getParent();
assert(MI.getOpcode() == TMS34010::GETFI &&
"only GETFI carries a frame index in M2");
(void)SPAdj;
(void)RS;
Register FrameReg;
getFrameLowering(MF)->getFrameIndexReference(
MF, II->getOperand(FIOperandNum).getIndex(), FrameReg);
StackOffset Offset = getFrameLowering(MF)->getFrameIndexReference(
MF, MI.getOperand(FIOperandNum).getIndex(), FrameReg);
assert(Offset.getScalable() == 0 && "scalable stack offsets are unsupported");
assert(FrameReg == TMS34010::A13 && "the frame base is always A13");
// GETFI is expanded after frame elimination into MOVEFP dst ; ADDI off, dst
// with the FP-relative bit offset as an immediate.
MI.getOperand(FIOperandNum).ChangeToImmediate(Offset.getFixed());
return false;
}
Register
TMS34010RegisterInfo::getFrameRegister(const MachineFunction &MF) const {
return TMS34010::A14;
return TMS34010::A13;
}
@@ -22,6 +22,9 @@ public:
const MCPhysReg *getCalleeSavedRegs(const MachineFunction *MF) const override;
const uint32_t *getCallPreservedMask(const MachineFunction &MF,
CallingConv::ID CC) const override;
BitVector getReservedRegs(const MachineFunction &MF) const override;
const TargetRegisterClass *
@@ -13,7 +13,10 @@ class TMS34010Reg<bits<5> num, string n> : Register<n> {
let DwarfNumbers = [num];
}
// The A file holds the general-purpose registers. M1 only allocates A0-A7.
// The A file holds the general-purpose registers. A8 (return value), A13
// (frame pointer), A14 (program-stack pointer) and A15 (system-stack pointer)
// are reserved by the ABI and are deliberately left out of the allocatable
// class.
def A0 : TMS34010Reg<0, "A0">;
def A1 : TMS34010Reg<1, "A1">;
def A2 : TMS34010Reg<2, "A2">;
@@ -29,7 +32,20 @@ def A11 : TMS34010Reg<11, "A11">;
def A12 : TMS34010Reg<12, "A12">;
def A13 : TMS34010Reg<13, "A13">;
def A14 : TMS34010Reg<14, "A14">;
def A15 : TMS34010Reg<15, "A15">;
// GSPA spells register 15 of the A file "SP"; it rejects "A15". The encoder
// and the fixed SP forms use the same register number, so an AltName is enough
// for the assembler. The printer canonicalises A15 to SP in
// TMS34010InstPrinter.
def A15 : TMS34010Reg<15, "A15"> {
let AltNames = ["sp"];
}
// The status register is not a general-purpose register: CMP/CMPI define it and
// the conditional branches use it. It is deliberately outside every register
// class so it can never be allocated.
def ST : Register<"ST"> {
let Namespace = "TMS34010";
}
// The B file is reserved for a later task.
def B0 : TMS34010Reg<16, "B0">;
@@ -50,7 +66,37 @@ 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)>;
// Every register in both files. This class is used only by the MC instruction
// descriptions: the assembler must accept the B file even though the M4
// allocator and ABI do not use it. It is never allocatable, so no code path
// can select a B register from a DAG pattern.
def GPR : RegisterClass<"TMS34010", [i32], 32,
(add A0, A1, A2, A3, A4, A5, A6, A7,
A8, A9, A10, A11, A12, A13, A14, A15,
B0, B1, B2, B3, B4, B5, B6, B7,
B8, B9, B10, B11, B12, B13, B14, B15)> {
let isAllocatable = 0;
}
// MPYS/MPYU/DIVS/DIVU treat an even Rd as the first of a register pair: an
// even multiply writes Rd:Rd+1, and an even divide reads a 64-bit Rd:Rd+1
// dividend and writes the quotient to Rd and the remainder to Rd+1. The
// 32-bit forms here always want the odd (single-register) encoding, so the
// tied destination operand is restricted to the odd allocatable A registers.
def ARegsOdd : RegisterClass<"TMS34010", [i32], 32,
(add A1, A3, A5, A7, A9, A11)>;
// ARegsAll names every A register. It is used only as the operand class of
// the frame and system-stack instructions, which need to name the reserved
// FP/STK/SP registers explicitly; it is never allocated.
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,
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "TMS34010SelectionDAGInfo.h"
#include "llvm/CodeGen/SelectionDAG.h"
#define GET_SDNODE_DESC
#include "TMS34010GenSDNodeInfo.inc"
@@ -17,3 +18,122 @@ TMS34010SelectionDAGInfo::TMS34010SelectionDAGInfo()
: SelectionDAGGenTargetInfo(TMS34010GenSDNodeInfo) {}
TMS34010SelectionDAGInfo::~TMS34010SelectionDAGInfo() = default;
/// The largest known-size IR memory operation that is expanded inline. Larger
/// constants are normally already converted to loops by
/// PreISelIntrinsicLowering, whose default `getMaxMemIntrinsicInlineSizeThreshold`
/// is also 64. Keep this bound in sync with that threshold: if PreISel is
/// configured to inline larger constants, this hook would see them and (for a
/// non-AlwaysInline copy) return SDValue(), which falls into the libcall path
/// that this target cannot lower.
static constexpr uint64_t MaxInlineMemOpBytes = 64;
/// Byte address arithmetic on this target is bit address arithmetic: a
/// one-byte step is eight in the pointer value. The generic memcpy/memset
/// expansion adds byte offsets, so the target expands the known-size copies
/// itself with bit-scaled word and byte accesses.
static SDValue emitMemCopy(SelectionDAG &DAG, const SDLoc &DL, SDValue Chain,
SDValue Dst, SDValue Src, uint64_t Size,
bool IsVolatile, MachinePointerInfo DstPtrInfo,
MachinePointerInfo SrcPtrInfo) {
EVT PtrVT = Dst.getValueType();
MachineMemOperand::Flags MMOFlags =
IsVolatile ? MachineMemOperand::MOVolatile : MachineMemOperand::MONone;
uint64_t I = 0;
// 32-bit accesses are the widest legal scalar memory operation and are
// unaligned-safe on the TMS34010.
for (; I + 4 <= Size; I += 4) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue SrcAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Src, Off);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
SDValue Load = DAG.getLoad(MVT::i32, DL, Chain, SrcAddr,
SrcPtrInfo.getWithOffset(I), Align(1), MMOFlags);
Chain = DAG.getStore(Load.getValue(1), DL, Load, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
for (; I < Size; ++I) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue SrcAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Src, Off);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
SDValue Load = DAG.getLoad(MVT::i8, DL, Chain, SrcAddr,
SrcPtrInfo.getWithOffset(I), Align(1), MMOFlags);
Chain = DAG.getStore(Load.getValue(1), DL, Load, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
return Chain;
}
SDValue TMS34010SelectionDAGInfo::EmitTargetCodeForMemcpy(
SelectionDAG &DAG, const SDLoc &DL, SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align DstAlign, Align SrcAlign, bool IsVolatile,
bool AlwaysInline, MachinePointerInfo DstPtrInfo,
MachinePointerInfo SrcPtrInfo) const {
(void)DstAlign;
(void)SrcAlign;
auto *ConstantSize = dyn_cast<ConstantSDNode>(Size);
if (!ConstantSize)
return SDValue();
uint64_t Bytes = ConstantSize->getZExtValue();
if (!Bytes)
return Chain;
// A non-inline constant this large falls back to the libcall path; byval
// copies are AlwaysInline and stay exact (they must not call with A14 as
// both the destination and the argument area). The bound is coupled to
// PreISel's inline threshold; see MaxInlineMemOpBytes.
if (Bytes > MaxInlineMemOpBytes && !AlwaysInline)
return SDValue();
return emitMemCopy(DAG, DL, Chain, Dst, Src, Bytes, IsVolatile, DstPtrInfo,
SrcPtrInfo);
}
SDValue TMS34010SelectionDAGInfo::EmitTargetCodeForMemset(
SelectionDAG &DAG, const SDLoc &DL, SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align Alignment, bool IsVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo) const {
(void)Alignment;
auto *ConstantSize = dyn_cast<ConstantSDNode>(Size);
if (!ConstantSize)
return SDValue();
uint64_t Bytes = ConstantSize->getZExtValue();
if (!Bytes)
return Chain;
if (Bytes > MaxInlineMemOpBytes && !AlwaysInline)
return SDValue();
EVT PtrVT = Dst.getValueType();
MachineMemOperand::Flags MMOFlags =
IsVolatile ? MachineMemOperand::MOVolatile : MachineMemOperand::MONone;
// Replicate the byte value into a 32-bit word. Src may have been promoted
// past i8 by legalization, so mask the low byte first.
SDValue Byte = DAG.getAnyExtOrTrunc(Src, DL, MVT::i32);
Byte = DAG.getNode(ISD::AND, DL, MVT::i32, Byte,
DAG.getConstant(0xFF, DL, MVT::i32));
SDValue Shift8 = DAG.getConstant(8, DL, MVT::i32);
SDValue Shift16 = DAG.getConstant(16, DL, MVT::i32);
SDValue Shift24 = DAG.getConstant(24, DL, MVT::i32);
SDValue Word = Byte;
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift8));
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift16));
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift24));
SDValue Byte8 = DAG.getAnyExtOrTrunc(Src, DL, MVT::i8);
uint64_t I = 0;
for (; I + 4 <= Bytes; I += 4) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
Chain = DAG.getStore(Chain, DL, Word, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
for (; I < Bytes; ++I) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
Chain = DAG.getStore(Chain, DL, Byte8, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
return Chain;
}
@@ -21,6 +21,22 @@ public:
TMS34010SelectionDAGInfo();
~TMS34010SelectionDAGInfo() override;
// The TMS34010 pointer type is a *bit* address, while LLVM's generic
// memcpy/memset expansion adds byte offsets. Emitting the copies here keeps
// every offset in bits: four bytes advance the address by 32.
SDValue EmitTargetCodeForMemcpy(SelectionDAG &DAG, const SDLoc &DL,
SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align DstAlign, Align SrcAlign,
bool IsVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo,
MachinePointerInfo SrcPtrInfo) const override;
SDValue EmitTargetCodeForMemset(SelectionDAG &DAG, const SDLoc &DL,
SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align Alignment,
bool IsVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo) const override;
};
} // namespace llvm
@@ -7,18 +7,39 @@
//===----------------------------------------------------------------------===//
#include "TMS34010TargetMachine.h"
#include "MCTargetDesc/TMS34010MCAsmBackend.h"
#include "MCTargetDesc/TMS34010MCObjectWriter.h"
#include "TMS34010.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Mangler.h"
#include "llvm/IR/Module.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Pass.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
#include <memory>
#include <optional>
using namespace llvm;
@@ -29,6 +50,97 @@ 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());
}
// Private/local IR symbols are module-scoped, but GSPA links every translation
// unit into one flat symbol table. Two modules that both contain `.str` would
// otherwise emit the same ``__.str`` label and GSPA reports a duplicate
// definition. Append a deterministic token derived from the module identifier
// to every local symbol; public names are left exactly as the IR spells them.
//
// llc gives a file input the file name as the module identifier (or the IR's
// own source_filename) and "<stdin>" for standard input, so the token is
// deterministic for a given input and stable for lit's stdin RUN lines.
//
// GSPA 2.00 keeps only 31 identifier characters and silently truncates the
// rest. The token itself is `_` plus eight hex digits, so a long private base
// name would push the token past the limit and reintroduce the collision. Cap
// the base at 22 characters; if it is longer, fold the full name into another
// eight-hex token (13-char prefix + `_` + 8 hex = 22) so the module token is
// always the last field of a 31-character label.
//
// Only local symbols are capped. A *public* name longer than 31 characters is
// still silently truncated by GSPA; that remains a known limitation outside
// the private-label scope of this task.
static constexpr unsigned MaxGSPASymbolLength = 31;
static constexpr unsigned HexTokenLength = 9; // '_' + 8 hex digits
static unsigned hashName(StringRef S) {
// Custom 64-bit string hash (FNV-style, not canonical FNV-1a), folded to
// 32 bits. Eight uppercase hex digits stay GSPA-safe.
uint64_t Hash = 1469598103934665603ULL;
for (unsigned char C : S) {
Hash ^= C;
Hash *= 1099511628211ULL;
}
return static_cast<unsigned>(Hash ^ (Hash >> 32));
}
static void appendHexToken(SmallVectorImpl<char> &Name, unsigned Token) {
Name.push_back('_');
for (int Shift = 28; Shift >= 0; Shift -= 4)
Name.push_back(hexdigit((Token >> Shift) & 0xF));
}
static void appendModuleToken(SmallVectorImpl<char> &Name,
const GlobalValue *GV) {
const Module *M = GV->getParent();
StringRef ID = M ? M->getModuleIdentifier() : StringRef();
unsigned Token = hashName(ID);
constexpr unsigned MaxBaseLength =
MaxGSPASymbolLength - HexTokenLength;
if (Name.size() > MaxBaseLength) {
unsigned NameToken = hashName(StringRef(Name.data(), Name.size()));
constexpr unsigned Keep =
MaxBaseLength - HexTokenLength;
SmallString<64> Short;
Short.append(Name.begin(), Name.begin() + Keep);
appendHexToken(Short, NameToken);
Name.assign(Short.begin(), Short.end());
}
appendHexToken(Name, Token);
}
namespace {
class TMS34010ObjectFile : public TargetLoweringObjectFileELF {
@@ -37,6 +149,9 @@ public:
const TargetMachine &TM) const override {
SmallString<128> Name("_");
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
sanitizeGSPASymbolName(Name);
if (GV->hasLocalLinkage())
appendModuleToken(Name, GV);
return getContext().getOrCreateSymbol(Name);
}
@@ -46,6 +161,9 @@ public:
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
OutName.push_back('_');
OutName.append(Name.begin(), Name.end());
sanitizeGSPASymbolName(OutName);
if (GV->hasLocalLinkage())
appendModuleToken(OutName, GV);
}
};
@@ -57,6 +175,204 @@ static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
namespace {
/// Every register-to-register MOVE writes the status flags. PHI elimination
/// inserts a loop backedge's copy immediately before the terminator; if the
/// terminator is a conditional branch, that copy lands between the CMP and the
/// JA<cc> and the branch tests the copy's flags. Splitting critical edges
/// moves the copy onto the edge. The new blocks are appended at the end of
/// the function so the source block's fallthrough is its false successor,
/// which keeps the original branch polarity (and so its condition code).
class TMS34010SplitCriticalEdges : public FunctionPass {
public:
static char ID;
TMS34010SplitCriticalEdges() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
SmallPtrSet<BasicBlock *, 8> Before;
for (BasicBlock &BB : F)
Before.insert(&BB);
if (!SplitAllCriticalEdges(F))
return false;
SmallVector<BasicBlock *, 8> NewBlocks;
for (BasicBlock &BB : F)
if (!Before.count(&BB))
NewBlocks.push_back(&BB);
for (BasicBlock *BB : NewBlocks)
BB->moveAfter(&F.back());
return true;
}
};
char TMS34010SplitCriticalEdges::ID = 0;
/// PreISelIntrinsicLowering expands a runtime-length or larger-than-threshold
/// constant memcpy/memset/memmove into IR loops built from i8 GEPs. Those
/// GEPs carry byte offsets, but TMS34010 pointers are bit addresses (the
/// tms-clang pipeline normalizes earlier GEPs before llc). Scale only the
/// GEPs in the known expansion / memmove loop blocks; every GEP in the input
/// module is already a bit offset by contract.
class TMS34010ScaleMemExpansionGEPs : public FunctionPass {
public:
static char ID;
TMS34010ScaleMemExpansionGEPs() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
bool Changed = false;
for (BasicBlock &BB : F) {
StringRef Name = BB.getName();
// The exact LowerMemIntrinsics block names. Both the runtime ("dynamic")
// and the >threshold constant ("static") expansions must be scaled.
// memmove has its own loop/middle/residual blocks; do NOT match
// memmove_done (the continuation block keeps the user's already-bit-
// offset GEPs) or the copy_backwards/copy_forward selection blocks.
// LLVM uniquifies repeated block names with a numeric suffix, so match
// the prefixes rather than exact names: a function with two memmoves
// has e.g. memmove_bwd_main_loop7 next to memmove_bwd_main_loop.
bool IsMemMoveLoop = Name.starts_with("memmove_") &&
!Name.starts_with("memmove_done") &&
!Name.starts_with("memmove_copy_");
if (!Name.starts_with("dynamic-memcpy-expansion-") &&
!Name.starts_with("static-memcpy-expansion-") &&
!Name.starts_with("dynamic-memset-expansion-") &&
!Name.starts_with("static-memset-expansion-") && !IsMemMoveLoop)
continue;
SmallVector<GetElementPtrInst *, 4> GEPs;
for (Instruction &I : BB)
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I))
GEPs.push_back(GEP);
for (GetElementPtrInst *GEP : GEPs)
Changed |= scaleGEP(GEP);
}
return Changed;
}
private:
static bool scaleGEP(GetElementPtrInst *GEP) {
// The LowerMemIntrinsics form always indexes i8 with one index.
if (GEP->getNumOperands() != 2 ||
!GEP->getSourceElementType()->isIntegerTy(8))
return false;
IRBuilder<> B(GEP);
Value *Idx = B.CreateSExtOrTrunc(GEP->getOperand(1), B.getInt32Ty(),
"bit.idx");
Value *Base =
B.CreatePtrToInt(GEP->getPointerOperand(), B.getInt32Ty(), "bit.base");
Value *Off = B.CreateShl(Idx, 3, "bit.off");
Value *Addr = B.CreateAdd(Base, Off, "bit.addr");
Value *NewPtr = B.CreateIntToPtr(Addr, GEP->getType(), "bit.ptr");
GEP->replaceAllUsesWith(NewPtr);
GEP->eraseFromParent();
return true;
}
};
char TMS34010ScaleMemExpansionGEPs::ID = 0;
/// PreISelIntrinsicLowering expands only memmove intrinsics whose size is not a
/// compile-time constant (or is above its threshold). A small constant
/// memmove is left for SelectionDAG, where this target has no memmove hook and
/// the fallback libcall is rejected by LowerCall. Expand every remaining
/// memmove into the same overlap-safe forward/backward loops used by the
/// dynamic form; the GEP scaling pass below handles their byte offsets.
class TMS34010LowerMemMove : public FunctionPass {
public:
static char ID;
TMS34010LowerMemMove() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
const TargetTransformInfo &TTI =
getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
SmallVector<MemMoveInst *, 4> Work;
for (BasicBlock &BB : F)
for (Instruction &I : BB)
if (auto *MM = dyn_cast<MemMoveInst>(&I))
Work.push_back(MM);
bool Changed = false;
for (MemMoveInst *MM : Work) {
if (expandMemMoveAsLoop(MM, TTI)) {
MM->eraseFromParent();
Changed = true;
}
}
return Changed;
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<TargetTransformInfoWrapperPass>();
}
};
char TMS34010LowerMemMove::ID = 0;
/// Reject IR shapes that are outside the supported M5 pipeline, so the raw
/// pipeline gets an actionable diagnostic instead of a generic SelectionDAG
/// failure or an accidental reliance on an unsupported wide-multiply path.
///
/// - An i33 multiply (exactly 33 bits) is what IndVarSimplify's closed-form
/// exit-value replacement produces (n*(n+1)/2). tms-clang keeps the loop
/// with -replexitval=never; an opt run that omits it reaches llc as
/// `mul i33` and gets a diagnostic naming that driver policy.
/// - A 64-bit (or wider) multiply is the ordinary i64 path, not the i33
/// closed-form shape. It is reserved for M6, and its diagnostic must not
/// point at -replexitval=never (that policy is already applied by tms-clang
/// and does nothing for i64).
/// - A local (static) variadic definition loses its `...` to
/// DeadArgumentElimination at -O2 because nothing calls llvm.va_start. The
/// M4 contiguous-stack walk still reads those slots, so reject it with a
/// remedy. -O0 keeps the M4 varargs gate working, and external definitions
/// (sprintf) are unaffected.
class TMS34010RejectUnsupportedIR : public FunctionPass {
public:
static char ID;
explicit TMS34010RejectUnsupportedIR(CodeGenOptLevel OL)
: FunctionPass(ID), OptLevel(OL) {}
bool runOnFunction(Function &F) override {
for (BasicBlock &BB : F)
for (Instruction &I : BB)
if (auto *BO = dyn_cast<BinaryOperator>(&I))
if (BO->getOpcode() == Instruction::Mul)
if (auto *IT = dyn_cast<IntegerType>(BO->getType())) {
unsigned Width = IT->getBitWidth();
if (Width == 33)
report_fatal_error(
"TMS34010: i33 multiply (umul_lohi shape) reached code "
"generation from the raw opt pipeline; tms-clang avoids "
"this closed-form loop exit value with -replexitval=never. "
"Add that flag to opt; the closed-form path is reserved "
"for i64/M6.",
false);
if (Width > 33)
report_fatal_error(
"TMS34010: 64-bit multiply reached code generation; the "
"i64 path is reserved for M6. M5 supports 32-bit "
"multiplication only.",
false);
}
if (OptLevel != CodeGenOptLevel::None && F.isVarArg() &&
F.hasLocalLinkage() && !F.isDeclaration())
report_fatal_error(
Twine("TMS34010: local variadic definition '") + F.getName() +
"' is not accepted at a non-O0 llc level; general varargs/"
"va_list are an M6 feature. M5 supports only the contiguous-"
"stack form anchored at an i32/pointer last fixed argument; "
"compile with -O0 for that M4-compatible subset.",
false);
return false;
}
private:
CodeGenOptLevel OptLevel;
};
char TMS34010RejectUnsupportedIR::ID = 0;
class TMS34010PassConfig : public TargetPassConfig {
public:
TMS34010PassConfig(TMS34010TargetMachine &TM, PassManagerBase &PM)
@@ -68,6 +384,19 @@ public:
bool addInstSelector() override;
void addIRPasses() override {
TargetPassConfig::addIRPasses();
addPass(new TMS34010RejectUnsupportedIR(
getTMS34010TargetMachine().getOptLevel()));
// PreISelIntrinsicLowering ran before this hook and may have introduced
// byte-offset GEPs in the runtime-size mem intrinsic loops. Small constant
// memmove intrinsics are not expanded by it; expand them here so they use
// the same overlap-safe loops and the scaling pass below.
addPass(new TMS34010LowerMemMove());
addPass(new TMS34010ScaleMemExpansionGEPs());
addPass(new TMS34010SplitCriticalEdges());
}
// M1 has no stack. The fast register allocator relies on being able to
// spill and reload values; with four argument registers and a fixed A0
// return register, a `-O0` leaf function can hit its spill path even though
@@ -75,6 +404,17 @@ public:
// This is a machine-level choice only: llc still skips the IR pipeline at
// -O0, so late GEPs keep their bit offsets.
void addFastRegAlloc() override { addOptimizedRegAlloc(); }
// Branch relaxation is a pre-emit pass: it runs after prologue/epilogue
// insertion, pseudo expansion and block placement, when the final layout
// decides which local branches fit the one-word relative form.
void addPostRegAlloc() override {
addPass(createTMS34010ExpandCmpBrPass());
}
void addPreEmitPass() override {
addPass(createTMS34010BranchRelaxationPass());
}
};
} // end anonymous namespace
@@ -102,6 +442,45 @@ TMS34010TargetMachine::createPassConfig(PassManagerBase &PM) {
return new TMS34010PassConfig(*this, PM);
}
Expected<std::unique_ptr<MCStreamer>> TMS34010TargetMachine::createMCStreamer(
raw_pwrite_stream &Out, raw_pwrite_stream *DwoOut, CodeGenFileType FileType,
MCContext &Ctx) {
if (FileType != CodeGenFileType::ObjectFile)
return CodeGenTargetMachineImpl::createMCStreamer(Out, DwoOut, FileType,
Ctx);
if (DwoOut)
return make_error<StringError>(
"TMS34010 does not support split DWARF objects",
inconvertibleErrorCode());
std::unique_ptr<MCCodeEmitter> MCE(
getTarget().createMCCodeEmitter(*getMCInstrInfo(), Ctx));
if (!MCE)
return make_error<StringError>("createMCCodeEmitter failed",
inconvertibleErrorCode());
std::unique_ptr<MCAsmBackend> MAB(getTarget().createMCAsmBackend(
getMCSubtargetInfo(), getMCRegisterInfo(), Options.MCOptions));
if (!MAB)
return make_error<StringError>("createMCAsmBackend failed",
inconvertibleErrorCode());
// TI COFF is not one of LLVM's built-in object formats, so build the writer
// and the generic object streamer directly instead of going through
// MCAsmBackend::createObjectWriter's format switch. The streamer records
// `.globl` symbols in a set shared with the writer, which uses it to tell
// global definitions from local labels.
auto Globals = std::make_shared<TMS34010GlobalSymbols>();
auto *TMSMAB = static_cast<TMS34010MCAsmBackend *>(MAB.get());
std::unique_ptr<MCObjectWriter> OW =
TMSMAB->createObjectWriter(Out, Globals);
return std::make_unique<TMS34010MCStreamer>(Ctx, std::move(MAB),
std::move(OW), std::move(MCE),
std::move(Globals));
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010Target() {
RegisterTargetMachine<TMS34010TargetMachine> X(getTheTMS34010Target());
@@ -32,6 +32,10 @@ public:
TargetPassConfig *createPassConfig(PassManagerBase &PM) override;
Expected<std::unique_ptr<MCStreamer>>
createMCStreamer(raw_pwrite_stream &Out, raw_pwrite_stream *DwoOut,
CodeGenFileType FileType, MCContext &Ctx) override;
TargetLoweringObjectFile *getObjFileLowering() const override {
return TLOF.get();
}
-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
}
-10
View File
@@ -1,10 +0,0 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; M1 has no varargs ABI, so even a definition that never reads its varargs
; must be rejected.
; CHECK: M1 does not support varargs
define i32 @vararg(i32 %n, ...) {
entry:
ret i32 %n
}
@@ -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, 1, 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-NEXT: MMTM SP, A0, A1
; CHECK: CALLA _callee
; CHECK: SETF 32, 0, 1
; CHECK: MOVE *SP(128), A14, 1
; CHECK-NEXT: MMFM SP, A0, A1
; CHECK-NEXT: MOVE *SP+, A13, 1
; CHECK-NEXT: 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{{[A-Za-z0-9_]*}}:
; CHECK: .byte 041h, 042h, 043h, 044h, 045h, 046h, 047h, 048h, 049h, 04Ah, 04Bh, 04Ch, 04Dh, 04Eh, 04Fh, 050h
; CHECK: .byte 051h, 052h, 053h, 054h, 000h
; CHECK-LABEL: {{.*}}zeros16{{[A-Za-z0-9_]*}}:
; CHECK: .word 0
; CHECK: .word 0
; CHECK-LABEL: {{.*}}zeros8{{[A-Za-z0-9_]*}}:
; 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, 1, 0
; CHECK-NEXT: SETF 32, 0, 1
; CHECK: CALLA _callee
; CHECK-NEXT: SETF 16, 1, 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-NOT: _arr+4,
; CHECK: MOVI _arr{{[A-Za-z0-9_]*}}+32, A{{[0-9]+}}
; CHECK-NOT: _arr{{[A-Za-z0-9_]*}}+4,
; CHECK: RETS 0
; CHECK: .bss _arr,128,16
; CHECK: .bss _arr{{[A-Za-z0-9_]*}},128,16
+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, 1, 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,9 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; Floating-point returns are outside the M5 integer ABI.
; CHECK: M2 supports only void/integer(<=32)/pointer returns
define float @retf() {
entry:
ret float 1.0
}
@@ -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: TMS34010 supports only integer (<=32 bit) 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: TMS34010 supports only integer (<=32 bit) and pointer arguments
define i32 @i64arg(i64 %v) {
entry:
%t = trunc i64 %v to i32
@@ -0,0 +1,11 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; i64 returns stay outside the M5 ABI even though i8/i16 returns are now
; accepted; CanLowerReturn must reject the original IR type before the type
; legalizer can split it into two i32 return pieces.
; CHECK: M2 supports only void/integer(<=32)/pointer returns
define i64 @ret64() {
entry:
ret i64 7
}
@@ -0,0 +1,11 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; The inalloca convention has no TMS34010 lowering; reject the formal flag
; rather than treating the pointer as an ordinary argument.
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
define void @inalloca_arg(ptr inalloca(i32) %p) {
entry:
store i32 0, ptr %p
ret void
}
@@ -0,0 +1,11 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; The preallocated convention has no TMS34010 lowering; reject the formal flag
; rather than treating the pointer as an ordinary argument.
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
define void @preallocated_arg(ptr preallocated(i32) %p) {
entry:
store i32 0, ptr %p
ret void
}
@@ -1,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/inalloca/preallocated/inreg/nest)
define void @sret_arg(ptr sret(i32) %out) {
entry:
store i32 0, ptr %out
@@ -3,7 +3,7 @@
;
; Aggregates are legalized into scalar pieces; reject the struct argument
; rather than silently passing its fields in registers.
; CHECK: M1 supports only i32, i8, i16 and pointer arguments
; CHECK: TMS34010 supports only integer (<=32 bit) and pointer arguments
%struct.S = type { i32, i32 }
define i32 @structarg(%struct.S %s) {
@@ -4,7 +4,7 @@
; CanLowerReturn must reject an unsupported return type itself. Returning
; false would make SelectionDAG demote the aggregate to an sret pointer and
; silently change the function signature.
; CHECK: M1 supports only void/i32/pointer returns
; CHECK: M2 supports only void/integer(<=32)/pointer returns
%struct.S = type { i32, i32 }
define %struct.S @structret(i32 %a) {
@@ -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
+16
View File
@@ -0,0 +1,16 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; The self-loop keeps the unconditional branch through branch folding. A
; branch to its immediate fallthrough can be removed by branch folding/block
; simplification, so the loop back edge is the compile-only shape check here.
define void @spin() {
entry:
br label %loop
loop:
br label %loop
}
; CHECK-LABEL: _spin:
; CHECK-NOT: .LBB
; CHECK: [[L:LBB[0-9_]+]]:
; CHECK: J{{A|R}}UC [[L]]
; CHECK-NOT: .LBB
+23
View File
@@ -0,0 +1,23 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s --check-prefix=O0
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s --check-prefix=O2
define i32 @lt5(i32 %a) {
entry:
%c = icmp slt i32 %a, 5
%r = zext i1 %c to i32
ret i32 %r
}
; A small constant on the right becomes CMPI, and the compare must stay
; adjacent to the branch: nothing may write the flags in between. The two
; materialized values are pinned in their emitted order, but the surrounding
; frame/return layout is deliberately not pinned so this test anchors only
; the compare, branch, and MOVI order.
; O0-LABEL: _lt5:
; O0: CMPI 5
; O0-NEXT: J{{A|R}}LT
; O0: MOVI 0
; O0: MOVI 1
; O2-LABEL: _lt5:
; O2: CMPI 5
; O2-NEXT: J{{A|R}}LT
; O2: MOVI 1
; O2: MOVI 0
+11
View File
@@ -0,0 +1,11 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @max(i32 %a, i32 %b) {
entry:
%c = icmp sgt i32 %a, %b
%r = select i1 %c, i32 %a, i32 %b
ret i32 %r
}
; CHECK-LABEL: _max:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}GT
; CHECK: RETS 2
+19
View File
@@ -0,0 +1,19 @@
; RUN: llc -march=tms34010 -O0 -verify-machineinstrs -filetype=asm < %s | FileCheck %s
define i32 @sumto(i32 %n) {
entry:
br label %loop
loop:
%i = phi i32 [ 1, %entry ], [ %i.next, %loop ]
%s = phi i32 [ 0, %entry ], [ %s.next, %loop ]
%s.next = add i32 %s, %i
%i.next = add i32 %i, 1
%c = icmp sle i32 %i, %n
br i1 %c, label %loop, label %exit
exit:
ret i32 %s
}
; CHECK-LABEL: _sumto:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LE
; CHECK: J{{A|R}}UC
; CHECK: RETS 2
+48
View File
@@ -0,0 +1,48 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @ops(i32 %a, i32 %b) {
entry:
%s = sub i32 %a, %b
%an = and i32 %s, %a
%o = or i32 %an, %b
%x = xor i32 %o, %s
%l = shl i32 %x, 3
%r = lshr i32 %l, 2
%q = ashr i32 %r, 1
ret i32 %q
}
; `ops` folds the lshr/ashr chain to a logical shift (the sign bit is known
; zero), so a separate function keeps an arithmetic shift in the test.
define i32 @sar(i32 %a) {
entry:
%q = ashr i32 %a, 1
ret i32 %q
}
; `more` keeps the unary forms and the register shift alive at -O2: every
; partial result feeds the returned value, so none of NEG/NOT/ANDI/SLL folds
; away.
define i32 @more(i32 %a, i32 %b) {
entry:
%neg = sub i32 0, %a
%not = xor i32 %b, -1
%masked = and i32 %b, 255
%shl = shl i32 %a, %b
%r = or i32 %neg, %not
%r2 = xor i32 %r, %masked
%r3 = add i32 %r2, %shl
ret i32 %r3
}
; CHECK: SUB
; CHECK: AND
; CHECK: OR
; CHECK: XOR
; CHECK: SLL
; CHECK: SRL
; CHECK-LABEL: _sar:
; CHECK: SRA
; CHECK: RETS 2
; CHECK-LABEL: _more:
; CHECK-DAG: NEG
; CHECK-DAG: NOT
; CHECK-DAG: ANDI
; CHECK-DAG: SLL A
; CHECK: RETS 2
+13
View File
@@ -0,0 +1,13 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
define i32 @lt(i32 %a, i32 %b) {
entry:
%c = icmp slt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _lt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LT
; CHECK: MOVI 0
; CHECK: MOVI 1
; CHECK: RETS 2
@@ -0,0 +1,48 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
;
; The TMS34010 performs unaligned field accesses (Users Guide, section 7.5):
; MOVE, MOVB and the FS0/FS1 forms have no alignment requirement. Packed
; struct fields and the bit-address normalizer's align-1 accesses must select
; the native instructions directly instead of being split into byte loads,
; which only have a sign-extending MOVB form.
define i32 @load32(ptr %p) {
%v = load i32, ptr %p, align 1
ret i32 %v
}
; CHECK-LABEL: _load32:
; CHECK-NOT: MOVB
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-NOT: MOVB
; CHECK: RETS 2
define void @store32(ptr %p, i32 %v) {
store i32 %v, ptr %p, align 1
ret void
}
; CHECK-LABEL: _store32:
; CHECK-NOT: MOVB
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
; CHECK-NOT: MOVB
; CHECK: RETS 2
define ptr @loadptr(ptr %p) {
%v = load ptr, ptr %p, align 1
ret ptr %v
}
; CHECK-LABEL: _loadptr:
; CHECK-NOT: MOVB
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-NOT: MOVB
; CHECK: RETS 2
define i32 @load16sext(ptr %p) {
%v = load i16, ptr %p, align 1
%e = sext i16 %v to i32
ret i32 %e
}
; CHECK-LABEL: _load16sext:
; CHECK-NOT: MOVB
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK-NOT: MOVB
; CHECK: RETS 2
+19
View File
@@ -0,0 +1,19 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @ult(i32 %a, i32 %b) {
entry:
%c = icmp ult i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ugt(i32 %a, i32 %b) {
entry:
%c = icmp ugt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _ult:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LO
; CHECK-LABEL: _ugt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}HI
+16
View File
@@ -0,0 +1,16 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; One i16 load can be shared by a truncating and a widening use; the DAG then
; makes it an anyext load. Any high bits are valid, so lowering it as the
; sign-extending field load plus a width mask is correct and selectable.
define i32 @anyext(ptr %p, i16 %q) {
%v = load i16, ptr %p, align 2
%d = sub i16 %v, %q
%z = zext i16 %d to i32
%w = zext i16 %v to i32
%r = add i32 %z, %w
ret i32 %r
}
; CHECK-LABEL: _anyext:
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK: ANDI 65535, A{{[0-9]+}}
+41
View File
@@ -0,0 +1,41 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; M4 promotes an i8/i16 parameter to one 32-bit program-stack slot. The
; caller extends the value according to the parameter's signext/zeroext
; attribute; the callee loads the slot and the SelectionDAG caller truncates
; it back to the declared width (applying AssertZext/AssertSext).
define i32 @arg8(i8 zeroext %c) {
%z = zext i8 %c to i32
ret i32 %z
}
define i32 @arg16(i16 signext %s) {
%z = sext i16 %s to i32
ret i32 %z
}
declare void @g8(i8 zeroext)
declare void @g16(i16 signext)
define void @call8(i32 %x) {
%c = trunc i32 %x to i8
call void @g8(i8 zeroext %c)
ret void
}
define void @call16(i32 %x) {
%s = trunc i32 %x to i16
call void @g16(i16 signext %s)
ret void
}
; CHECK-LABEL: _arg8:
; CHECK: ADDI -32, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-LABEL: _arg16:
; CHECK: ADDI -32, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-LABEL: _call8:
; CHECK: ANDI 255, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
; CHECK: CALLA _g8
; CHECK-LABEL: _call16:
; CHECK: SLL 16, A{{[0-9]+}}
; CHECK: SRA 16, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
; CHECK: CALLA _g16
+94
View File
@@ -0,0 +1,94 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; M4 pins the complete signed/unsigned integer predicate map. The compare is
; glued to the conditional branch, so the CMP and Jcc must stay adjacent.
define i32 @slt(i32 %a, i32 %b) {
entry:
%c = icmp slt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @sle(i32 %a, i32 %b) {
entry:
%c = icmp sle i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @sgt(i32 %a, i32 %b) {
entry:
%c = icmp sgt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @sge(i32 %a, i32 %b) {
entry:
%c = icmp sge i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ult(i32 %a, i32 %b) {
entry:
%c = icmp ult i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ule(i32 %a, i32 %b) {
entry:
%c = icmp ule i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ugt(i32 %a, i32 %b) {
entry:
%c = icmp ugt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @uge(i32 %a, i32 %b) {
entry:
%c = icmp uge i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @eq(i32 %a, i32 %b) {
entry:
%c = icmp eq i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ne(i32 %a, i32 %b) {
entry:
%c = icmp ne i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _slt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LT
; CHECK-LABEL: _sle:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LE
; CHECK-LABEL: _sgt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}GT
; CHECK-LABEL: _sge:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}GE
; CHECK-LABEL: _ult:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LO
; CHECK-LABEL: _ule:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LS
; CHECK-LABEL: _ugt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}HI
; CHECK-LABEL: _uge:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}HS
; CHECK-LABEL: _eq:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}EQ
; CHECK-LABEL: _ne:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}NE
@@ -0,0 +1,42 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; Comparisons against a 32-bit immediate with bit 31 set must materialise the
; low 32 bits, not sign-extend to uint64_t. This was an APInt assertion in
; LowerSELECT_CC/emitCmpAndBranch (getTargetConstant with a negative
; getSExtValue); the xgfix range check is the original live case.
define i32 @ult_high_bit(i32 %a) {
entry:
%c = icmp ult i32 %a, 2147483649
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @eq_all_ones_select(i32 %a) {
entry:
%c = icmp eq i32 %a, -1
%s = select i1 %c, i32 7, i32 9
ret i32 %s
}
define i32 @xg_range(i32 %proc) {
entry:
%t = add i32 %proc, -33554432
%c = icmp ult i32 %t, -33554176
br i1 %c, label %bad, label %good
bad:
ret i32 0
good:
ret i32 1
}
; CHECK-LABEL: _ult_high_bit:
; CHECK: CMPI -2147483647
; CHECK-NEXT: J{{A|R}}LO
; CHECK-LABEL: _eq_all_ones_select:
; CHECK: CMPI -1
; CHECK-NEXT: J{{A|R}}EQ
; CHECK-LABEL: _xg_range:
; CHECK: CMPI -33554177
; CHECK-NEXT: J{{A|R}}HI
@@ -0,0 +1,10 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; A nested array initializer (termtris' uint16_t tiles[][2]) recurses element
; by element; the flattened word sequence must keep every element.
@tiles = internal global [2 x [2 x i16]] [[2 x i16] [i16 1, i16 2], [2 x i16] [i16 3, i16 4]], align 2
; CHECK: _tiles_[[HASH:[0-9A-F]+]]:
; CHECK-NEXT: .word 00001h
; CHECK-NEXT: .word 00002h
; CHECK-NEXT: .word 00003h
; CHECK-NEXT: .word 00004h
+30
View File
@@ -0,0 +1,30 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
;
; The target has no high-half multiply. If MULHS/MULHU or SMUL_LOHI/UMUL_LOHI
; were legal, the target-independent divide-by-constant combine would replace
; these with an unselectable magic-number sequence. They must stay DIVS/DIVU.
define i32 @sdiv7(i32 %x) {
%r = sdiv i32 %x, 7
ret i32 %r
}
define i32 @srem7(i32 %x) {
%r = srem i32 %x, 7
ret i32 %r
}
define i32 @udiv10(i32 %x) {
%r = udiv i32 %x, 10
ret i32 %r
}
define i32 @urem10(i32 %x) {
%r = urem i32 %x, 10
ret i32 %r
}
; CHECK-LABEL: _sdiv7:
; CHECK: DIVS
; CHECK-NOT: MPYS A{{[0-9]+}}, A{{[0-9]+}}
; CHECK-LABEL: _srem7:
; CHECK: DIVS
; CHECK-LABEL: _udiv10:
; CHECK: DIVU
; CHECK-LABEL: _urem10:
; CHECK: DIVU
+82
View File
@@ -0,0 +1,82 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; M4 function pointers. The callee is a 32-bit bit address in an A register
; and the call is the one-word register call CALL Rs (opcode 0x0920). The
; indirect sequence is the M3 direct-call sequence with CALL in place of CALLA:
; save the caller's STK, push the arguments last-first, call, re-establish
; FS0/FS1, reload the old STK and return in A8 through RETS 2.
;
; GSPA 2.00 accepts the register-call spelling "CALL A0" (not "CALL $A0"); the
; printer emits uppercase mnemonics/registers, matching this test.
define i32 @inc(i32 %x) {
%r = add i32 %x, 1
ret i32 %r
}
; The callee arrives as a stack argument, the callback ABI. It must be copied
; into an allocatable A register after the argument pushes and called through
; that register, not through CALLA.
define i32 @apply(ptr %f, i32 %x) {
%r = call i32 %f(i32 %x)
ret i32 %r
}
; CHECK-LABEL: _apply:
; CHECK: MOVE A14, -*SP, 1
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
; CHECK: CALL A{{[0-9]+}}
; CHECK-NOT: CALLA
; CHECK: SETF 32, 0, 1
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
; CHECK: RETS 2
; A function pointer loaded from memory: materialise the pointer variable's
; address, load the 32-bit bit address, then call through the register.
@fnptr = internal global ptr @inc
define i32 @call_loaded(i32 %x) {
%f = load ptr, ptr @fnptr
%r = call i32 %f(i32 %x)
ret i32 %r
}
; CHECK-LABEL: _call_loaded:
; CHECK: MOVI _fnptr{{[A-Za-z0-9_]*}}, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: CALL A{{[0-9]+}}
; CHECK: RETS 2
; The Xlib-header pattern: an absolute ROM entry cast to a function pointer.
; MOVI materialises the 32-bit bit address (0x3FD64F60) and CALL goes through
; the register. This is a lowering test only; the MAME gate never calls an
; arbitrary ROM address.
define i32 @call_abs(i32 %x) {
%r = call i32 inttoptr (i32 1071009632 to ptr)(i32 %x)
ret i32 %r
}
; CHECK-LABEL: _call_abs:
; CHECK: MOVI 1071009632, A{{[0-9]+}}
; CHECK: CALL A{{[0-9]+}}
; CHECK-NOT: CALLA
; CHECK: RETS 2
; A no-argument callback (the termtris term_reset shape): no argument push and
; no old-STK save, but the same register call and RETS path.
define i32 @call_noargs(ptr %f) {
%r = call i32 %f()
ret i32 %r
}
; CHECK-LABEL: _call_noargs:
; CHECK-NOT: MOVE A14, -*SP, 1
; CHECK: CALL A{{[0-9]+}}
; CHECK: RETS 2
; A function pointer passed as a direct argument stays a symbol: taking its
; address is a MOVI of the function, and the call to the helper is direct.
define i32 @pass_inc() {
%r = call i32 @apply(ptr @inc, i32 41)
ret i32 %r
}
; CHECK-LABEL: _pass_inc:
; CHECK: MOVI _inc, A{{[0-9]+}}
; CHECK: CALLA _apply
; CHECK: RETS 0
+22
View File
@@ -0,0 +1,22 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; clang's GlobalOpt can shrink a boolean global to i1. The object still
; occupies a byte (the data layout's i8 minimum), so an i1 load is a byte load
; masked to one bit. A store truncates to the same byte.
@g = internal global i1 false, align 2
define i32 @load_i1() {
%v = load i1, ptr @g, align 2
%z = zext i1 %v to i32
ret i32 %z
}
define void @store_i1(i32 %x) {
%t = trunc i32 %x to i1
store i1 %t, ptr @g, align 2
ret void
}
; CHECK-LABEL: _load_i1:
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: ANDI 1, A{{[0-9]+}}
; CHECK-LABEL: _store_i1:
; CHECK: ANDI 1, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
+80
View File
@@ -0,0 +1,80 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; M4 adds the register-file ALU multiply and divide instructions. MPYS and
; MPYU multiply the 32-bit Rd (the tied destination) by the multiplier field
; Rs; with an odd Rd the low 32 bits are stored back and no register pair is
; touched. The destination must be odd, so the tied operand uses the
; odd-only ARegsOdd class.
;
; DIVS/DIVU with an odd Rd divide the 32-bit Rd by Rs, leaving the quotient in
; Rd. The remainder is derived as dividend - quotient * divisor, because the
; hardware only returns the remainder with an even destination pair.
; A 32x32 low product: one MPYS to an odd destination.
define i32 @mul32(i32 %a, i32 %b) {
%r = mul i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _mul32:
; CHECK: MPYS A{{[0-9]+}}, {{A([13579]|11)$}}
; CHECK: RETS 2
; A 16x16 signed product: the operands are truncated to 16 bits and
; sign-extended with a shift pair, then multiplied with MPYS.
define i32 @mul16s(i32 %a, i32 %b) {
%as = shl i32 %a, 16
%bs = shl i32 %b, 16
%ae = ashr i32 %as, 16
%be = ashr i32 %bs, 16
%r = mul i32 %ae, %be
ret i32 %r
}
; CHECK-LABEL: _mul16s:
; CHECK: MPYS
; A 16x16 unsigned product, masked to 16 bits: MPYU.
define i32 @mul16u(i32 %a, i32 %b) {
%ae = and i32 %a, 65535
%be = and i32 %b, 65535
%r = mul i32 %ae, %be
ret i32 %r
}
; CHECK-LABEL: _mul16u:
; CHECK: MPYU
; Signed and unsigned 32/32 division with an odd destination.
define i32 @sdiv32(i32 %a, i32 %b) {
%r = sdiv i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _sdiv32:
; CHECK: DIVS A{{[0-9]+}}, {{A([13579]|11)$}}
; CHECK: RETS 2
define i32 @udiv32(i32 %a, i32 %b) {
%r = udiv i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _udiv32:
; CHECK: DIVU A{{[0-9]+}}, {{A([13579]|11)$}}
; CHECK: RETS 2
; Remainder = dividend - quotient * divisor, using the same divide.
define i32 @srem32(i32 %a, i32 %b) {
%r = srem i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _srem32:
; CHECK: DIVS
; CHECK: MPYS
; CHECK: SUB
define i32 @urem32(i32 %a, i32 %b) {
%r = urem i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _urem32:
; CHECK: DIVU
; CHECK: MPYS
; CHECK: SUB
+30
View File
@@ -0,0 +1,30 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm %s -o %t-a.s
; RUN: cp %s %t-b.ll
; RUN: llc -march=tms34010 -O0 -filetype=asm %t-b.ll -o %t-b.s
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s -o %t-stdin.s
; RUN: FileCheck %s --check-prefix=SYM < %t-a.s
; RUN: FileCheck %s --check-prefix=PUBLIC < %t-a.s
; RUN: FileCheck %s --check-prefix=SYM < %t-b.s
; RUN: FileCheck %s --check-prefix=PUBLIC < %t-b.s
; RUN: FileCheck %s --check-prefix=STDIN < %t-stdin.s
; RUN: grep -oE '__2EL_2Estr_[0-9A-F]+' %t-a.s > %t-a.sym
; RUN: grep -oE '__2EL_2Estr_[0-9A-F]+' %t-b.s > %t-b.sym
; RUN: not diff %t-a.sym %t-b.sym
; GSPA has one flat symbol table per linked program, so a private label such as
; the string literal's ``__.str`` collides when two translation units are
; concatenated. M4 appends a deterministic module token to private/local
; symbols while leaving public names unchanged. Both files below are compiled
; under different module identifiers, so their private names must differ. The
; stdin RUN line pins the stable fallback token used when llc's module
; identifier is "<stdin>".
@.str = private unnamed_addr constant [5 x i8] c"same\00", align 1
define ptr @public_string() {
ret ptr @.str
}
; STDIN: __2EL_2Estr_6750C254:
; SYM: __2EL_2Estr_{{[0-9A-F]+}}:
; PUBLIC: .globl _public_string
; PUBLIC-LABEL: _public_string:
@@ -0,0 +1,14 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; A variadic callee anchors the contiguous-stack walk at the address of its
; last fixed argument. The argument-copy elision that makes `&arg` the
; incoming slot needs the fixed object and the parameter alloca to have the
; same size; an i8/i16 anchor would remain a private local and silently read
; the wrong slot. Reject it rather than miscompile.
; CHECK: TMS34010 varargs requires an i32 or pointer last fixed argument
define i32 @vararg_narrow_i8(i32 %a, i8 %b, ...) {
entry:
%r = zext i8 %b to i32
ret i32 %r
}
@@ -0,0 +1,10 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; The i16 counterpart of m4-reject-vararg-narrow.ll.
; CHECK: TMS34010 varargs requires an i32 or pointer last fixed argument
define i32 @vararg_narrow_i16(i32 %a, i16 %b, ...) {
entry:
%r = zext i16 %b to i32
ret i32 %r
}
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more