tms34010: emit .data and string literals for GSPA

This commit is contained in:
arti committed 2026-11-11 16:33:37 +00:00
1 parent 0512ce9d6b
commit 454fb4ac15
6 files changed
+392 -24

No files matched your search

+320 -23
View File
@@ -9,17 +9,26 @@
#include "TMS34010MCInstLower.h"
#include "TMS34010.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Twine.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Operator.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetLoweringObjectFile.h"
#include <algorithm>
using namespace llvm;
@@ -32,15 +41,94 @@ public:
StringRef getPassName() const override { return "TMS34010 Assembly Printer"; }
bool doInitialization(Module &M) override;
void emitInstruction(const MachineInstr *MI) override;
void emitGlobalVariable(const GlobalVariable *GV) override;
/// Emit one `.data` definition. Data is emitted during doInitialization so
/// it precedes the code, which is how GSPA programs lay out a module.
void emitDataGlobal(const GlobalVariable *GV);
/// Emit the GSPA directives for one constant initializer. The initializer
/// is already in target (bit-addressed) form; no scaling happens here.
void emitDataInitializer(const Constant *Init);
/// Emit a constant pointer as `.long <symbol>[+<bit addend>]`.
void emitPointerInitializer(const Constant *Init);
/// Emit `.long <symbol>[+<bit addend>]`.
void emitPointerReference(const GlobalValue *GV, int64_t Addend);
/// Emit byte values as wrapped `.byte` lines (16 values per line keeps the
/// line well inside GSPA's DOS-era input buffer). DecimalZeros prints `0`
/// instead of `000h`, used for zero-initialized byte arrays.
void emitByteLines(ArrayRef<uint64_t> Bytes, bool DecimalZeros = false);
static char ID;
};
} // end anonymous namespace
[[noreturn]] static void reportUnsupportedDataInitializer() {
report_fatal_error(
"M2 .data supports only integer, pointer and byte-array initializers",
false);
}
/// Match the integer expression produced by `tms-bitaddr --normalize` for a
/// constant pointer: `ptrtoint(@g)` optionally plus a constant bit offset,
/// with an `add` operand order that may be commuted.
static bool decomposePointerInt(const Constant *C, const GlobalValue *&GV,
int64_t &Addend) {
const auto *CE = dyn_cast<ConstantExpr>(C);
if (!CE)
return false;
if (CE->getOpcode() == Instruction::PtrToInt) {
GV = dyn_cast<GlobalValue>(CE->getOperand(0)->stripPointerCasts());
Addend = 0;
return GV != nullptr;
}
if (CE->getOpcode() != Instruction::Add)
return false;
const Constant *L = CE->getOperand(0);
const Constant *R = CE->getOperand(1);
const GlobalValue *LGV = nullptr;
int64_t LAddend = 0;
if (decomposePointerInt(L, LGV, LAddend)) {
if (const auto *CI = dyn_cast<ConstantInt>(R)) {
GV = LGV;
Addend = LAddend + CI->getSExtValue();
return true;
}
return false;
}
const GlobalValue *RGV = nullptr;
int64_t RAddend = 0;
if (decomposePointerInt(R, RGV, RAddend)) {
if (const auto *CI = dyn_cast<ConstantInt>(L)) {
GV = RGV;
Addend = RAddend + CI->getSExtValue();
return true;
}
}
return false;
}
/// A zero-initialized, writable, externally-visible object is one `.bss`
/// line. Every other definition is emitted as GSPA `.data`.
static bool isBSSGlobal(const GlobalVariable *GV) {
return GV->hasInitializer() && GV->getInitializer()->isNullValue() &&
!GV->isConstant() && !GV->hasPrivateLinkage() &&
GV->getSection().empty() && !GV->isThreadLocal();
}
void TMS34010AsmPrinter::emitInstruction(const MachineInstr *MI) {
TMS34010MCInstLower MCInstLowering(OutContext, *this);
@@ -49,34 +137,243 @@ 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;
}
if (ElemTy->isIntegerTy() || ElemTy->isPointerTy()) {
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
emitDataInitializer(CDA->getElementAsConstant(I));
return;
}
reportUnsupportedDataInitializer();
}
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
Type *ElemTy = CA->getType()->getElementType();
if (ElemTy->isIntegerTy(8)) {
SmallVector<uint64_t, 32> Bytes;
for (const Use &U : CA->operands()) {
const auto *CI = dyn_cast<ConstantInt>(U.get());
if (!CI)
reportUnsupportedDataInitializer();
Bytes.push_back(CI->getZExtValue() & 0xFF);
}
emitByteLines(Bytes);
return;
}
if (ElemTy->isIntegerTy() || 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
OutStreamer->emitRawText("\t.long 0\n");
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() || ElemTy->isPointerTy()) {
for (uint64_t I = 0; I != N; ++I)
OutStreamer->emitRawText("\t.long 0\n");
return;
}
}
reportUnsupportedDataInitializer();
}
reportUnsupportedDataInitializer();
}
bool TMS34010AsmPrinter::doInitialization(Module &M) {
AsmPrinter::doInitialization(M);
// Emit initialized data before the code. Section switches go through the
// streamer so the printer re-emits `.text` for the first function.
// `available_externally` definitions are the linker's job, not ours: emitting
// them as `.globl` definitions could cause a duplicate-definition error.
for (const GlobalVariable &GV : M.globals())
if (!GV.isDeclarationForLinker() && !isBSSGlobal(&GV))
emitDataGlobal(&GV);
return false;
}
void TMS34010AsmPrinter::emitDataGlobal(const GlobalVariable *GV) {
// Only constant initializers can be written as GSPA .data.
const Constant *Init = GV->getInitializer();
if (!Init || GV->isThreadLocal() || !GV->getSection().empty())
reportUnsupportedDataInitializer();
OutStreamer->switchSection(getObjFileLowering().getDataSection());
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitRawText(getSymbol(GV)->getName() + ":\n");
emitDataInitializer(Init);
}
void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
// External declarations (including available_externally) need no output.
if (GV->isDeclarationForLinker())
return;
// Initialized data has already been emitted by doInitialization.
if (!isBSSGlobal(GV))
return;
// GSPA has no .comm/.local directives; a zero-initialized object is one
// .bss line. Sizes and alignment in GSPA are in bits.
const DataLayout &DL = GV->getParent()->getDataLayout();
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
uint64_t SizeBits =
DL.getTypeAllocSize(GV->getValueType()).getFixedValue() * 8;
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitRawText(Twine("\t.bss\t") + getSymbol(GV)->getName() + "," +
Twine(SizeBits) + "," +
Twine(Alignment.value() * 8));
}
char TMS34010AsmPrinter::ID = 0;
@@ -10,6 +10,7 @@
#include "TMS34010.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/GlobalValue.h"
@@ -29,6 +30,36 @@ 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 stays deterministic and collision-free.
static void sanitizeGSPASymbolName(SmallVectorImpl<char> &Name) {
bool NeedsSanitizing = false;
for (char C : Name) {
if (!isAlnum(C) && C != '_') {
NeedsSanitizing = true;
break;
}
}
if (!NeedsSanitizing)
return;
SmallString<128> Sanitized;
Sanitized.reserve(Name.size());
for (char C : Name) {
if (isAlnum(C) || C == '_') {
Sanitized.push_back(C);
} else {
unsigned char Byte = static_cast<unsigned char>(C);
Sanitized.push_back('_');
Sanitized.push_back(hexdigit(Byte >> 4));
Sanitized.push_back(hexdigit(Byte & 0xF));
}
}
Name.assign(Sanitized.begin(), Sanitized.end());
}
namespace {
class TMS34010ObjectFile : public TargetLoweringObjectFileELF {
@@ -37,6 +68,7 @@ public:
const TargetMachine &TM) const override {
SmallString<128> Name("_");
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
sanitizeGSPASymbolName(Name);
return getContext().getOrCreateSymbol(Name);
}
@@ -46,6 +78,7 @@ public:
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
OutName.push_back('_');
OutName.append(Name.begin(), Name.end());
sanitizeGSPASymbolName(OutName);
}
};
@@ -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
+20
View File
@@ -1,7 +1,13 @@
; 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
@@ -9,7 +15,21 @@ entry:
; CHECK: .data
; CHECK-LABEL: _g:
; CHECK: .long 42
; CHECK-LABEL: _c:
; CHECK: .byte 041h
; CHECK-LABEL: _s:
; CHECK: .word 01234h
; CHECK-LABEL: _mask:
; CHECK: .word 0FFFFh
; CHECK-LABEL: _arr:
; CHECK: .long 11
; CHECK: .long 22
; CHECK: .byte 030h, 03Ah, 03Ah, 030h, 000h
; CHECK-LABEL: {{.*}}long:
; CHECK: .byte 041h, 042h, 043h, 044h, 045h, 046h, 047h, 048h, 049h, 04Ah, 04Bh, 04Ch, 04Dh, 04Eh, 04Fh, 050h
; CHECK: .byte 051h, 052h, 053h, 054h, 000h
; CHECK-LABEL: {{.*}}zeros16:
; CHECK: .word 0
; CHECK: .word 0
; CHECK-LABEL: {{.*}}zeros8:
; CHECK: .byte 0, 0, 0
+3
View File
@@ -1,6 +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
+2 -1
View File
@@ -5,6 +5,7 @@ entry:
ret ptr @.str
}
; CHECK: .data
; CHECK: [[STR:__[A-Za-z0-9_]+]]:
; CHECK: .byte 068h, 065h, 06Ch, 06Ch, 06Fh, 000h
; CHECK: MOVI _{{.*}}, A{{[0-9]+}}
; CHECK: MOVI [[STR]], A{{[0-9]+}}
; CHECK: RETS 0