[lld] [llvm] Add initial support for Xtensa to LLD (PR #202926)
Alexey Gerenkov via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 5 08:00:41 PDT 2026
https://github.com/gerekon updated https://github.com/llvm/llvm-project/pull/202926
>From 9838a720d22757645d4d71174aeac16806fc8f5c 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] [lld][Xtensa] Add LLD linker support
---
lld/ELF/Arch/Xtensa.cpp | 213 ++++++++++++++++++
lld/ELF/CMakeLists.txt | 1 +
lld/ELF/InputFiles.cpp | 2 +
lld/ELF/Target.cpp | 2 +
lld/ELF/Target.h | 1 +
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..16164ac7d5f34
--- /dev/null
+++ b/lld/ELF/Arch/Xtensa.cpp
@@ -0,0 +1,213 @@
+//===- 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..1d264a2b8a6a9 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -217,6 +217,7 @@ void setSPARCV9TargetInfo(Ctx &);
void setSystemZTargetInfo(Ctx &);
void setX86TargetInfo(Ctx &);
void setX86_64TargetInfo(Ctx &);
+void setXtensaTargetInfo(Ctx &);
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..c560f9957b5d2 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)
More information about the llvm-commits
mailing list