Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9c593ddd5 | ||
|
|
466b7ec23b | ||
|
|
e0e81b722e | ||
|
|
04ec6b1419 | ||
|
|
af2e1ded7a | ||
|
|
a5f0c56bf2 | ||
|
|
f1f8ffbb61 | ||
|
|
53293c399a | ||
|
|
78cd63b234 | ||
|
|
e14fcf5682 | ||
|
|
b3e6d7389c | ||
|
|
b166d892e7 | ||
|
|
96391dc024 | ||
|
|
2604471eb6 | ||
|
|
cd074251fd | ||
|
|
bbc3b21421 | ||
|
|
eab7503d76 | ||
|
|
40f5520e3d | ||
|
|
f1450ebe7e | ||
|
|
d0d1d6e101 | ||
|
|
a34b8a3fb7 | ||
|
|
ea154fa696 | ||
|
|
df6f6fa9d6 | ||
|
|
67bd4c7dda | ||
|
|
fd41299260 | ||
|
|
de5a365596 | ||
|
|
61ee9bbc99 | ||
|
|
b08e57424a | ||
|
|
6261ac442a | ||
|
|
3c810ec0be | ||
|
|
a49c934380 | ||
|
|
4ff6e2df9d | ||
|
|
4b300a75e9 | ||
|
|
df6a6128d2 | ||
|
|
73c85350a4 | ||
|
|
1968ccd39b | ||
|
|
8d088c40df | ||
|
|
afb4ad2eef | ||
|
|
05762fceb8 | ||
|
|
bbc591b77c | ||
|
|
39512c272d | ||
|
|
d7317305b9 | ||
|
|
696832dd44 | ||
|
|
ce084040aa | ||
|
|
a9c33665ea | ||
|
|
454fb4ac15 | ||
|
|
0512ce9d6b | ||
|
|
a6954059ea | ||
|
|
11bdccf7f1 |
No files matched your search
+980
-40
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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M1 has no calls
|
||||
declare i32 @callee(i32)
|
||||
define i32 @caller(i32 %x) {
|
||||
%r = call i32 @callee(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; GSPA has no .data/.p2align directives. An initialized global used to fall
|
||||
; through to the generic ELF asm printer; it must fail loudly instead.
|
||||
; CHECK: M1 supports only zero-initialized globals; .data/string literals are M2
|
||||
@x = global i32 42
|
||||
@@ -1,19 +0,0 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; M1 has no stack. The first function needs a frame at -O0; the escaping
|
||||
; alloca below keeps the frame requirement even if the optimizer removes the
|
||||
; first one at -O2.
|
||||
; CHECK: M1 has no stack frame
|
||||
define i32 @frame(i32 %n) {
|
||||
%a = alloca i32
|
||||
store i32 %n, ptr %a
|
||||
%v = load i32, ptr %a
|
||||
ret i32 %v
|
||||
}
|
||||
|
||||
define ptr @escape(i32 %n) {
|
||||
%a = alloca i32
|
||||
store i32 %n, ptr %a
|
||||
ret ptr %a
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; FS0 selects 16-bit fields. The M1 harness must execute SETF 16,0,0
|
||||
; before calling this code (Task 4).
|
||||
define i32 @loads(ptr %p) {
|
||||
%v = load i16, ptr %p
|
||||
%r = sext i16 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define void @stores(ptr %p, i32 %v) {
|
||||
%t = trunc i32 %v to i16
|
||||
store i16 %t, ptr %p
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _loads:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK-LABEL: _stores:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
@@ -1,14 +0,0 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; -O0 still has to allocate registers for a leaf function. M1 has no stack
|
||||
; to spill to, so the target uses the greedy allocator at -O0 too. The add
|
||||
; operands arrive in A0/A1 and the result returns in A0.
|
||||
define i32 @add_b_a(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%r = add i32 %b, %a
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _add_b_a:
|
||||
; CHECK: ADD A0, A1
|
||||
; CHECK: MOVE A1, A0
|
||||
; CHECK: RETS 0
|
||||
@@ -1,12 +0,0 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; A string literal is a private constant with a non-null initializer. It
|
||||
; used to print .rodata/.asciz, which GSPA rejects; it must fail loudly.
|
||||
; CHECK: M1 supports only zero-initialized globals; .data/string literals are M2
|
||||
@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
|
||||
|
||||
define ptr @str() {
|
||||
entry:
|
||||
ret ptr @.str
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,15 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32)
|
||||
define i32 @caller(i32 %x) {
|
||||
entry:
|
||||
%r = call i32 @callee(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _caller:
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
|
||||
; CHECK: MOVE *SP+, A13, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,14 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32, i32, i32)
|
||||
define void @push3(i32 %a, i32 %b, i32 %c) {
|
||||
entry:
|
||||
%r = call i32 @callee(i32 %a, i32 %b, i32 %c)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _push3:
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,18 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32)
|
||||
define i32 @keep(ptr %p, i32 %a) {
|
||||
entry:
|
||||
%x = load i32, ptr %p
|
||||
%y = call i32 @callee(i32 %a)
|
||||
%r = add i32 %x, %y
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _keep:
|
||||
; CHECK: MOVE A13, -*SP, 1
|
||||
; CHECK-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
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; `[8 x i1] zeroinitializer` occupies one byte in the data layout, but there
|
||||
; is no 1-bit GSPA directive. Emitting a .byte or .long per element would
|
||||
; silently change the object size, so the printer must refuse it.
|
||||
@b = private constant [8 x i1] zeroinitializer
|
||||
|
||||
; CHECK: M2 .data supports only integer, pointer and byte-array initializers
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; A `[2 x i64] zeroinitializer` is 16 bytes, but .data has no 64-bit
|
||||
; directive. Emitting two .longs would silently shrink it and shift every
|
||||
; following object, so the printer must refuse it.
|
||||
@z64 = private global [2 x i64] zeroinitializer
|
||||
|
||||
; CHECK: M2 .data supports only integer, pointer and byte-array initializers
|
||||
@@ -0,0 +1,39 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
@g = global i32 42
|
||||
@c = global i8 65
|
||||
@s = global i16 4660
|
||||
@mask = global i16 -1
|
||||
@arr = global [2 x i32] [i32 11, i32 22]
|
||||
@msg = private unnamed_addr constant [5 x i8] c"0::0\00", align 1
|
||||
@long = private unnamed_addr constant [21 x i8] c"ABCDEFGHIJKLMNOPQRST\00", align 1
|
||||
@zeros16 = private constant [2 x i16] zeroinitializer
|
||||
@zeros8 = private constant [3 x i8] zeroinitializer
|
||||
define ptr @msgp() {
|
||||
entry:
|
||||
ret ptr @msg
|
||||
}
|
||||
; CHECK: .data
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _g:
|
||||
; CHECK: .long 42
|
||||
; CHECK-LABEL: _c:
|
||||
; CHECK: .byte 041h
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _s:
|
||||
; CHECK: .word 01234h
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _mask:
|
||||
; CHECK: .word 0FFFFh
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _arr:
|
||||
; CHECK: .long 11
|
||||
; CHECK: .long 22
|
||||
; CHECK: .byte 030h, 03Ah, 03Ah, 030h, 000h
|
||||
; CHECK-LABEL: {{.*}}long{{[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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File renamed without changes.
@@ -5,4 +5,4 @@ define i32 @load(ptr %p) {
|
||||
}
|
||||
; CHECK-LABEL: _load:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 0
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,14 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @locals() {
|
||||
entry:
|
||||
%x = alloca i32
|
||||
store i32 7, ptr %x
|
||||
%v = load i32, ptr %x
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK-LABEL: _locals:
|
||||
; CHECK: ADDI {{[1-9][0-9]*, A14}}
|
||||
; CHECK: MOVE A13, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 0
|
||||
@@ -0,0 +1,7 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
define void @nothing() {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _nothing:
|
||||
; CHECK: RETS 0
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
@arr = global [2 x i32] [i32 11, i32 22]
|
||||
@p = global ptr getelementptr (i8, ptr @arr, i32 32)
|
||||
@pn = global ptr inttoptr (i32 add (i32 ptrtoint (ptr @arr to i32), i32 32) to ptr)
|
||||
; CHECK: _p:
|
||||
; CHECK: .long _arr+32
|
||||
; CHECK-NOT: _arr+4
|
||||
; CHECK-LABEL: _pn:
|
||||
; CHECK: .long _arr+32
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; M1 has no sret convention. The pointer attribute must be rejected even
|
||||
; M2 has no sret convention. The pointer attribute must be rejected even
|
||||
; though its legalized type is a plain i32.
|
||||
; CHECK: M1 supports only plain register arguments
|
||||
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
|
||||
define void @sret_arg(ptr sret(i32) %out) {
|
||||
entry:
|
||||
store i32 0, ptr %out
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
;
|
||||
; Aggregates are legalized into scalar pieces; reject the struct argument
|
||||
; rather than silently passing its fields in registers.
|
||||
; CHECK: M1 supports only i32, i8, i16 and pointer arguments
|
||||
; CHECK: TMS34010 supports only integer (<=32 bit) and pointer arguments
|
||||
%struct.S = type { i32, i32 }
|
||||
|
||||
define i32 @structarg(%struct.S %s) {
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
; CanLowerReturn must reject an unsupported return type itself. Returning
|
||||
; false would make SelectionDAG demote the aggregate to an sret pointer and
|
||||
; silently change the function signature.
|
||||
; CHECK: M1 supports only void/i32/pointer returns
|
||||
; CHECK: M2 supports only void/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
|
||||
@@ -0,0 +1,15 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; `returned` is an optimizer hint, not an ABI convention: the optimizer infers
|
||||
; it for `return a`, and the argument still lives in its normal FP-relative
|
||||
; stack slot. It must not be rejected.
|
||||
define i32 @first(i32 returned %a, i32 %b) {
|
||||
entry:
|
||||
ret i32 %a
|
||||
}
|
||||
; CHECK-LABEL: _first:
|
||||
; CHECK: MOVE A13, -*SP, 1
|
||||
; CHECK: ADDI -32, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,57 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32)
|
||||
define i32 @spill13(ptr %p0, ptr %p1, ptr %p2, ptr %p3, ptr %p4, ptr %p5,
|
||||
ptr %p6, ptr %p7, ptr %p8, ptr %p9, ptr %p10, ptr %p11,
|
||||
ptr %p12, i32 %a) {
|
||||
entry:
|
||||
%x0 = load i32, ptr %p0
|
||||
%x1 = load i32, ptr %p1
|
||||
%x2 = load i32, ptr %p2
|
||||
%x3 = load i32, ptr %p3
|
||||
%x4 = load i32, ptr %p4
|
||||
%x5 = load i32, ptr %p5
|
||||
%x6 = load i32, ptr %p6
|
||||
%x7 = load i32, ptr %p7
|
||||
%x8 = load i32, ptr %p8
|
||||
%x9 = load i32, ptr %p9
|
||||
%x10 = load i32, ptr %p10
|
||||
%x11 = load i32, ptr %p11
|
||||
%x12 = load i32, ptr %p12
|
||||
%r = call i32 @callee(i32 %a)
|
||||
; Consume %r in every add built from a loaded value. Without this the adds
|
||||
; can all be scheduled above the call and only the final sum stays live,
|
||||
; which fits in registers and never exercises a spill.
|
||||
%s0 = add i32 %x0, %r
|
||||
%s1 = add i32 %x1, %r
|
||||
%s2 = add i32 %x2, %r
|
||||
%s3 = add i32 %x3, %r
|
||||
%s4 = add i32 %x4, %r
|
||||
%s5 = add i32 %x5, %r
|
||||
%s6 = add i32 %x6, %r
|
||||
%s7 = add i32 %x7, %r
|
||||
%s8 = add i32 %x8, %r
|
||||
%s9 = add i32 %x9, %r
|
||||
%s10 = add i32 %x10, %r
|
||||
%s11 = add i32 %x11, %r
|
||||
%s12 = add i32 %x12, %r
|
||||
%t0 = add i32 %s0, %s1
|
||||
%t1 = add i32 %s2, %s3
|
||||
%t2 = add i32 %s4, %s5
|
||||
%t3 = add i32 %s6, %s7
|
||||
%t4 = add i32 %s8, %s9
|
||||
%t5 = add i32 %s10, %s11
|
||||
%t6 = add i32 %t0, %t1
|
||||
%t7 = add i32 %t2, %t3
|
||||
%t8 = add i32 %t4, %t5
|
||||
%t9 = add i32 %t6, %t7
|
||||
%t10 = add i32 %t8, %s12
|
||||
%t11 = add i32 %t9, %t10
|
||||
ret i32 %t11
|
||||
}
|
||||
; CHECK-LABEL: _spill13:
|
||||
; The 14 incoming arguments alone occupy 448 frame bits (14 * 32); a prologue
|
||||
; frame larger than that proves a spill slot was allocated.
|
||||
; CHECK: ADDI {{4[5-9][0-9]|[5-9][0-9][0-9]}}, A14
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -15,7 +15,7 @@ define void @store16(ptr %p, i32 %v) {
|
||||
|
||||
; CHECK-LABEL: _store32:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 0
|
||||
; CHECK: RETS 2
|
||||
; CHECK-LABEL: _store16:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
; CHECK: RETS 0
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,11 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
|
||||
define ptr @str() {
|
||||
entry:
|
||||
ret ptr @.str
|
||||
}
|
||||
; CHECK: .data
|
||||
; CHECK: [[STR:__[A-Za-z0-9_]+]]:
|
||||
; CHECK: .byte 068h, 065h, 06Ch, 06Ch, 06Fh, 000h
|
||||
; CHECK: MOVI [[STR]], A{{[0-9]+}}
|
||||
; CHECK: RETS 0
|
||||
@@ -0,0 +1,16 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
|
||||
; The self-loop keeps the unconditional branch through branch folding. A
|
||||
; branch to its immediate fallthrough can be removed by branch folding/block
|
||||
; simplification, so the loop back edge is the compile-only shape check here.
|
||||
define void @spin() {
|
||||
entry:
|
||||
br label %loop
|
||||
loop:
|
||||
br label %loop
|
||||
}
|
||||
; CHECK-LABEL: _spin:
|
||||
; CHECK-NOT: .LBB
|
||||
; CHECK: [[L:LBB[0-9_]+]]:
|
||||
; CHECK: J{{A|R}}UC [[L]]
|
||||
; CHECK-NOT: .LBB
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,48 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @ops(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%s = sub i32 %a, %b
|
||||
%an = and i32 %s, %a
|
||||
%o = or i32 %an, %b
|
||||
%x = xor i32 %o, %s
|
||||
%l = shl i32 %x, 3
|
||||
%r = lshr i32 %l, 2
|
||||
%q = ashr i32 %r, 1
|
||||
ret i32 %q
|
||||
}
|
||||
; `ops` folds the lshr/ashr chain to a logical shift (the sign bit is known
|
||||
; zero), so a separate function keeps an arithmetic shift in the test.
|
||||
define i32 @sar(i32 %a) {
|
||||
entry:
|
||||
%q = ashr i32 %a, 1
|
||||
ret i32 %q
|
||||
}
|
||||
; `more` keeps the unary forms and the register shift alive at -O2: every
|
||||
; partial result feeds the returned value, so none of NEG/NOT/ANDI/SLL folds
|
||||
; away.
|
||||
define i32 @more(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%neg = sub i32 0, %a
|
||||
%not = xor i32 %b, -1
|
||||
%masked = and i32 %b, 255
|
||||
%shl = shl i32 %a, %b
|
||||
%r = or i32 %neg, %not
|
||||
%r2 = xor i32 %r, %masked
|
||||
%r3 = add i32 %r2, %shl
|
||||
ret i32 %r3
|
||||
}
|
||||
; CHECK: SUB
|
||||
; CHECK: AND
|
||||
; CHECK: OR
|
||||
; CHECK: XOR
|
||||
; CHECK: SLL
|
||||
; CHECK: SRL
|
||||
; CHECK-LABEL: _sar:
|
||||
; CHECK: SRA
|
||||
; CHECK: RETS 2
|
||||
; CHECK-LABEL: _more:
|
||||
; CHECK-DAG: NEG
|
||||
; CHECK-DAG: NOT
|
||||
; CHECK-DAG: ANDI
|
||||
; CHECK-DAG: SLL A
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,13 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @lt(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp slt i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _lt:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: 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
|
||||
@@ -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
|
||||
@@ -0,0 +1,16 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; One i16 load can be shared by a truncating and a widening use; the DAG then
|
||||
; makes it an anyext load. Any high bits are valid, so lowering it as the
|
||||
; sign-extending field load plus a width mask is correct and selectable.
|
||||
define i32 @anyext(ptr %p, i16 %q) {
|
||||
%v = load i16, ptr %p, align 2
|
||||
%d = sub i16 %v, %q
|
||||
%z = zext i16 %d to i32
|
||||
%w = zext i16 %v to i32
|
||||
%r = add i32 %z, %w
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _anyext:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK: ANDI 65535, A{{[0-9]+}}
|
||||
@@ -0,0 +1,41 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; M4 promotes an i8/i16 parameter to one 32-bit program-stack slot. The
|
||||
; caller extends the value according to the parameter's signext/zeroext
|
||||
; attribute; the callee loads the slot and the SelectionDAG caller truncates
|
||||
; it back to the declared width (applying AssertZext/AssertSext).
|
||||
define i32 @arg8(i8 zeroext %c) {
|
||||
%z = zext i8 %c to i32
|
||||
ret i32 %z
|
||||
}
|
||||
define i32 @arg16(i16 signext %s) {
|
||||
%z = sext i16 %s to i32
|
||||
ret i32 %z
|
||||
}
|
||||
declare void @g8(i8 zeroext)
|
||||
declare void @g16(i16 signext)
|
||||
define void @call8(i32 %x) {
|
||||
%c = trunc i32 %x to i8
|
||||
call void @g8(i8 zeroext %c)
|
||||
ret void
|
||||
}
|
||||
define void @call16(i32 %x) {
|
||||
%s = trunc i32 %x to i16
|
||||
call void @g16(i16 signext %s)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _arg8:
|
||||
; CHECK: ADDI -32, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK-LABEL: _arg16:
|
||||
; CHECK: ADDI -32, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK-LABEL: _call8:
|
||||
; CHECK: ANDI 255, A{{[0-9]+}}
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALLA _g8
|
||||
; CHECK-LABEL: _call16:
|
||||
; CHECK: SLL 16, A{{[0-9]+}}
|
||||
; CHECK: SRA 16, A{{[0-9]+}}
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALLA _g16
|
||||
@@ -0,0 +1,94 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
|
||||
; M4 pins the complete signed/unsigned integer predicate map. The compare is
|
||||
; glued to the conditional branch, so the CMP and Jcc must stay adjacent.
|
||||
define i32 @slt(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp slt i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @sle(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp sle i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @sgt(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp sgt i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @sge(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp sge i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @ult(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp ult i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @ule(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp ule i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @ugt(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp ugt i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @uge(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp uge i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @eq(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp eq i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @ne(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%c = icmp ne i32 %a, %b
|
||||
%r = zext i1 %c to i32
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _slt:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: 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
|
||||
@@ -0,0 +1,30 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; The target has no high-half multiply. If MULHS/MULHU or SMUL_LOHI/UMUL_LOHI
|
||||
; were legal, the target-independent divide-by-constant combine would replace
|
||||
; these with an unselectable magic-number sequence. They must stay DIVS/DIVU.
|
||||
define i32 @sdiv7(i32 %x) {
|
||||
%r = sdiv i32 %x, 7
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @srem7(i32 %x) {
|
||||
%r = srem i32 %x, 7
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @udiv10(i32 %x) {
|
||||
%r = udiv i32 %x, 10
|
||||
ret i32 %r
|
||||
}
|
||||
define i32 @urem10(i32 %x) {
|
||||
%r = urem i32 %x, 10
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _sdiv7:
|
||||
; CHECK: DIVS
|
||||
; CHECK-NOT: MPYS A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK-LABEL: _srem7:
|
||||
; CHECK: DIVS
|
||||
; CHECK-LABEL: _udiv10:
|
||||
; CHECK: DIVU
|
||||
; CHECK-LABEL: _urem10:
|
||||
; CHECK: DIVU
|
||||
@@ -0,0 +1,82 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
|
||||
; M4 function pointers. The callee is a 32-bit bit address in an A register
|
||||
; and the call is the one-word register call CALL Rs (opcode 0x0920). The
|
||||
; indirect sequence is the M3 direct-call sequence with CALL in place of CALLA:
|
||||
; save the caller's STK, push the arguments last-first, call, re-establish
|
||||
; FS0/FS1, reload the old STK and return in A8 through RETS 2.
|
||||
;
|
||||
; GSPA 2.00 accepts the register-call spelling "CALL A0" (not "CALL $A0"); the
|
||||
; printer emits uppercase mnemonics/registers, matching this test.
|
||||
|
||||
define i32 @inc(i32 %x) {
|
||||
%r = add i32 %x, 1
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; The callee arrives as a stack argument, the callback ABI. It must be copied
|
||||
; into an allocatable A register after the argument pushes and called through
|
||||
; that register, not through CALLA.
|
||||
define i32 @apply(ptr %f, i32 %x) {
|
||||
%r = call i32 %f(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _apply:
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALL A{{[0-9]+}}
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
|
||||
; CHECK: RETS 2
|
||||
|
||||
; A function pointer loaded from memory: materialise the pointer variable's
|
||||
; address, load the 32-bit bit address, then call through the register.
|
||||
@fnptr = internal global ptr @inc
|
||||
define i32 @call_loaded(i32 %x) {
|
||||
%f = load ptr, ptr @fnptr
|
||||
%r = call i32 %f(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _call_loaded:
|
||||
; CHECK: MOVI _fnptr{{[A-Za-z0-9_]*}}, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: CALL A{{[0-9]+}}
|
||||
; CHECK: RETS 2
|
||||
|
||||
; The Xlib-header pattern: an absolute ROM entry cast to a function pointer.
|
||||
; MOVI materialises the 32-bit bit address (0x3FD64F60) and CALL goes through
|
||||
; the register. This is a lowering test only; the MAME gate never calls an
|
||||
; arbitrary ROM address.
|
||||
define i32 @call_abs(i32 %x) {
|
||||
%r = call i32 inttoptr (i32 1071009632 to ptr)(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _call_abs:
|
||||
; CHECK: MOVI 1071009632, A{{[0-9]+}}
|
||||
; CHECK: CALL A{{[0-9]+}}
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
; A no-argument callback (the termtris term_reset shape): no argument push and
|
||||
; no old-STK save, but the same register call and RETS path.
|
||||
define i32 @call_noargs(ptr %f) {
|
||||
%r = call i32 %f()
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _call_noargs:
|
||||
; CHECK-NOT: MOVE A14, -*SP, 1
|
||||
; CHECK: CALL A{{[0-9]+}}
|
||||
; CHECK: RETS 2
|
||||
|
||||
; A function pointer passed as a direct argument stays a symbol: taking its
|
||||
; address is a MOVI of the function, and the call to the helper is direct.
|
||||
define i32 @pass_inc() {
|
||||
%r = call i32 @apply(ptr @inc, i32 41)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _pass_inc:
|
||||
; CHECK: MOVI _inc, A{{[0-9]+}}
|
||||
; CHECK: CALLA _apply
|
||||
; CHECK: RETS 0
|
||||
@@ -0,0 +1,22 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; clang's GlobalOpt can shrink a boolean global to i1. The object still
|
||||
; occupies a byte (the data layout's i8 minimum), so an i1 load is a byte load
|
||||
; masked to one bit. A store truncates to the same byte.
|
||||
@g = internal global i1 false, align 2
|
||||
define i32 @load_i1() {
|
||||
%v = load i1, ptr @g, align 2
|
||||
%z = zext i1 %v to i32
|
||||
ret i32 %z
|
||||
}
|
||||
define void @store_i1(i32 %x) {
|
||||
%t = trunc i32 %x to i1
|
||||
store i1 %t, ptr @g, align 2
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _load_i1:
|
||||
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: ANDI 1, A{{[0-9]+}}
|
||||
; CHECK-LABEL: _store_i1:
|
||||
; CHECK: ANDI 1, A{{[0-9]+}}
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
@@ -0,0 +1,80 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
|
||||
; M4 adds the register-file ALU multiply and divide instructions. MPYS and
|
||||
; MPYU multiply the 32-bit Rd (the tied destination) by the multiplier field
|
||||
; Rs; with an odd Rd the low 32 bits are stored back and no register pair is
|
||||
; touched. The destination must be odd, so the tied operand uses the
|
||||
; odd-only ARegsOdd class.
|
||||
;
|
||||
; DIVS/DIVU with an odd Rd divide the 32-bit Rd by Rs, leaving the quotient in
|
||||
; Rd. The remainder is derived as dividend - quotient * divisor, because the
|
||||
; hardware only returns the remainder with an even destination pair.
|
||||
|
||||
; A 32x32 low product: one MPYS to an odd destination.
|
||||
define i32 @mul32(i32 %a, i32 %b) {
|
||||
%r = mul i32 %a, %b
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _mul32:
|
||||
; CHECK: MPYS A{{[0-9]+}}, {{A([13579]|11)$}}
|
||||
; CHECK: RETS 2
|
||||
|
||||
; A 16x16 signed product: the operands are truncated to 16 bits and
|
||||
; sign-extended with a shift pair, then multiplied with MPYS.
|
||||
define i32 @mul16s(i32 %a, i32 %b) {
|
||||
%as = shl i32 %a, 16
|
||||
%bs = shl i32 %b, 16
|
||||
%ae = ashr i32 %as, 16
|
||||
%be = ashr i32 %bs, 16
|
||||
%r = mul i32 %ae, %be
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _mul16s:
|
||||
; CHECK: MPYS
|
||||
|
||||
; A 16x16 unsigned product, masked to 16 bits: MPYU.
|
||||
define i32 @mul16u(i32 %a, i32 %b) {
|
||||
%ae = and i32 %a, 65535
|
||||
%be = and i32 %b, 65535
|
||||
%r = mul i32 %ae, %be
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _mul16u:
|
||||
; CHECK: MPYU
|
||||
|
||||
; Signed and unsigned 32/32 division with an odd destination.
|
||||
define i32 @sdiv32(i32 %a, i32 %b) {
|
||||
%r = sdiv i32 %a, %b
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _sdiv32:
|
||||
; CHECK: DIVS A{{[0-9]+}}, {{A([13579]|11)$}}
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i32 @udiv32(i32 %a, i32 %b) {
|
||||
%r = udiv i32 %a, %b
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _udiv32:
|
||||
; CHECK: DIVU A{{[0-9]+}}, {{A([13579]|11)$}}
|
||||
; CHECK: RETS 2
|
||||
|
||||
; Remainder = dividend - quotient * divisor, using the same divide.
|
||||
define i32 @srem32(i32 %a, i32 %b) {
|
||||
%r = srem i32 %a, %b
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _srem32:
|
||||
; CHECK: DIVS
|
||||
; CHECK: MPYS
|
||||
; CHECK: SUB
|
||||
|
||||
define i32 @urem32(i32 %a, i32 %b) {
|
||||
%r = urem i32 %a, %b
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _urem32:
|
||||
; CHECK: DIVU
|
||||
; CHECK: MPYS
|
||||
; CHECK: SUB
|
||||
@@ -0,0 +1,30 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm %s -o %t-a.s
|
||||
; RUN: cp %s %t-b.ll
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm %t-b.ll -o %t-b.s
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s -o %t-stdin.s
|
||||
; RUN: FileCheck %s --check-prefix=SYM < %t-a.s
|
||||
; RUN: FileCheck %s --check-prefix=PUBLIC < %t-a.s
|
||||
; RUN: FileCheck %s --check-prefix=SYM < %t-b.s
|
||||
; RUN: FileCheck %s --check-prefix=PUBLIC < %t-b.s
|
||||
; RUN: FileCheck %s --check-prefix=STDIN < %t-stdin.s
|
||||
; RUN: grep -oE '__2EL_2Estr_[0-9A-F]+' %t-a.s > %t-a.sym
|
||||
; RUN: grep -oE '__2EL_2Estr_[0-9A-F]+' %t-b.s > %t-b.sym
|
||||
; RUN: not diff %t-a.sym %t-b.sym
|
||||
|
||||
; GSPA has one flat symbol table per linked program, so a private label such as
|
||||
; the string literal's ``__.str`` collides when two translation units are
|
||||
; concatenated. M4 appends a deterministic module token to private/local
|
||||
; symbols while leaving public names unchanged. Both files below are compiled
|
||||
; under different module identifiers, so their private names must differ. The
|
||||
; stdin RUN line pins the stable fallback token used when llc's module
|
||||
; identifier is "<stdin>".
|
||||
@.str = private unnamed_addr constant [5 x i8] c"same\00", align 1
|
||||
|
||||
define ptr @public_string() {
|
||||
ret ptr @.str
|
||||
}
|
||||
|
||||
; STDIN: __2EL_2Estr_6750C254:
|
||||
; SYM: __2EL_2Estr_{{[0-9A-F]+}}:
|
||||
; PUBLIC: .globl _public_string
|
||||
; PUBLIC-LABEL: _public_string:
|
||||
@@ -0,0 +1,14 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; A variadic callee anchors the contiguous-stack walk at the address of its
|
||||
; last fixed argument. The argument-copy elision that makes `&arg` the
|
||||
; incoming slot needs the fixed object and the parameter alloca to have the
|
||||
; same size; an i8/i16 anchor would remain a private local and silently read
|
||||
; the wrong slot. Reject it rather than miscompile.
|
||||
; CHECK: TMS34010 varargs requires an i32 or pointer last fixed argument
|
||||
define i32 @vararg_narrow_i8(i32 %a, i8 %b, ...) {
|
||||
entry:
|
||||
%r = zext i8 %b to i32
|
||||
ret i32 %r
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; The i16 counterpart of m4-reject-vararg-narrow.ll.
|
||||
; CHECK: TMS34010 varargs requires an i32 or pointer last fixed argument
|
||||
define i32 @vararg_narrow_i16(i32 %a, i16 %b, ...) {
|
||||
entry:
|
||||
%r = zext i16 %b to i32
|
||||
ret i32 %r
|
||||
}
|
||||
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user