[lld] [llvm] Add initial support for Xtensa to LLD (PR #202926)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 10 03:50:15 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lld

Author: Alexey Gerenkov (gerekon)

<details>
<summary>Changes</summary>



---
Full diff: https://github.com/llvm/llvm-project/pull/202926.diff


9 Files Affected:

- (added) lld/ELF/Arch/Xtensa.cpp (+212) 
- (modified) lld/ELF/CMakeLists.txt (+1) 
- (modified) lld/ELF/InputFiles.cpp (+2) 
- (modified) lld/ELF/Target.cpp (+2) 
- (modified) lld/ELF/Target.h (+2) 
- (modified) lld/ELF/Writer.cpp (+57) 
- (added) lld/test/ELF/xtensa-reloc.s (+63) 
- (modified) lld/test/lit.cfg.py (+1) 
- (modified) llvm/include/llvm/BinaryFormat/ELFRelocs/Xtensa.def (+6) 


``````````diff
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/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;
 
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)

``````````

</details>


https://github.com/llvm/llvm-project/pull/202926


More information about the llvm-commits mailing list