[lld] [llvm] Add initial support for Xtensa to LLD (PR #202926)
Alexey Gerenkov via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 10 03:49:35 PDT 2026
https://github.com/gerekon created https://github.com/llvm/llvm-project/pull/202926
None
>From bbce89f5ba64ccbe8195d806f9d3fc105222e309 Mon Sep 17 00:00:00 2001
From: Andrei Safronov <safronov at espressif.com>
Date: Wed, 17 Sep 2025 19:06:51 +0300
Subject: [PATCH 1/2] [lld][Xtensa] Add LLD linker support
---
lld/ELF/Arch/Xtensa.cpp | 212 ++++++++++++++++++
lld/ELF/CMakeLists.txt | 1 +
lld/ELF/InputFiles.cpp | 2 +
lld/ELF/Target.cpp | 2 +
lld/ELF/Target.h | 2 +
lld/test/ELF/xtensa-reloc.s | 63 ++++++
lld/test/lit.cfg.py | 1 +
.../llvm/BinaryFormat/ELFRelocs/Xtensa.def | 6 +
8 files changed, 289 insertions(+)
create mode 100644 lld/ELF/Arch/Xtensa.cpp
create mode 100644 lld/test/ELF/xtensa-reloc.s
diff --git a/lld/ELF/Arch/Xtensa.cpp b/lld/ELF/Arch/Xtensa.cpp
new file mode 100644
index 0000000000000..b763ae7bbd56b
--- /dev/null
+++ b/lld/ELF/Arch/Xtensa.cpp
@@ -0,0 +1,212 @@
+//===- Xtensa.cpp ---------------------------------------------------------===//
+//
+// 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 "InputFiles.h"
+#include "Symbols.h"
+#include "Target.h"
+#include <bitset>
+#include <iostream>
+#include <string>
+
+using namespace llvm;
+using namespace llvm::object;
+using namespace llvm::support::endian;
+using namespace llvm::ELF;
+using namespace lld;
+using namespace lld::elf;
+
+namespace {
+
+class Xtensa final : public TargetInfo {
+public:
+ Xtensa(Ctx &ctx);
+ RelExpr getRelExpr(RelType type, const Symbol &s,
+ const uint8_t *loc) const override;
+ void relocate(uint8_t *loc, const Relocation &rel,
+ uint64_t val) const override;
+};
+
+} // namespace
+
+Xtensa::Xtensa(Ctx &ctx) : TargetInfo(ctx) {}
+
+RelExpr Xtensa::getRelExpr(RelType type, const Symbol &s,
+ const uint8_t *loc) const {
+ switch (type) {
+ case R_XTENSA_32:
+ return R_ABS;
+ case R_XTENSA_SLOT0_OP:
+ // This relocation is used for various instructions, with varying ways to
+ // calculate the relocation value. This is unlike most ELF architectures,
+ // and is arguably bad design (see the comment on R_386_GOT32 in X86.cpp).
+ // But that's what compilers emit, so it needs to be supported.
+ //
+ // We work around this by returning R_PC here and calculating the PC address
+ // in Xtensa::relocate based on the relative value. That's ugly. A better
+ // solution would be to look at the instruction here and emit various
+ // Xtensa-specific RelTypes, but that has another problem: the RelExpr enum
+ // is at its maximum size of 64. This will need to be fixed eventually, but
+ // for now hack around it and return R_PC.
+ return R_PC;
+ case R_XTENSA_ASM_EXPAND:
+ // This relocation appears to be emitted by the GNU Xtensa compiler as a
+ // linker relaxation hint. For example, for the following code:
+ //
+ // .section .foo
+ // .align 4
+ // foo:
+ // nop
+ // nop
+ // call0 bar
+ // .align 4
+ // bar:
+ //
+ // The call0 instruction is compiled to a l32r and callx0 instruction.
+ // The LLVM Xtensa backend does not emit this relocation.
+ // Because it's a relaxation hint, this relocation can be ignored for now
+ // until linker relaxations are implemented.
+ return R_NONE;
+ case R_XTENSA_DIFF8:
+ case R_XTENSA_DIFF16:
+ case R_XTENSA_DIFF32:
+ case R_XTENSA_PDIFF8:
+ case R_XTENSA_PDIFF16:
+ case R_XTENSA_PDIFF32:
+ case R_XTENSA_NDIFF8:
+ case R_XTENSA_NDIFF16:
+ case R_XTENSA_NDIFF32:
+ // > Xtensa relocations to mark the difference of two local symbols.
+ // > These are only needed to support linker relaxation and can be ignored
+ // > when not relaxing.
+ // Source:
+ // https://github.com/espressif/binutils-gdb/commit/30ce8e47fad9b057b6d7af9e1d43061126d34d20:
+ // Because we don't do linker relaxation, we can ignore these relocations.
+ return R_NONE;
+ default:
+ Err(ctx) << getErrorLoc(ctx, loc) << "unknown relocation (" << type.v
+ << ") against symbol " << &s;
+ return R_NONE;
+ }
+}
+
+static inline bool isRRI8Branch(uint8_t *loc) {
+ // instructions: ball, bany, bbc, bbci, bbs, bbsi, beq, bge, bgeu, blt,
+ // bltu, bnall, bne, bnone
+ if ((loc[0] & 0x0f) == 0b0111)
+ return true;
+ // instructions: beqi, bgei, bnei, blti
+ if ((loc[0] & 0b11'1111) == 0b10'0110)
+ return true;
+ // instructions: bgeui, bltui
+ if ((loc[0] & 0b1011'1111) == 0b1011'0110)
+ return true;
+ if ((loc[0] & 0b0111'1111) == 0b0111'0110) {
+ // instruction: bf
+ if ((loc[1] & 0b1111'0000) == 0b0000'0000)
+ return true;
+ // instruction: bt
+ if ((loc[1] & 0b1111'0000) == 0b0001'0000)
+ return true;
+ }
+ // some other instruction
+ return false;
+}
+
+static inline bool isLoop(uint8_t *loc) {
+ // instructions: loop, loopgtz, loopnez
+ if ((loc[0] & 0b1111'1111) == 0b0111'0110) {
+ // instruction: loop
+ if ((loc[1] & 0b1111'0000) == 0b1000'0000)
+ return true;
+ // instruction: loopgtz
+ if ((loc[1] & 0b1111'0000) == 0b1010'0000)
+ return true;
+ // instruction: loopnez
+ if ((loc[1] & 0b1111'0000) == 0b1001'0000)
+ return true;
+ }
+ // some other instruction
+ return false;
+}
+
+void Xtensa::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
+ switch (rel.type) {
+ case R_XTENSA_32:
+ // R_XTENSA_32 is a partial_inplace relocation (GNU bfd howto:
+ // src_mask = dst_mask = 0xffffffff). The GNU Xtensa assembler stores the
+ // addend in the relocated word itself and leaves the RELA r_addend as 0,
+ // so the result must be S + r_addend + <existing contents>. Objects from
+ // the LLVM Xtensa backend keep the addend in r_addend and zero the field,
+ // so reading the contents back adds 0 and is harmless. Plain
+ // write32le(loc, val) drops the in-place addend, which miscomputes every
+ // R_XTENSA_32 with a nonzero addend -- e.g. the switch jump tables in the
+ // precompiled Espressif Wi-Fi blobs, which then point at a section base
+ // instead of the intended in-section offset.
+ write32le(loc, val + read32le(loc));
+ break;
+ case R_XTENSA_SLOT0_OP: {
+ // HACK: calculate the instruction location based on the PC-relative
+ // relocation value.
+ uint64_t dest = rel.sym->getVA(ctx, rel.addend);
+ uint64_t p = dest - val;
+
+ // This relocation is used for various instructions.
+ // Look at the instruction to determine how to do the relocation.
+ uint8_t opcode = loc[0] & 0x0f;
+ if (opcode == 0b0001) { // RI16 format: l32r
+ int64_t val = dest - ((p + 3) & (uint64_t)0xfffffffc);
+ if ((val < -262144 || val > -4))
+ reportRangeError(ctx, loc, rel, Twine(static_cast<int64_t>(val)), -262141,
+ -4);
+ checkAlignment(ctx, loc, static_cast<uint64_t>(val), 4, rel);
+ write16le(loc + 1, val >> 2);
+ } else if (opcode == 0b0101) { // call0, call4, call8, call12 (CALL format)
+ uint64_t val = dest - ((p + 4) & (uint64_t)0xfffffffc);
+ checkInt(ctx, loc, static_cast<int64_t>(val) >> 2, 18, rel);
+ checkAlignment(ctx, loc, val, 4, rel);
+ const int64_t target = static_cast<int64_t>(val) >> 2;
+ loc[0] = (loc[0] & 0b0011'1111) | ((target & 0b0000'0011) << 6);
+ loc[1] = target >> 2;
+ loc[2] = target >> 10;
+ } else if ((loc[0] & 0x3f) == 0b00'0110) { // j (CALL format)
+ uint64_t valJ = val - 4;
+ checkInt(ctx, loc, static_cast<int64_t>(valJ), 18, rel);
+ loc[0] = (loc[0] & 0b0011'1111) | ((valJ & 0b0000'0011) << 6);
+ loc[1] = valJ >> 2;
+ loc[2] = valJ >> 10;
+ } else if (isRRI8Branch(loc)) { // RRI8 format (various branch instructions)
+ uint64_t v = val - 4;
+ checkInt(ctx, loc, static_cast<int64_t>(v), 8, rel);
+ loc[2] = v & 0xff;
+ } else if (isLoop(loc)) { // loop instructions
+ uint64_t v = val - 4;
+ checkUInt(ctx, loc, v, 8, rel);
+ loc[2] = v & 0xff;
+ } else if ((loc[0] & 0b1000'1111) == 0b1000'1100) { // RI16 format: beqz.n, bnez.n
+ uint64_t v = val - 4;
+ checkUInt(ctx, loc, v, 6, rel);
+ loc[0] = (loc[0] & 0xcf) | (v & 0x30);
+ loc[1] = (loc[1] & 0x0f) | ((v & 0x0f) << 4);
+ } else if ((loc[0] & 0b0011'1111) == 0b0001'0110) { // BRI12 format: beqz, bgez, bltz, bnez
+ uint64_t v = val - 4;
+ checkInt(ctx, loc, static_cast<int64_t>(v), 12, rel);
+ loc[1] = ((loc[1] & 0x0f)) | ((v & 0x0f) << 4);
+ loc[2] = (v >> 4) & 0xff;
+ } else {
+ Err(ctx) << getErrorLoc(ctx, loc) <<
+ "unknown opcode for relocation: " << loc[0];
+ }
+ break;
+ }
+ default:
+ llvm_unreachable("unknown relocation");
+ }
+}
+
+void elf::setXtensaTargetInfo(Ctx &ctx) { ctx.target.reset(new Xtensa(ctx)); }
+
diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt
index e22897c2789d8..29eb121b1c87b 100644
--- a/lld/ELF/CMakeLists.txt
+++ b/lld/ELF/CMakeLists.txt
@@ -36,6 +36,7 @@ add_lld_library(lldELF
Arch/SystemZ.cpp
Arch/X86.cpp
Arch/X86_64.cpp
+ Arch/Xtensa.cpp
ARMErrataFix.cpp
BPSectionOrderer.cpp
CallGraphSort.cpp
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 2f544f0fe0958..77299bc38bb06 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1783,6 +1783,8 @@ static uint16_t getBitcodeMachineKind(Ctx &ctx, StringRef path,
return t.isOSIAMCU() ? EM_IAMCU : EM_386;
case Triple::x86_64:
return EM_X86_64;
+ case Triple::xtensa:
+ return EM_XTENSA;
default:
ErrAlways(ctx) << path
<< ": could not infer e_machine from bitcode target triple "
diff --git a/lld/ELF/Target.cpp b/lld/ELF/Target.cpp
index 89e4dbeed3109..89cc9357cac1b 100644
--- a/lld/ELF/Target.cpp
+++ b/lld/ELF/Target.cpp
@@ -83,6 +83,8 @@ void elf::setTarget(Ctx &ctx) {
return setSystemZTargetInfo(ctx);
case EM_X86_64:
return setX86_64TargetInfo(ctx);
+ case EM_XTENSA:
+ return setXtensaTargetInfo(ctx);
default:
Fatal(ctx) << "unsupported e_machine value: " << ctx.arg.emachine;
}
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index c9b2af07149e0..e5c0e4a17c259 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -217,6 +217,8 @@ void setSPARCV9TargetInfo(Ctx &);
void setSystemZTargetInfo(Ctx &);
void setX86TargetInfo(Ctx &);
void setX86_64TargetInfo(Ctx &);
+void setXtensaTargetInfo(Ctx &);
+template <class ELFT> TargetInfo *getMipsTargetInfo();
struct ErrorPlace {
InputSectionBase *isec;
diff --git a/lld/test/ELF/xtensa-reloc.s b/lld/test/ELF/xtensa-reloc.s
new file mode 100644
index 0000000000000..cf0312166ee61
--- /dev/null
+++ b/lld/test/ELF/xtensa-reloc.s
@@ -0,0 +1,63 @@
+# REQUIRES: xtensa
+# RUN: llvm-mc -filetype=obj -triple=xtensa -mcpu=esp32 %s -o %t.o
+# RUN: ld.lld %t.o --defsym=a=0x2000 --section-start=.CALL=0x1000 --defsym=b=0x40 --defsym=c=0x140 --section-start=.BRANCH=0x5000 --defsym=d=0x5010 --section-start=.BR12=0x100 --image-base=0x0 -o %t
+# RUN: llvm-objdump -d --mcpu=esp32 --print-imm-hex %t | FileCheck %s
+
+.section .BR12,"ax", at progbits
+ .globl _start
+ .balign 0x100
+ .type _start,%function
+_start:
+# CHECK-LABEL:section .BR12
+# CHECK: beqz a2, 0x140
+# CHECK-NEXT: bnez a3, 0x140
+# CHECK-NEXT: bgez a4, 0x140
+# CHECK-NEXT: bltz a5, 0x140
+ beqz a2, c
+ bnez a3, c
+ bgez a4, c
+ bltz a5, c
+
+.section .CALL,"ax", at progbits
+# CHECK-LABEL: section .CALL:
+# CHECK: call0 0x2000
+# CHECK-NEXT: call0 0x2000
+# CHECK-NEXT: call0 0x2000
+# CHECK-NEXT: call0 0x2000
+# CHECK-NEXT: j 0x2000
+# CHECK-NEXT: j 0x2000
+# CHECK-NEXT: j 0x2000
+# CHECK-NEXT: j 0x40
+# CHECK-NEXT: j 0x140
+# CHECK-NEXT: call0 0x40
+# CHECK-NEXT: call0 0x140
+# CHECK-NEXT: l32r a3, 0x40
+# CHECK-NEXT: callx0 a3
+# CHECK-NEXT: l32r a4, 0x140
+# CHECK-NEXT: callx0 a4
+ call0 a
+ call0 a
+ call0 a
+ call0 a
+ j a
+ j a
+ j a
+ j b
+ j c
+ call0 b
+ call0 c
+ l32r a3, b
+ callx0 a3
+ l32r a4, c
+ callx0 a4
+
+.section .BRANCH,"ax", at progbits
+# CHECK-LABEL: section .BRANCH:
+# CHECK: beq a3, a4, 0x5010
+# CHECK-NEXT: ball a3, a4, 0x5010
+# CHECK-NEXT: blt a3, a4, 0x5010
+# CHECK-NEXT: bt b0, 0x5010
+ beq a3, a4, d
+ ball a3, a4, d
+ blt a3, a4, d
+ bt b0, d
diff --git a/lld/test/lit.cfg.py b/lld/test/lit.cfg.py
index 39c3d0aa36bfb..ff4236a3d6762 100644
--- a/lld/test/lit.cfg.py
+++ b/lld/test/lit.cfg.py
@@ -99,6 +99,7 @@
"SystemZ": "systemz",
"WebAssembly": "wasm",
"X86": "x86",
+ 'Xtensa': 'xtensa',
},
),
("--assertion-mode", {"ON": "asserts"}),
diff --git a/llvm/include/llvm/BinaryFormat/ELFRelocs/Xtensa.def b/llvm/include/llvm/BinaryFormat/ELFRelocs/Xtensa.def
index 6791a842181ff..c2e11259164bb 100644
--- a/llvm/include/llvm/BinaryFormat/ELFRelocs/Xtensa.def
+++ b/llvm/include/llvm/BinaryFormat/ELFRelocs/Xtensa.def
@@ -58,3 +58,9 @@ ELF_RELOC(R_XTENSA_TLS_TPOFF, 53)
ELF_RELOC(R_XTENSA_TLS_FUNC, 54)
ELF_RELOC(R_XTENSA_TLS_ARG, 55)
ELF_RELOC(R_XTENSA_TLS_CALL, 56)
+ELF_RELOC(R_XTENSA_PDIFF8, 57)
+ELF_RELOC(R_XTENSA_PDIFF16, 58)
+ELF_RELOC(R_XTENSA_PDIFF32, 59)
+ELF_RELOC(R_XTENSA_NDIFF8, 60)
+ELF_RELOC(R_XTENSA_NDIFF16, 61)
+ELF_RELOC(R_XTENSA_NDIFF32, 62)
>From 93b6a083f026a57128ef5dc7a75c3a97361f98e3 Mon Sep 17 00:00:00 2001
From: Andrei Safronov <safronov at espressif.com>
Date: Thu, 12 Oct 2023 01:53:02 +0300
Subject: [PATCH 2/2] [lld][Xtensa] Improve literal sections placement.
---
lld/ELF/Writer.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index c2d05c75df39d..9313e9527749c 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -33,6 +33,7 @@
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/xxhash.h"
#include <climits>
+#include <map>
#define DEBUG_TYPE "lld"
@@ -1216,6 +1217,58 @@ sortISDBySectionOrder(Ctx &ctx, InputSectionDescription *isd,
isd->sections.push_back(isec);
}
+// Sort Xtensa literal sections in OutputSection. For each literal section we try
+// to find by name text(code) section, which uses these literals. The literal
+// section should always be placed before code section.
+// Also we try to place literal section just before code section.
+static void sortSectionXtensa(OutputSection &osec) {
+ for (SectionCommand *b : osec.commands) {
+ if (auto *isd = dyn_cast<InputSectionDescription>(b)) {
+ std::map<std::string, int> orderedNames;
+ SmallVector<std::pair<InputSection *, int>, 0> orderedSections;
+ int sidx = 0;
+
+ for (InputSection *isec : isd->sections) {
+ if (orderedNames.count(isec->name.str())) {
+ orderedSections.push_back({isec, orderedNames[isec->name.str()]});
+ continue;
+ }
+ //Check if current section contains literals
+ if (isec->name.contains(".literal")) {
+ std::string literalName = isec->name.str();
+ std::size_t pos = literalName.find(".literal");
+ std::string textName;
+ // Reconstructing text(code) section name by literal section name
+ if (pos == 0) {
+ textName = ".text";
+ } else {
+ textName = literalName.substr(0, pos);
+ }
+ textName += literalName.substr(pos + 8, std::string::npos);
+ if (orderedNames.count(textName)) {
+ int textIdx = orderedNames[textName];
+ int literalIdx = textIdx - 1;
+ orderedSections.push_back({isec, literalIdx});
+ orderedNames[isec->name.str()] = literalIdx;
+ } else {
+ orderedSections.push_back({isec, sidx});
+ orderedNames[isec->name.str()] = sidx;
+ }
+ } else {
+ orderedSections.push_back({isec, sidx});
+ orderedNames[isec->name.str()] = sidx;
+ }
+ sidx += 2;
+ }
+
+ llvm::sort(orderedSections, llvm::less_second());
+ isd->sections.clear();
+ for (std::pair<InputSection *, int> p : orderedSections)
+ isd->sections.push_back(p.first);
+ }
+ }
+}
+
static void sortSection(Ctx &ctx, OutputSection &osec,
const DenseMap<const InputSectionBase *, int> &order) {
StringRef name = osec.name;
@@ -1233,6 +1286,10 @@ static void sortSection(Ctx &ctx, OutputSection &osec,
if (auto *isd = dyn_cast<InputSectionDescription>(b))
sortISDBySectionOrder(ctx, isd, order, osec.flags & SHF_EXECINSTR);
+ if (ctx.arg.emachine == EM_XTENSA) {
+ sortSectionXtensa(osec);
+ }
+
if (ctx.script->hasSectionsCommand)
return;
More information about the llvm-commits
mailing list