From 454fb4ac15be8a3303d737c7deff46bfcc68f452 Mon Sep 17 00:00:00 2001 From: arti Date: Wed, 11 Nov 2026 16:23:34 +0000 Subject: [PATCH] tms34010: emit .data and string literals for GSPA --- .../Target/TMS34010/TMS34010AsmPrinter.cpp | 343 ++++++++++++++++-- .../Target/TMS34010/TMS34010TargetMachine.cpp | 33 ++ llvm/test/CodeGen/TMS34010/m2-data-avail.ll | 14 + llvm/test/CodeGen/TMS34010/m2-data.ll | 20 + llvm/test/CodeGen/TMS34010/m2-ptrinit.ll | 3 + llvm/test/CodeGen/TMS34010/m2-string.ll | 3 +- 6 files changed, 392 insertions(+), 24 deletions(-) create mode 100644 llvm/test/CodeGen/TMS34010/m2-data-avail.ll diff --git a/llvm/lib/Target/TMS34010/TMS34010AsmPrinter.cpp b/llvm/lib/Target/TMS34010/TMS34010AsmPrinter.cpp index 5a48b7a3263a..d95e16b9cf5e 100644 --- a/llvm/lib/Target/TMS34010/TMS34010AsmPrinter.cpp +++ b/llvm/lib/Target/TMS34010/TMS34010AsmPrinter.cpp @@ -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 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 [+]`. + void emitPointerInitializer(const Constant *Init); + + /// Emit `.long [+]`. + 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 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(C); + if (!CE) + return false; + + if (CE->getOpcode() == Instruction::PtrToInt) { + GV = dyn_cast(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(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(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(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(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(V)) { + const auto *BaseGV = + dyn_cast(GEP->getPointerOperand()->stripPointerCasts()); + if (!BaseGV || GEP->getNumOperands() != 2) + reportUnsupportedDataInitializer(); + + const auto *Idx = dyn_cast(GEP->getOperand(1)); + if (!Idx) + reportUnsupportedDataInitializer(); + + emitPointerReference(BaseGV, Idx->getSExtValue()); + return; + } + + // tms-bitaddr --normalize rewrites constant GEPs in initializers to + // inttoptr (add (ptrtoint @g), ); accept that shape too. + if (const auto *CE = dyn_cast(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 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(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(Init)) { + Type *ElemTy = CDA->getElementType(); + if (ElemTy->isIntegerTy(8)) { + SmallVector 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(Init)) { + Type *ElemTy = CA->getType()->getElementType(); + if (ElemTy->isIntegerTy(8)) { + SmallVector Bytes; + for (const Use &U : CA->operands()) { + const auto *CI = dyn_cast(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(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(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(Ty)) { + Type *ElemTy = AT->getElementType(); + uint64_t N = AT->getNumElements(); + if (ElemTy->isIntegerTy(8)) { + SmallVector 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; diff --git a/llvm/lib/Target/TMS34010/TMS34010TargetMachine.cpp b/llvm/lib/Target/TMS34010/TMS34010TargetMachine.cpp index 66744f2ab4dc..5b7ae2fae9c0 100644 --- a/llvm/lib/Target/TMS34010/TMS34010TargetMachine.cpp +++ b/llvm/lib/Target/TMS34010/TMS34010TargetMachine.cpp @@ -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 &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(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); } }; diff --git a/llvm/test/CodeGen/TMS34010/m2-data-avail.ll b/llvm/test/CodeGen/TMS34010/m2-data-avail.ll new file mode 100644 index 000000000000..a01c83b5fa87 --- /dev/null +++ b/llvm/test/CodeGen/TMS34010/m2-data-avail.ll @@ -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 diff --git a/llvm/test/CodeGen/TMS34010/m2-data.ll b/llvm/test/CodeGen/TMS34010/m2-data.ll index eb83a0a14fa9..e05daafc2b00 100644 --- a/llvm/test/CodeGen/TMS34010/m2-data.ll +++ b/llvm/test/CodeGen/TMS34010/m2-data.ll @@ -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 diff --git a/llvm/test/CodeGen/TMS34010/m2-ptrinit.ll b/llvm/test/CodeGen/TMS34010/m2-ptrinit.ll index 504c5a8436a1..0afd32c505d4 100644 --- a/llvm/test/CodeGen/TMS34010/m2-ptrinit.ll +++ b/llvm/test/CodeGen/TMS34010/m2-ptrinit.ll @@ -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 diff --git a/llvm/test/CodeGen/TMS34010/m2-string.ll b/llvm/test/CodeGen/TMS34010/m2-string.ll index dd5873588ee6..be58839280b6 100644 --- a/llvm/test/CodeGen/TMS34010/m2-string.ll +++ b/llvm/test/CodeGen/TMS34010/m2-string.ll @@ -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