[lld] [ELF,SPARC] Accept EM_SPARC32PLUS inputs on the 32-bit SPARC target (PR #228201)
Imre Kaloz via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 11:55:45 PDT 2026
https://github.com/kaloz created https://github.com/llvm/llvm-project/pull/228201
EM_SPARC32PLUS marks an object holding V9 instructions under the
32-bit ABI, so it links with plain EM_SPARC objects and needs no
relocation handling of its own. Rejecting it outright also rejected
the 32-bit libgcc and libatomic distributions ship.
Take it as input, and OR the widest instruction-set bits any
EM_SPARC32PLUS object asks for into the output, as elf32_sparc does:
EF_SPARC_32PLUS, then SUN_US1, then SUN_US3 (implies US1); HAL_R1
selects nothing. gas sets SUN_US1 per UltraSPARC I instruction, not
per -mcpu, so a static link needs the bits OR'd in from such a
library.
The machine is normalized to EM_SPARC during the link, promoted on
write. EM_SPARCV9 and elf32_sparc's other malformed shapes — wrong
class/byte order, or missing EF_SPARC_32PLUS — are rejected.
Signed-off-by: Imre Kaloz <kaloz at kernel.org>
>From 79e933d51b6b1c1b32c8613f884f560bc985c105 Mon Sep 17 00:00:00 2001
From: Imre Kaloz <kaloz at kernel.org>
Date: Tue, 1 Sep 2026 23:34:24 +0200
Subject: [PATCH 1/3] [ELF,SPARC] Handle TLS GD and LD relocations
A general-dynamic reference forms a GOT offset and calls __tls_get_addr:
sethi %tgd_hi22(sym), %o0
add %o0, %tgd_lo10(sym), %o0
add %l7, %o0, %o0, %tgd_add(sym)
call __tls_get_addr, %tgd_call(sym)
The GOT entry holds the module index and offset. Local dynamic uses
%tldm/%tldo_hix22/%tldo_lox10 the same way; @dtpoff is non-negative, so
HIX/LOX take the plain high/low split.
The call relocation names the TLS symbol, not the callee, so a call
surviving optimization is rebound to __tls_get_addr in
finalizeRelocScan(), after the parallel scan.
An executable optimizes general dynamic to initial exec or local exec,
local dynamic to local exec.
Signed-off-by: Imre Kaloz <kaloz at kernel.org>
---
lld/ELF/Arch/SPARCV9.cpp | 151 ++++++++++++++++++++++++++++++++++
lld/test/ELF/sparcv9-tls-gd.s | 87 ++++++++++++++++++++
lld/test/ELF/sparcv9-tls-ld.s | 78 ++++++++++++++++++
3 files changed, 316 insertions(+)
create mode 100644 lld/test/ELF/sparcv9-tls-gd.s
create mode 100644 lld/test/ELF/sparcv9-tls-ld.s
diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index 7797990571def..d777daea7bfd5 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -6,7 +6,9 @@
//
//===----------------------------------------------------------------------===//
+#include "InputFiles.h"
#include "RelocScan.h"
+#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
@@ -30,6 +32,7 @@ class SPARCV9 final : public TargetInfo {
void writeGotHeader(uint8_t *buf) const override;
void writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const override;
+ void finalizeRelocScan() override;
template <class ELFT, class RelTy>
void scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
unsigned shard);
@@ -48,6 +51,8 @@ SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
relativeRel = R_SPARC_RELATIVE;
symbolicRel = R_SPARC_64;
tlsGotRel = R_SPARC_TLS_TPOFF64;
+ tlsModuleIndexRel = R_SPARC_TLS_DTPMOD64;
+ tlsOffsetRel = R_SPARC_TLS_DTPOFF64;
gotHeaderEntriesNum = 1;
pltEntrySize = 32;
pltHeaderSize = 4 * pltEntrySize;
@@ -118,6 +123,7 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
switch (type) {
case R_SPARC_NONE:
case R_SPARC_TLS_IE_ADD:
+ case R_SPARC_TLS_LDO_ADD:
continue;
// Absolute relocations:
@@ -182,6 +188,44 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
}
break;
+ // TLS GD relocations. In an executable the sequence is optimized to
+ // Initial Exec for a preemptible symbol and to Local Exec otherwise.
+ case R_SPARC_TLS_GD_HI22:
+ case R_SPARC_TLS_GD_LO10:
+ rs.handleTlsGd(R_TLSGD_GOT, R_GOT_OFF, R_TPREL, type, offset, addend,
+ sym);
+ continue;
+ case R_SPARC_TLS_GD_ADD:
+ // A marker on the add. R_ABS is a dummy for the unoptimized sequence and
+ // writes nothing; an optimized one rewrites the instruction.
+ rs.handleTlsGd(R_ABS, R_GOT_OFF, R_TPREL, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_GD_CALL:
+ // The call names the TLS symbol rather than __tls_get_addr, so an
+ // unoptimized call is rebound by finalizeRelocScan().
+ rs.handleTlsGd(R_PLT_PC, R_GOT_OFF, R_TPREL, type, offset, addend, sym);
+ continue;
+
+ // TLS LD relocations. In an executable the sequence is optimized to
+ // Local Exec.
+ case R_SPARC_TLS_LDM_HI22:
+ case R_SPARC_TLS_LDM_LO10:
+ rs.handleTlsLd(R_TLSLD_GOT, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_LDM_ADD:
+ rs.handleTlsLd(R_ABS, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_LDM_CALL:
+ rs.handleTlsLd(R_PLT_PC, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_LDO_HIX22:
+ case R_SPARC_TLS_LDO_LOX10:
+ // @dtpoff is a non-negative offset into the module's TLS block, so it
+ // uses the plain high/low split despite the HIX/LOX names. Local Exec
+ // makes it the negative @tpoff, which needs the complement encoding.
+ expr = ctx.arg.shared ? R_DTPREL : R_TPREL;
+ break;
+
// TLS LE relocations:
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
@@ -394,11 +438,118 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
: 0x80100000 | (insn & 0x3e00001f));
break;
}
+ case R_SPARC_TLS_DTPMOD64:
+ case R_SPARC_TLS_DTPOFF64:
+ case R_SPARC_TLS_TPOFF64:
+ // V-xword64. A GOT slot the link resolves: the module index of the output
+ // module, or an offset within a module whose TLS block is known.
+ write64be(loc, val);
+ break;
+ case R_SPARC_TLS_GD_HI22: {
+ // T-imm22. Local Exec encodes the complement, as R_SPARC_TLS_LE_HIX22 does.
+ uint64_t v = rel.expr == R_TPREL ? ~val : val;
+ write32be(loc, (read32be(loc) & ~0x003fffff) | ((v >> 10) & 0x003fffff));
+ break;
+ }
+ case R_SPARC_TLS_GD_LO10:
+ if (rel.expr == R_TPREL)
+ // add %rs1, imm, %rd -> xor %rs1, imm, %rd, T-simm13.
+ write32be(loc, (read32be(loc) & ~0x00001fff) | 0x80182000 |
+ (val & 0x000003ff) | 0x1c00);
+ else
+ // T-simm10
+ write32be(loc, (read32be(loc) & ~0x000003ff) | (val & 0x000003ff));
+ break;
+ case R_SPARC_TLS_GD_ADD:
+ if (rel.expr == R_GOT_OFF)
+ // Initial Exec: add %rs1, %rs2, %rd -> ldx [%rs1 + %rs2], %rd.
+ write32be(loc, (read32be(loc) & 0x3e07c01f) | 0xc0000000 | (0x0b << 19));
+ else if (rel.expr == R_TPREL)
+ // Local Exec: the GOT pointer becomes the thread pointer, %rs1 -> %g7.
+ write32be(loc, (read32be(loc) & ~0x0007c000) | (7 << 14));
+ break;
+ case R_SPARC_TLS_GD_CALL:
+ if (rel.expr == R_GOT_OFF)
+ write32be(loc, 0x9001c008); // add %g7, %o0, %o0
+ else if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ else
+ // V-disp30, the call to __tls_get_addr.
+ write32be(loc, (read32be(loc) & ~0x3fffffff) | ((val >> 2) & 0x3fffffff));
+ break;
+ case R_SPARC_TLS_LDM_HI22:
+ if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ else
+ // T-imm22
+ write32be(loc,
+ (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
+ break;
+ case R_SPARC_TLS_LDM_LO10:
+ if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ else
+ // T-simm10
+ write32be(loc, (read32be(loc) & ~0x000003ff) | (val & 0x000003ff));
+ break;
+ case R_SPARC_TLS_LDM_ADD:
+ if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ break;
+ case R_SPARC_TLS_LDM_CALL:
+ if (rel.expr == R_TPREL)
+ // Local Exec: the paired LDO add takes the thread pointer from %o0.
+ write32be(loc, 0x90100007); // mov %g7, %o0
+ else
+ // V-disp30, the call to __tls_get_addr.
+ write32be(loc, (read32be(loc) & ~0x3fffffff) | ((val >> 2) & 0x3fffffff));
+ break;
+ case R_SPARC_TLS_LDO_HIX22: {
+ // T-imm22
+ uint64_t v = rel.expr == R_TPREL ? ~val : val;
+ write32be(loc, (read32be(loc) & ~0x003fffff) | ((v >> 10) & 0x003fffff));
+ break;
+ }
+ case R_SPARC_TLS_LDO_LOX10:
+ // T-simm13. Only the negative @tpoff needs the sign extension bits.
+ write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) |
+ (rel.expr == R_TPREL ? 0x1c00 : 0));
+ break;
default:
llvm_unreachable("unknown relocation");
}
}
+void SPARCV9::finalizeRelocScan() {
+ Symbol *tga = nullptr;
+
+ // R_SPARC_TLS_GD_CALL/LDM_CALL name the TLS symbol, not the callee. Rebind
+ // the calls that survived optimization (recorded as R_PLT_PC by
+ // scanSectionImpl) to __tls_get_addr. The symbol table cannot be reached
+ // from scanSectionImpl, which runs in parallel.
+ for (ELFFileBase *f : ctx.objectFiles) {
+ for (InputSectionBase *s : f->getSections()) {
+ auto *isec = dyn_cast_or_null<InputSection>(s);
+ if (!isec || !isec->isLive())
+ continue;
+ for (Relocation &rel : isec->relocs()) {
+ if (rel.expr != R_PLT_PC || (rel.type != R_SPARC_TLS_GD_CALL &&
+ rel.type != R_SPARC_TLS_LDM_CALL))
+ continue;
+ if (!tga) {
+ tga = ctx.symtab->addSymbol(Undefined{ctx.internalFile,
+ "__tls_get_addr", STB_GLOBAL,
+ STV_DEFAULT, STT_FUNC});
+ tga->isUsedInRegularObj = true;
+ tga->isPreemptible = true;
+ tga->setFlags(NEEDS_PLT | USED);
+ }
+ rel.sym = tga;
+ }
+ }
+ }
+}
+
void SPARCV9::writeGotHeader(uint8_t *buf) const {
// _GLOBAL_OFFSET_TABLE_[0] = _DYNAMIC
write64be(buf, ctx.in.dynamic->getVA());
diff --git a/lld/test/ELF/sparcv9-tls-gd.s b/lld/test/ELF/sparcv9-tls-gd.s
new file mode 100644
index 0000000000000..ddde0a65c79b7
--- /dev/null
+++ b/lld/test/ELF/sparcv9-tls-gd.s
@@ -0,0 +1,87 @@
+# REQUIRES: sparc
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 b.s -o b.o
+# RUN: ld.lld -shared b.o -o b.so
+# RUN: ld.lld -shared a.o b.so -o a.so
+# RUN: llvm-readelf -r a.so | FileCheck %s --check-prefix=GD-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a.so | FileCheck %s --check-prefix=GD
+
+## Each sequence gets a two-slot GOT entry holding the module index and the
+## offset within that module's TLS block. a0 is hidden, so its offset is known
+## at link time and only the module index needs a dynamic relocation. The call
+## goes through the PLT even though the relocation names the TLS symbol rather
+## than __tls_get_addr.
+# GD-REL: Relocation section '.rela.dyn' {{.*}} contains 3 entries:
+# GD-REL: R_SPARC_TLS_DTPMOD64 0{{$}}
+# GD-REL-DAG: R_SPARC_TLS_DTPMOD64 {{.*}} b + 0
+# GD-REL-DAG: R_SPARC_TLS_DTPOFF64 {{.*}} b + 0
+# GD-REL: Relocation section '.rela.plt' {{.*}} contains 1 entries:
+# GD-REL: R_SPARC_JMP_SLOT {{.*}} __tls_get_addr + 0
+
+# GD-LABEL: <_start>:
+# GD-NEXT: sethi 0, %o0
+# GD-NEXT: add %o0, 8, %o0
+# GD-NEXT: add %l7, %o0, %o0
+# GD-NEXT: call
+# GD-NEXT: nop
+# GD-NEXT: sethi 0, %o1
+# GD-NEXT: add %o1, 24, %o1
+# GD-NEXT: add %l7, %o1, %o0
+# GD-NEXT: call
+# GD-NEXT: nop
+
+## In an executable a0 is not preemptible, so its sequence becomes Local Exec:
+## the sethi holds the complement of the offset, the add becomes an xor, the
+## GOT pointer becomes the thread pointer and the call is dropped. b is
+## preemptible, so its sequence becomes Initial Exec instead: the add becomes
+## the GOT load and the call becomes the thread-pointer add.
+# RUN: ld.lld a.o b.so -o a
+# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=EXE-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a | FileCheck %s --check-prefix=EXE
+
+# EXE-REL: Relocation section '.rela.dyn' {{.*}} contains 1 entries:
+# EXE-REL: R_SPARC_TLS_TPOFF64 {{.*}} b + 0
+# EXE-REL-NOT: R_SPARC_TLS_DTPMOD64
+# EXE-REL-NOT: __tls_get_addr
+
+# EXE-LABEL: <_start>:
+# EXE-NEXT: sethi 0, %o0
+# EXE-NEXT: xor %o0, -8, %o0
+# EXE-NEXT: add %g7, %o0, %o0
+# EXE-NEXT: nop
+# EXE-NEXT: nop
+# EXE-NEXT: sethi 0, %o1
+# EXE-NEXT: add %o1, 8, %o1
+# EXE-NEXT: ldx [%l7+%o1], %o0
+# EXE-NEXT: add %g7, %o0, %o0
+# EXE-NEXT: nop
+
+#--- a.s
+.globl _start
+_start:
+## a0 is hidden, so it is not preemptible in an executable.
+ sethi %tgd_hi22(a0), %o0
+ add %o0, %tgd_lo10(a0), %o0
+ add %l7, %o0, %o0, %tgd_add(a0)
+ call __tls_get_addr, %tgd_call(a0)
+ nop
+
+## b is defined in a DSO, so it stays preemptible in an executable.
+ sethi %tgd_hi22(b), %o1
+ add %o1, %tgd_lo10(b), %o1
+ add %l7, %o1, %o0, %tgd_add(b)
+ call __tls_get_addr, %tgd_call(b)
+ nop
+
+.section .tbss,"awT", at nobits
+.globl a0
+.hidden a0
+a0:
+ .xword 0
+
+#--- b.s
+.section .tbss,"awT", at nobits
+.globl b
+b:
+ .xword 0
diff --git a/lld/test/ELF/sparcv9-tls-ld.s b/lld/test/ELF/sparcv9-tls-ld.s
new file mode 100644
index 0000000000000..843cb2d856cfa
--- /dev/null
+++ b/lld/test/ELF/sparcv9-tls-ld.s
@@ -0,0 +1,78 @@
+# REQUIRES: sparc
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 a.s -o a.o
+# RUN: ld.lld -shared a.o -o a.so
+# RUN: llvm-readelf -r a.so | FileCheck %s --check-prefix=LD-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a.so | FileCheck %s --check-prefix=LD
+
+## Local Dynamic needs only the module index, so one GOT entry and one dynamic
+## relocation serve every symbol in the module. The per-symbol offsets are
+## link-time constants.
+# LD-REL: Relocation section '.rela.dyn' {{.*}} contains 1 entries:
+# LD-REL: R_SPARC_TLS_DTPMOD64 0{{$}}
+# LD-REL: Relocation section '.rela.plt' {{.*}} contains 1 entries:
+# LD-REL: R_SPARC_JMP_SLOT {{.*}} __tls_get_addr + 0
+
+# LD-LABEL: <_start>:
+# LD-NEXT: sethi 0, %o0
+# LD-NEXT: add %o0, 8, %o0
+# LD-NEXT: add %l7, %o0, %o0
+# LD-NEXT: call
+# LD-NEXT: nop
+## a0 is at offset 0 in the TLS block, a1 at 8.
+# LD-NEXT: sethi 0, %o1
+# LD-NEXT: xor %o1, 0, %o1
+# LD-NEXT: add %o0, %o1, %o2
+# LD-NEXT: sethi 0, %o3
+# LD-NEXT: xor %o3, 8, %o3
+# LD-NEXT: add %o0, %o3, %o4
+
+## In an executable the whole sequence becomes Local Exec: the module-index
+## setup is dropped, the call loads the thread pointer into %o0, and the
+## per-symbol offsets become the negative @tpoff, encoded as a complement.
+# RUN: ld.lld a.o -o a
+# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=LE-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a | FileCheck %s --check-prefix=LE
+
+# LE-REL-NOT: R_SPARC_TLS
+# LE-REL-NOT: __tls_get_addr
+
+# LE-LABEL: <_start>:
+# LE-NEXT: nop
+# LE-NEXT: nop
+# LE-NEXT: nop
+# LE-NEXT: mov %g7, %o0
+# LE-NEXT: nop
+## a0 - tp = -0x10, a1 - tp = -8.
+# LE-NEXT: sethi 0, %o1
+# LE-NEXT: xor %o1, -16, %o1
+# LE-NEXT: add %o0, %o1, %o2
+# LE-NEXT: sethi 0, %o3
+# LE-NEXT: xor %o3, -8, %o3
+# LE-NEXT: add %o0, %o3, %o4
+
+#--- a.s
+.globl _start
+_start:
+ sethi %tldm_hi22(a0), %o0
+ add %o0, %tldm_lo10(a0), %o0
+ add %l7, %o0, %o0, %tldm_add(a0)
+ call __tls_get_addr, %tldm_call(a0)
+ nop
+
+ sethi %tldo_hix22(a0), %o1
+ xor %o1, %tldo_lox10(a0), %o1
+ add %o0, %o1, %o2, %tldo_add(a0)
+
+ sethi %tldo_hix22(a1), %o3
+ xor %o3, %tldo_lox10(a1), %o3
+ add %o0, %o3, %o4, %tldo_add(a1)
+
+.section .tbss,"awT", at nobits
+.globl a0, a1
+.hidden a0
+.hidden a1
+a0:
+ .xword 0
+a1:
+ .xword 0
>From 183b23b6de8e91e7786146b90fd7e2284e948102 Mon Sep 17 00:00:00 2001
From: Imre Kaloz <kaloz at kernel.org>
Date: Tue, 1 Sep 2026 23:40:59 +0200
Subject: [PATCH 2/3] [ELF,SPARC] Add the 32-bit SPARC target
The 32-bit psABI (EM_SPARC, elf32_sparc) shares its relocation set with
V9, so parametrize the existing target on ABI width rather than
duplicating it. Width-dependent parts: the absolute and TLS-GOT
relocation types, the PLT entry, and the register width a relaxed
initial exec load uses (ld, not ldx).
The PLT entry is three words where V9 uses eight, branching to .PLT0
rather than .PLT1:
sethi (. - .PLT0), %g1
ba,a .PLT0
nop
A relocated value reaches the target sign-extended to the ABI width,
so a 32-bit address with bit 31 set arrives with every high bit set;
unsigned range checks would take the masked-off bits as an overflow.
Both ABIs share GNU ld's 8K common page size; elf32_sparc's maximum
page size is 64K.
Signed-off-by: Imre Kaloz <kaloz at kernel.org>
---
lld/ELF/Arch/{SPARCV9.cpp => SPARC.cpp} | 135 +++++++++++++++++-------
lld/ELF/CMakeLists.txt | 2 +-
lld/ELF/Driver.cpp | 16 ++-
lld/ELF/InputFiles.cpp | 3 +
lld/ELF/InputSection.cpp | 1 +
lld/ELF/ScriptParser.cpp | 1 +
lld/ELF/SyntheticSections.cpp | 3 +-
lld/ELF/Target.cpp | 2 +
lld/ELF/Target.h | 1 +
lld/test/ELF/Inputs/sparc-elf64.yaml | 6 ++
lld/test/ELF/sparc32-plt.s | 36 +++++++
lld/test/ELF/sparc32-reloc.s | 28 +++++
lld/test/ELF/sparc32-tls.s | 66 ++++++++++++
13 files changed, 254 insertions(+), 46 deletions(-)
rename lld/ELF/Arch/{SPARCV9.cpp => SPARC.cpp} (82%)
create mode 100644 lld/test/ELF/Inputs/sparc-elf64.yaml
create mode 100644 lld/test/ELF/sparc32-plt.s
create mode 100644 lld/test/ELF/sparc32-reloc.s
create mode 100644 lld/test/ELF/sparc32-tls.s
diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARC.cpp
similarity index 82%
rename from lld/ELF/Arch/SPARCV9.cpp
rename to lld/ELF/Arch/SPARC.cpp
index d777daea7bfd5..8270265d9a4d9 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARC.cpp
@@ -22,9 +22,13 @@ using namespace lld;
using namespace lld::elf;
namespace {
-class SPARCV9 final : public TargetInfo {
+// Handles both the 64-bit (V9, EM_SPARCV9) and the 32-bit (V8, EM_SPARC)
+// psABIs, which share their relocation set. The width-dependent parts are the
+// absolute and TLS-GOT relocation types, the PLT entry, and the register width
+// a relaxed TLS load uses.
+class SPARC final : public TargetInfo {
public:
- SPARCV9(Ctx &);
+ SPARC(Ctx &, bool is64);
RelExpr getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const override;
RelType getDynRel(RelType type) const override;
@@ -37,36 +41,53 @@ class SPARCV9 final : public TargetInfo {
void scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
unsigned shard);
void scanSection(InputSectionBase &sec, unsigned shard) override {
- elf::scanSection1<SPARCV9, ELF64BE>(*this, sec, shard);
+ if (is64)
+ elf::scanSection1<SPARC, ELF64BE>(*this, sec, shard);
+ else
+ elf::scanSection1<SPARC, ELF32BE>(*this, sec, shard);
}
void relocate(uint8_t *loc, const Relocation &rel,
uint64_t val) const override;
+
+private:
+ bool is64;
};
} // namespace
-SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
+SPARC::SPARC(Ctx &ctx, bool is64) : TargetInfo(ctx), is64(is64) {
copyRel = R_SPARC_COPY;
gotRel = R_SPARC_GLOB_DAT;
pltRel = R_SPARC_JMP_SLOT;
relativeRel = R_SPARC_RELATIVE;
- symbolicRel = R_SPARC_64;
- tlsGotRel = R_SPARC_TLS_TPOFF64;
- tlsModuleIndexRel = R_SPARC_TLS_DTPMOD64;
- tlsOffsetRel = R_SPARC_TLS_DTPOFF64;
+ if (is64) {
+ symbolicRel = R_SPARC_64;
+ tlsGotRel = R_SPARC_TLS_TPOFF64;
+ tlsModuleIndexRel = R_SPARC_TLS_DTPMOD64;
+ tlsOffsetRel = R_SPARC_TLS_DTPOFF64;
+ pltEntrySize = 32;
+ defaultMaxPageSize = 0x100000;
+ defaultImageBase = 0x100000;
+ } else {
+ symbolicRel = R_SPARC_32;
+ tlsGotRel = R_SPARC_TLS_TPOFF32;
+ tlsModuleIndexRel = R_SPARC_TLS_DTPMOD32;
+ tlsOffsetRel = R_SPARC_TLS_DTPOFF32;
+ pltEntrySize = 12;
+ defaultMaxPageSize = 0x10000;
+ defaultImageBase = 0x10000;
+ }
gotHeaderEntriesNum = 1;
- pltEntrySize = 32;
pltHeaderSize = 4 * pltEntrySize;
usesGotPlt = false;
+ // Both ABIs share GNU ld's 8K common page size.
defaultCommonPageSize = 8192;
- defaultMaxPageSize = 0x100000;
- defaultImageBase = 0x100000;
}
// Only needed to support relocations used by relocateNonAlloc and
// preprocessRelocs.
-RelExpr SPARCV9::getRelExpr(RelType type, const Symbol &s,
- const uint8_t *loc) const {
+RelExpr SPARC::getRelExpr(RelType type, const Symbol &s,
+ const uint8_t *loc) const {
switch (type) {
case R_SPARC_8:
case R_SPARC_16:
@@ -87,13 +108,13 @@ RelExpr SPARCV9::getRelExpr(RelType type, const Symbol &s,
}
}
-RelType SPARCV9::getDynRel(RelType type) const {
- if (type == R_SPARC_64)
+RelType SPARC::getDynRel(RelType type) const {
+ if (type == symbolicRel)
return type;
return R_SPARC_NONE;
}
-int64_t SPARCV9::getImplicitAddend(const uint8_t *buf, RelType type) const {
+int64_t SPARC::getImplicitAddend(const uint8_t *buf, RelType type) const {
switch (type) {
case R_SPARC_64:
case R_SPARC_GLOB_DAT:
@@ -105,8 +126,8 @@ int64_t SPARCV9::getImplicitAddend(const uint8_t *buf, RelType type) const {
}
template <class ELFT, class RelTy>
-void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
- unsigned shard) {
+void SPARC::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
+ unsigned shard) {
RelocScan rs(ctx, &sec, shard);
sec.relocations.reserve(rels.size());
for (auto it = rels.begin(); it != rels.end(); ++it) {
@@ -257,8 +278,12 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
}
}
-void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
+void SPARC::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
+ // relocateImpl sign-extends to the ABI width, so on the 32-bit ABI a value
+ // with bit 31 set arrives with every high bit set. The field writes mask
+ // those away, but the unsigned range checks would read them as an overflow.
+ const uint64_t uval = is64 ? val : uint32_t(val);
+
switch (rel.type) {
case R_SPARC_8:
// V-byte8
@@ -274,7 +299,7 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
case R_SPARC_32:
case R_SPARC_UA32:
// V-word32
- checkUInt(ctx, loc, val, 32, rel);
+ checkUInt(ctx, loc, uval, 32, rel);
write32be(loc, val);
break;
case R_SPARC_DISP8:
@@ -325,7 +350,7 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
break;
case R_SPARC_HI22:
// V-imm22
- checkUInt(ctx, loc, val, 32, rel);
+ checkUInt(ctx, loc, uval, 32, rel);
write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
break;
case R_SPARC_WDISP22:
@@ -445,6 +470,12 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
// module, or an offset within a module whose TLS block is known.
write64be(loc, val);
break;
+ case R_SPARC_TLS_DTPMOD32:
+ case R_SPARC_TLS_DTPOFF32:
+ case R_SPARC_TLS_TPOFF32:
+ // V-word32, the 32-bit ABI's GOT slots.
+ write32be(loc, val);
+ break;
case R_SPARC_TLS_GD_HI22: {
// T-imm22. Local Exec encodes the complement, as R_SPARC_TLS_LE_HIX22 does.
uint64_t v = rel.expr == R_TPREL ? ~val : val;
@@ -462,8 +493,9 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
break;
case R_SPARC_TLS_GD_ADD:
if (rel.expr == R_GOT_OFF)
- // Initial Exec: add %rs1, %rs2, %rd -> ldx [%rs1 + %rs2], %rd.
- write32be(loc, (read32be(loc) & 0x3e07c01f) | 0xc0000000 | (0x0b << 19));
+ // Initial Exec: add %rs1, %rs2, %rd -> ld/ldx [%rs1 + %rs2], %rd.
+ write32be(loc, (read32be(loc) & 0x3e07c01f) | 0xc0000000 |
+ (is64 ? (0x0b << 19) : 0));
else if (rel.expr == R_TPREL)
// Local Exec: the GOT pointer becomes the thread pointer, %rs1 -> %g7.
write32be(loc, (read32be(loc) & ~0x0007c000) | (7 << 14));
@@ -520,7 +552,7 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
}
}
-void SPARCV9::finalizeRelocScan() {
+void SPARC::finalizeRelocScan() {
Symbol *tga = nullptr;
// R_SPARC_TLS_GD_CALL/LDM_CALL name the TLS symbol, not the callee. Rebind
@@ -550,28 +582,51 @@ void SPARCV9::finalizeRelocScan() {
}
}
-void SPARCV9::writeGotHeader(uint8_t *buf) const {
+void SPARC::writeGotHeader(uint8_t *buf) const {
// _GLOBAL_OFFSET_TABLE_[0] = _DYNAMIC
- write64be(buf, ctx.in.dynamic->getVA());
+ if (is64)
+ write64be(buf, ctx.in.dynamic->getVA());
+ else
+ write32be(buf, ctx.in.dynamic->getVA());
}
-void SPARCV9::writePlt(uint8_t *buf, const Symbol & /*sym*/,
- uint64_t pltEntryAddr) const {
+void SPARC::writePlt(uint8_t *buf, const Symbol & /*sym*/,
+ uint64_t pltEntryAddr) const {
+ uint64_t off = pltEntryAddr - ctx.in.plt->getVA();
+ if (is64) {
+ const uint8_t pltData[] = {
+ 0x03, 0x00, 0x00, 0x00, // sethi (. - .PLT0), %g1
+ 0x30, 0x68, 0x00, 0x00, // ba,a %xcc, .PLT1
+ 0x01, 0x00, 0x00, 0x00, // nop
+ 0x01, 0x00, 0x00, 0x00, // nop
+ 0x01, 0x00, 0x00, 0x00, // nop
+ 0x01, 0x00, 0x00, 0x00, // nop
+ 0x01, 0x00, 0x00, 0x00, // nop
+ 0x01, 0x00, 0x00, 0x00 // nop
+ };
+ memcpy(buf, pltData, sizeof(pltData));
+ relocateNoSym(buf, R_SPARC_22, off);
+ relocateNoSym(buf + 4, R_SPARC_WDISP19, -(off + 4 - pltEntrySize));
+ return;
+ }
+
+ // The 32-bit entry is three words and branches to .PLT0, where the 64-bit
+ // one branches to .PLT1. The sethi carries the entry's own offset, which is
+ // how the resolver recovers the relocation index.
const uint8_t pltData[] = {
- 0x03, 0x00, 0x00, 0x00, // sethi (. - .PLT0), %g1
- 0x30, 0x68, 0x00, 0x00, // ba,a %xcc, .PLT1
- 0x01, 0x00, 0x00, 0x00, // nop
- 0x01, 0x00, 0x00, 0x00, // nop
- 0x01, 0x00, 0x00, 0x00, // nop
- 0x01, 0x00, 0x00, 0x00, // nop
- 0x01, 0x00, 0x00, 0x00, // nop
+ 0x03, 0x00, 0x00, 0x00, // sethi (. - .PLT0), %g1
+ 0x30, 0x80, 0x00, 0x00, // ba,a .PLT0
0x01, 0x00, 0x00, 0x00 // nop
};
memcpy(buf, pltData, sizeof(pltData));
-
- uint64_t off = pltEntryAddr - ctx.in.plt->getVA();
relocateNoSym(buf, R_SPARC_22, off);
- relocateNoSym(buf + 4, R_SPARC_WDISP19, -(off + 4 - pltEntrySize));
+ relocateNoSym(buf + 4, R_SPARC_WDISP22, -(off + 4));
}
-void elf::setSPARCV9TargetInfo(Ctx &ctx) { ctx.target.reset(new SPARCV9(ctx)); }
+void elf::setSPARCV9TargetInfo(Ctx &ctx) {
+ ctx.target.reset(new SPARC(ctx, /*is64=*/true));
+}
+
+void elf::setSPARC32TargetInfo(Ctx &ctx) {
+ ctx.target.reset(new SPARC(ctx, /*is64=*/false));
+}
diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt
index e22897c2789d8..0e9983d41a59f 100644
--- a/lld/ELF/CMakeLists.txt
+++ b/lld/ELF/CMakeLists.txt
@@ -32,7 +32,7 @@ add_lld_library(lldELF
Arch/PPC.cpp
Arch/PPC64.cpp
Arch/RISCV.cpp
- Arch/SPARCV9.cpp
+ Arch/SPARC.cpp
Arch/SystemZ.cpp
Arch/X86.cpp
Arch/X86_64.cpp
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 4e5a5eb8664d7..f0d47fd5e8706 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -173,6 +173,7 @@ static std::tuple<ELFKind, uint16_t, uint8_t> parseEmulation(Ctx &ctx,
.Cases({"elf_amd64", "elf_x86_64"}, {ELF64LEKind, EM_X86_64})
.Case("elf_i386", {ELF32LEKind, EM_386})
.Case("elf_iamcu", {ELF32LEKind, EM_IAMCU})
+ .Case("elf32_sparc", {ELF32BEKind, EM_SPARC})
.Case("elf64_sparc", {ELF64BEKind, EM_SPARCV9})
.Case("msp430elf", {ELF32LEKind, EM_MSP430})
.Case("elf64_amdgpu", {ELF64LEKind, EM_AMDGPU})
@@ -1345,10 +1346,10 @@ static SmallVector<StringRef, 0> getSymbolOrderingFile(Ctx &ctx,
static bool getIsRela(Ctx &ctx, opt::InputArgList &args) {
// The psABI specifies the default relocation entry format.
- bool rela =
- is_contained({EM_AARCH64, EM_AMDGPU, EM_HEXAGON, EM_LOONGARCH, EM_PPC,
- EM_PPC64, EM_RISCV, EM_S390, EM_SPARCV9, EM_X86_64},
- ctx.arg.emachine);
+ bool rela = is_contained({EM_AARCH64, EM_AMDGPU, EM_HEXAGON, EM_LOONGARCH,
+ EM_PPC, EM_PPC64, EM_RISCV, EM_S390, EM_SPARC,
+ EM_SPARCV9, EM_X86_64},
+ ctx.arg.emachine);
// If -z rel or -z rela is specified, use the last option.
for (auto *arg : args.filtered(OPT_z)) {
StringRef s(arg->getValue());
@@ -2359,6 +2360,13 @@ void LinkerDriver::inferMachineType() {
if (f->ekind == ELFNoneKind)
continue;
if (!inferred) {
+ // EM_SPARC names the 32-bit big-endian ABI, so an object claiming it
+ // with another class or byte order is malformed. A later object is
+ // caught by the ELF kind check in isCompatible.
+ if (f->emachine == EM_SPARC && f->ekind != ELF32BEKind) {
+ Err(ctx) << f.get() << " is incompatible";
+ return;
+ }
inferred = true;
ctx.arg.ekind = f->ekind;
ctx.arg.emachine = f->emachine;
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 4af0898cef607..9a919c4c3d5ef 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1787,6 +1787,9 @@ static uint16_t getBitcodeMachineKind(Ctx &ctx, StringRef path,
case Triple::riscv32:
case Triple::riscv64:
return EM_RISCV;
+ case Triple::sparc:
+ case Triple::sparcel:
+ return EM_SPARC;
case Triple::sparcv9:
return EM_SPARCV9;
case Triple::systemz:
diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index 13baaa5b8c2ad..a088982e179c4 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -795,6 +795,7 @@ static int64_t getTlsTpOffset(Ctx &ctx, const Symbol &s) {
// Variant 2.
case EM_HEXAGON:
case EM_S390:
+ case EM_SPARC:
case EM_SPARCV9:
case EM_386:
case EM_X86_64:
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 0c54fde488292..5b2a7beb22331 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -482,6 +482,7 @@ static std::pair<ELFKind, uint16_t> parseBfdName(StringRef s) {
.Case("elf64-tradlittlemips", {ELF64LEKind, EM_MIPS})
.Case("elf32-littleriscv", {ELF32LEKind, EM_RISCV})
.Case("elf64-littleriscv", {ELF64LEKind, EM_RISCV})
+ .Case("elf32-sparc", {ELF32BEKind, EM_SPARC})
.Case("elf64-sparc", {ELF64BEKind, EM_SPARCV9})
.Case("elf32-msp430", {ELF32LEKind, EM_MSP430})
.Case("elf32-loongarch", {ELF32LEKind, EM_LOONGARCH})
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index f59e676c2e6f5..392d490083cd6 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -1306,6 +1306,7 @@ DynamicSection<ELFT>::computeContents() {
case EM_S390:
addInSec(DT_PLTGOT, *ctx.in.got);
break;
+ case EM_SPARC:
case EM_SPARCV9:
addInSec(DT_PLTGOT, *ctx.in.plt);
break;
@@ -2451,7 +2452,7 @@ PltSection::PltSection(Ctx &ctx)
// The PLT needs to be writable on SPARC as the dynamic linker will
// modify the instructions in the PLT entries.
- if (ctx.arg.emachine == EM_SPARCV9)
+ if (ctx.arg.emachine == EM_SPARC || ctx.arg.emachine == EM_SPARCV9)
this->flags |= SHF_WRITE;
}
diff --git a/lld/ELF/Target.cpp b/lld/ELF/Target.cpp
index 89e4dbeed3109..c362762186d79 100644
--- a/lld/ELF/Target.cpp
+++ b/lld/ELF/Target.cpp
@@ -77,6 +77,8 @@ void elf::setTarget(Ctx &ctx) {
return setPPC64TargetInfo(ctx);
case EM_RISCV:
return setRISCVTargetInfo(ctx);
+ case EM_SPARC:
+ return setSPARC32TargetInfo(ctx);
case EM_SPARCV9:
return setSPARCV9TargetInfo(ctx);
case EM_S390:
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 174e389db155d..ef867fef7cd51 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -221,6 +221,7 @@ void setMipsTargetInfo(Ctx &);
void setPPC64TargetInfo(Ctx &);
void setPPCTargetInfo(Ctx &);
void setRISCVTargetInfo(Ctx &);
+void setSPARC32TargetInfo(Ctx &);
void setSPARCV9TargetInfo(Ctx &);
void setSystemZTargetInfo(Ctx &);
void setX86TargetInfo(Ctx &);
diff --git a/lld/test/ELF/Inputs/sparc-elf64.yaml b/lld/test/ELF/Inputs/sparc-elf64.yaml
new file mode 100644
index 0000000000000..737d58b40eaaf
--- /dev/null
+++ b/lld/test/ELF/Inputs/sparc-elf64.yaml
@@ -0,0 +1,6 @@
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2MSB
+ Type: ET_REL
+ Machine: EM_SPARC
diff --git a/lld/test/ELF/sparc32-plt.s b/lld/test/ELF/sparc32-plt.s
new file mode 100644
index 0000000000000..657716d0d3dc9
--- /dev/null
+++ b/lld/test/ELF/sparc32-plt.s
@@ -0,0 +1,36 @@
+# REQUIRES: sparc
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=sparc a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=sparc b.s -o b.o
+# RUN: ld.lld -shared b.o -soname=b.so -o b.so
+# RUN: ld.lld a.o b.so -o a
+# RUN: llvm-readelf -S -r a | FileCheck %s
+# RUN: llvm-objdump -d -j .plt --no-show-raw-insn a | FileCheck %s --check-prefix=PLT
+
+## As on V9 the loader resolves a PLT slot by writing a jump into the entry, so
+## .plt is writable and the JMP_SLOT relocation names the entry rather than a
+## .got.plt slot. The 32-bit entry is 12 bytes and the reserved header is four
+## of them, so the first entry is at .plt + 0x30.
+# CHECK: .plt PROGBITS [[#%x,PLTADDR:]] {{.*}} WAX
+# CHECK: Relocation section '.rela.plt' {{.*}} contains 1 entries:
+# CHECK: {{0*}}[[#%x,PLTADDR+0x30]] {{.*}} R_SPARC_JMP_SLOT {{.*}} g + 0
+
+## The sethi carries the entry's offset from .PLT0, which is how the resolver
+## recovers the relocation index, and the branch goes back to .PLT0.
+# PLT: <.plt>:
+# PLT: sethi 0x30, %g1
+# PLT-NEXT: ba,a
+# PLT-NEXT: nop
+
+#--- a.s
+.globl _start
+_start:
+ call g
+ nop
+
+#--- b.s
+.globl g
+.type g, at function
+g:
+ retl
+ nop
diff --git a/lld/test/ELF/sparc32-reloc.s b/lld/test/ELF/sparc32-reloc.s
new file mode 100644
index 0000000000000..34a863baa2d0d
--- /dev/null
+++ b/lld/test/ELF/sparc32-reloc.s
@@ -0,0 +1,28 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparc %s -o %t.o
+# RUN: ld.lld -m elf32_sparc -Ttext=0xf0004000 %t.o -o %t
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex %t | FileCheck %s
+# RUN: llvm-readelf -x .data %t | FileCheck %s --check-prefix=DATA
+
+## The relocated value is sign-extended to the ABI width, so on the 32-bit ABI
+## an address with bit 31 set arrives with every high bit set. The unsigned
+## range checks must read it as a 32-bit value rather than an overflow.
+# CHECK: sethi 3932176, %g1
+# CHECK-NEXT: or %g1, 8, %g1
+
+# DATA: 0x{{[0-9a-f]+}} f0004008
+
+## EM_SPARC is the 32-bit big-endian ABI, so an object claiming it with another
+## class or byte order is malformed.
+# RUN: yaml2obj %S/Inputs/sparc-elf64.yaml -o %t64.o
+# RUN: not ld.lld %t64.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=BADCLASS
+
+# BADCLASS: error: {{.*}}64.o is incompatible
+
+.globl _start
+_start:
+ sethi %hi(_start+8), %g1
+ or %g1, %lo(_start+8), %g1
+
+.data
+.word _start + 8
diff --git a/lld/test/ELF/sparc32-tls.s b/lld/test/ELF/sparc32-tls.s
new file mode 100644
index 0000000000000..ffa69cda52fb0
--- /dev/null
+++ b/lld/test/ELF/sparc32-tls.s
@@ -0,0 +1,66 @@
+# REQUIRES: sparc
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=sparc a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=sparc b.s -o b.o
+# RUN: ld.lld -shared b.o -soname=b.so -o b.so
+# RUN: ld.lld -shared a.o b.so -o a.so
+# RUN: llvm-readelf -r a.so | FileCheck %s --check-prefix=GD-REL
+
+## The 32-bit ABI uses the DTPMOD32/DTPOFF32/TPOFF32 GOT slots. a0 is hidden,
+## so its offset is known at link time and only the module index needs a
+## dynamic relocation.
+# GD-REL: Relocation section '.rela.dyn' {{.*}} contains 3 entries:
+# GD-REL: R_SPARC_TLS_DTPMOD32 0{{$}}
+# GD-REL-DAG: R_SPARC_TLS_DTPMOD32 {{.*}} b + 0
+# GD-REL-DAG: R_SPARC_TLS_DTPOFF32 {{.*}} b + 0
+# GD-REL: R_SPARC_JMP_SLOT {{.*}} __tls_get_addr + 0
+
+## a0 is not preemptible in an executable, so its sequence becomes Local Exec.
+## b stays preemptible and becomes Initial Exec, where the 32-bit ABI loads the
+## offset with ld rather than V9's ldx.
+# RUN: ld.lld a.o b.so -o a
+# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=EXE-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a | FileCheck %s --check-prefix=EXE
+
+# EXE-REL: R_SPARC_TLS_TPOFF32 {{.*}} b + 0
+# EXE-REL-NOT: R_SPARC_TLS_DTPMOD32
+
+## a0 is the only symbol in the TLS block, so a0 - tp = -4.
+# EXE-LABEL: <_start>:
+# EXE-NEXT: sethi 0, %o0
+# EXE-NEXT: xor %o0, -4, %o0
+# EXE-NEXT: add %g7, %o0, %o0
+# EXE-NEXT: nop
+# EXE-NEXT: nop
+# EXE-NEXT: sethi 0, %o1
+# EXE-NEXT: add %o1, 4, %o1
+# EXE-NEXT: ld [%l7+%o1], %o0
+# EXE-NEXT: add %g7, %o0, %o0
+# EXE-NEXT: nop
+
+#--- a.s
+.globl _start
+_start:
+ sethi %tgd_hi22(a0), %o0
+ add %o0, %tgd_lo10(a0), %o0
+ add %l7, %o0, %o0, %tgd_add(a0)
+ call __tls_get_addr, %tgd_call(a0)
+ nop
+
+ sethi %tgd_hi22(b), %o1
+ add %o1, %tgd_lo10(b), %o1
+ add %l7, %o1, %o0, %tgd_add(b)
+ call __tls_get_addr, %tgd_call(b)
+ nop
+
+.section .tbss,"awT", at nobits
+.globl a0
+.hidden a0
+a0:
+ .word 0
+
+#--- b.s
+.section .tbss,"awT", at nobits
+.globl b
+b:
+ .word 0
>From 851d2d9c2fbdef567756e863dadcb50121928b74 Mon Sep 17 00:00:00 2001
From: Imre Kaloz <kaloz at kernel.org>
Date: Wed, 2 Sep 2026 02:49:03 +0200
Subject: [PATCH 3/3] [ELF,SPARC] Accept EM_SPARC32PLUS inputs on the 32-bit
SPARC target
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
EM_SPARC32PLUS marks an object holding V9 instructions under the
32-bit ABI, so it links with plain EM_SPARC objects and needs no
relocation handling of its own. Rejecting it outright also rejected
the 32-bit libgcc and libatomic distributions ship.
Take it as input, and OR the widest instruction-set bits any
EM_SPARC32PLUS object asks for into the output, as elf32_sparc does:
EF_SPARC_32PLUS, then SUN_US1, then SUN_US3 (implies US1); HAL_R1
selects nothing. gas sets SUN_US1 per UltraSPARC I instruction, not
per -mcpu, so a static link needs the bits OR'd in from such a
library.
The machine is normalized to EM_SPARC during the link, promoted on
write. EM_SPARCV9 and elf32_sparc's other malformed shapes — wrong
class/byte order, or missing EF_SPARC_32PLUS — are rejected.
Signed-off-by: Imre Kaloz <kaloz at kernel.org>
---
lld/ELF/Arch/SPARC.cpp | 40 +++++++++++
lld/ELF/Driver.cpp | 13 ++--
lld/ELF/InputFiles.cpp | 8 ++-
lld/ELF/SyntheticSections.cpp | 4 ++
lld/test/ELF/sparc32-v8plus.s | 132 ++++++++++++++++++++++++++++++++++
5 files changed, 191 insertions(+), 6 deletions(-)
create mode 100644 lld/test/ELF/sparc32-v8plus.s
diff --git a/lld/ELF/Arch/SPARC.cpp b/lld/ELF/Arch/SPARC.cpp
index 8270265d9a4d9..0d33a4b99b06b 100644
--- a/lld/ELF/Arch/SPARC.cpp
+++ b/lld/ELF/Arch/SPARC.cpp
@@ -31,6 +31,7 @@ class SPARC final : public TargetInfo {
SPARC(Ctx &, bool is64);
RelExpr getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const override;
+ uint32_t calcEFlags() const override;
RelType getDynRel(RelType type) const override;
int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;
void writeGotHeader(uint8_t *buf) const override;
@@ -108,6 +109,45 @@ RelExpr SPARC::getRelExpr(RelType type, const Symbol &s,
}
}
+uint32_t SPARC::calcEFlags() const {
+ if (is64)
+ return 0;
+
+ // EF_SPARC_32PLUS and the Sun extension bits name nested instruction sets, so
+ // GNU ld's elf32_sparc gives the output the widest one any object file asks
+ // for, whatever the link order. EF_SPARC_SUN_US3 implies EF_SPARC_SUN_US1.
+ // Only the bits of an EM_SPARC32PLUS object count, only object files count,
+ // and EF_SPARC_HAL_R1 selects nothing.
+ enum { Base, V8Plus, UltraSPARC1, UltraSPARC3 } level = Base;
+ for (InputFile *f : ctx.objectFiles) {
+ if (f->emachine != EM_SPARC32PLUS)
+ continue;
+ uint32_t eflags = cast<ObjFile<ELF32BE>>(f)->getObj().getHeader().e_flags;
+ if (!(eflags & EF_SPARC_32PLUS)) {
+ Err(ctx) << f << ": EM_SPARC32PLUS object without EF_SPARC_32PLUS";
+ continue;
+ }
+ if (eflags & EF_SPARC_SUN_US3)
+ level = std::max(level, UltraSPARC3);
+ else if (eflags & EF_SPARC_SUN_US1)
+ level = std::max(level, UltraSPARC1);
+ else
+ level = std::max(level, V8Plus);
+ }
+
+ switch (level) {
+ case Base:
+ return 0;
+ case V8Plus:
+ return EF_SPARC_32PLUS;
+ case UltraSPARC1:
+ return EF_SPARC_32PLUS | EF_SPARC_SUN_US1;
+ case UltraSPARC3:
+ return EF_SPARC_32PLUS | EF_SPARC_SUN_US1 | EF_SPARC_SUN_US3;
+ }
+ llvm_unreachable("invalid level");
+}
+
RelType SPARC::getDynRel(RelType type) const {
if (type == symbolicRel)
return type;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index f0d47fd5e8706..c8489bec86eee 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2360,16 +2360,19 @@ void LinkerDriver::inferMachineType() {
if (f->ekind == ELFNoneKind)
continue;
if (!inferred) {
- // EM_SPARC names the 32-bit big-endian ABI, so an object claiming it
- // with another class or byte order is malformed. A later object is
- // caught by the ELF kind check in isCompatible.
- if (f->emachine == EM_SPARC && f->ekind != ELF32BEKind) {
+ // EM_SPARC and EM_SPARC32PLUS name the 32-bit big-endian ABI, so an
+ // object claiming either with another class or byte order is malformed.
+ // A later object is caught by the ELF kind check in isCompatible.
+ if ((f->emachine == EM_SPARC || f->emachine == EM_SPARC32PLUS) &&
+ f->ekind != ELF32BEKind) {
Err(ctx) << f.get() << " is incompatible";
return;
}
inferred = true;
ctx.arg.ekind = f->ekind;
- ctx.arg.emachine = f->emachine;
+ // EM_SPARC32PLUS is the 32-bit SPARC ABI, and is promoted back on
+ // output when an input needs it.
+ ctx.arg.emachine = f->emachine == EM_SPARC32PLUS ? EM_SPARC : f->emachine;
ctx.arg.mipsN32Abi = ctx.arg.emachine == EM_MIPS && isMipsN32Abi(ctx, *f);
}
ctx.arg.osabi = f->osabi;
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 9a919c4c3d5ef..49a2ea2a916da 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -267,7 +267,13 @@ static bool isCompatible(Ctx &ctx, InputFile *file) {
if (!file->isElf() && !isa<BitcodeFile>(file))
return true;
- if (file->ekind == ctx.arg.ekind && file->emachine == ctx.arg.emachine) {
+ // An EM_SPARC32PLUS object holds V9 instructions in the 32-bit SPARC ABI, so
+ // it links with plain EM_SPARC objects. The output machine is EM_SPARC until
+ // the header is written.
+ uint16_t emachine =
+ file->emachine == EM_SPARC32PLUS ? EM_SPARC : file->emachine;
+
+ if (file->ekind == ctx.arg.ekind && emachine == ctx.arg.emachine) {
if (ctx.arg.emachine != EM_MIPS)
return true;
if (isMipsN32Abi(ctx, *file) == ctx.arg.mipsN32Abi)
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 392d490083cd6..70e7f9f4cb501 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4271,6 +4271,10 @@ template <typename ELFT> void elf::writeEhdr(Ctx &ctx, uint8_t *buf) {
eHdr->e_ident[EI_OSABI] = ctx.arg.osabi;
eHdr->e_ident[EI_ABIVERSION] = getAbiVersion(ctx);
eHdr->e_machine = ctx.arg.emachine;
+ // A 32-bit SPARC link whose objects need V9 instructions is tagged
+ // EM_SPARC32PLUS, which goes with EF_SPARC_32PLUS.
+ if (ctx.arg.emachine == EM_SPARC && (ctx.arg.eflags & EF_SPARC_32PLUS))
+ eHdr->e_machine = EM_SPARC32PLUS;
eHdr->e_version = EV_CURRENT;
eHdr->e_flags = ctx.arg.eflags;
eHdr->e_ehsize = sizeof(typename ELFT::Ehdr);
diff --git a/lld/test/ELF/sparc32-v8plus.s b/lld/test/ELF/sparc32-v8plus.s
new file mode 100644
index 0000000000000..9087433c8284b
--- /dev/null
+++ b/lld/test/ELF/sparc32-v8plus.s
@@ -0,0 +1,132 @@
+# REQUIRES: sparc
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=sparc a.s -o v8.o
+# RUN: llvm-mc -filetype=obj -triple=sparc -mattr=+v8plus b.s -o v8plus.o
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 b.s -o v9.o
+# RUN: yaml2obj us1.yaml -o us1.o
+# RUN: yaml2obj us3.yaml -o us3.o
+# RUN: yaml2obj hal.yaml -o hal.o
+
+## An EM_SPARC32PLUS object holds V9 instructions in the 32-bit ABI, so it links
+## with plain EM_SPARC objects. A link needing them is tagged EM_SPARC32PLUS
+## with EF_SPARC_32PLUS, whatever the order the objects appear in.
+# RUN: ld.lld -m elf32_sparc v8.o v8plus.o -o mixed1
+# RUN: llvm-readelf -h mixed1 | FileCheck %s --check-prefix=PLUS
+# RUN: ld.lld -m elf32_sparc v8plus.o v8.o -o mixed2
+# RUN: llvm-readelf -h mixed2 | FileCheck %s --check-prefix=PLUS
+# RUN: ld.lld -m elf32_sparc v8plus.o -o pureplus
+# RUN: llvm-readelf -h pureplus | FileCheck %s --check-prefix=PLUS
+
+# PLUS: Machine: Sparc v8+
+# PLUS: Flags: 0x100, V8+ ABI{{$}}
+
+## A link whose objects are all plain V8 keeps the base machine and no flags.
+# RUN: ld.lld -m elf32_sparc v8.o -o purev8
+# RUN: llvm-readelf -h purev8 | FileCheck %s --check-prefix=V8
+
+# V8: Machine: Sparc{{$}}
+# V8: Flags: 0x0{{$}}
+
+## The extension bits name nested instruction sets, so the output takes the
+## widest one any object asks for. EF_SPARC_SUN_US3 implies EF_SPARC_SUN_US1.
+# RUN: ld.lld -m elf32_sparc v8.o us1.o -o us1a
+# RUN: ld.lld -m elf32_sparc us1.o v8plus.o -o us1b
+# RUN: llvm-readelf -h us1a us1b | FileCheck %s --check-prefix=US1
+
+# US1: Machine: Sparc v8+
+# US1: Flags: 0x300, V8+ ABI, Sun UltraSPARC I extensions{{$}}
+# US1: Machine: Sparc v8+
+# US1: Flags: 0x300, V8+ ABI, Sun UltraSPARC I extensions{{$}}
+
+# RUN: ld.lld -m elf32_sparc us1.o us3.o -o us3a
+# RUN: ld.lld -m elf32_sparc us3.o us1.o -o us3b
+# RUN: llvm-readelf -h us3a us3b | FileCheck %s --check-prefix=US3
+
+# US3: Flags: 0xb00, V8+ ABI, Sun UltraSPARC I extensions, Sun UltraSPARC III extensions{{$}}
+# US3: Flags: 0xb00, V8+ ABI, Sun UltraSPARC I extensions, Sun UltraSPARC III extensions{{$}}
+
+## EF_SPARC_HAL_R1 names no instruction set here, so it selects nothing and is
+## not written out.
+# RUN: ld.lld -m elf32_sparc v8.o hal.o -o hala
+# RUN: llvm-readelf -h hala | FileCheck %s --check-prefix=PLUS
+
+## EM_SPARCV9 is the 64-bit ABI and does not belong in a 32-bit link.
+# RUN: not ld.lld -m elf32_sparc v8.o v9.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
+
+# ERR: error: v9.o is incompatible with elf32_sparc
+
+## EM_SPARC32PLUS names the 32-bit big-endian ABI, so another class or byte
+## order is malformed, as is leaving out the instruction set flag.
+# RUN: yaml2obj be64.yaml -o be64.o
+# RUN: yaml2obj le32.yaml -o le32.o
+# RUN: yaml2obj bare.yaml -o bare.o
+# RUN: not ld.lld be64.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=BE64
+# RUN: not ld.lld le32.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=LE32
+# RUN: not ld.lld bare.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=BARE
+
+# BE64: error: be64.o is incompatible
+# LE32: error: le32.o is incompatible
+# BARE: error: bare.o: EM_SPARC32PLUS object without EF_SPARC_32PLUS
+
+#--- a.s
+.globl _start
+_start:
+ nop
+
+#--- b.s
+.globl g
+g:
+ nop
+
+#--- us1.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS32
+ Data: ELFDATA2MSB
+ Type: ET_REL
+ Machine: EM_SPARC32PLUS
+ Flags: [ EF_SPARC_32PLUS, EF_SPARC_SUN_US1 ]
+
+#--- us3.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS32
+ Data: ELFDATA2MSB
+ Type: ET_REL
+ Machine: EM_SPARC32PLUS
+ Flags: [ EF_SPARC_32PLUS, EF_SPARC_SUN_US3 ]
+
+#--- hal.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS32
+ Data: ELFDATA2MSB
+ Type: ET_REL
+ Machine: EM_SPARC32PLUS
+ Flags: [ EF_SPARC_32PLUS, EF_SPARC_HAL_R1 ]
+
+#--- be64.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2MSB
+ Type: ET_REL
+ Machine: EM_SPARC32PLUS
+ Flags: [ EF_SPARC_32PLUS, EF_SPARC_SUN_US1 ]
+
+#--- le32.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS32
+ Data: ELFDATA2LSB
+ Type: ET_REL
+ Machine: EM_SPARC32PLUS
+ Flags: [ EF_SPARC_32PLUS, EF_SPARC_SUN_US1 ]
+
+#--- bare.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS32
+ Data: ELFDATA2MSB
+ Type: ET_REL
+ Machine: EM_SPARC32PLUS
More information about the llvm-commits
mailing list