[lld] [ELF] Add target-specific relocation scanning for RISC-V (PR #181332)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Wed Mar 4 01:29:35 PST 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/181332

>From 5a3f8ba98f223e452656d8e0b089dcf652d0e423 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Thu, 12 Feb 2026 00:26:01 -0800
Subject: [PATCH 1/4] [ELF] Add target-specific relocation scanning for RISC-V

Implement RISCV::scanSectionImpl, following the pattern established for
x86 (#178846). This merges the getRelExpr and TLS handling for SHF_ALLOC
sections into the target-specific scanner, enabling devirtualization and
eliminating abstraction overhead.

- Use processR_PC/processR_PLT_PC for common PC-relative and PLT
  relocations.
- Handle TLS relocations directly and remove RISC-V-specific checks from
  handleTlsRelocation.
- Simplify getRelExpr to only handle relocations needed by
  relocateNonAlloc and relocateEh.

Move scanSection after getRelExpr and before relocate similar to x86.
---
 lld/ELF/Arch/RISCV.cpp  | 355 +++++++++++++++++++++++++---------------
 lld/ELF/Relocations.cpp |  23 +--
 2 files changed, 229 insertions(+), 149 deletions(-)

diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 597ab602d3bd0..78ebd7054ab06 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -279,17 +279,14 @@ RelType RISCV::getDynRel(RelType type) const {
                                          : static_cast<RelType>(R_RISCV_NONE);
 }
 
+// Only needed to support relocations used by relocateNonAlloc and relocateEh.
 RelExpr RISCV::getRelExpr(const RelType type, const Symbol &s,
                           const uint8_t *loc) const {
   switch (type) {
   case R_RISCV_NONE:
-  case R_RISCV_VENDOR:
     return R_NONE;
   case R_RISCV_32:
   case R_RISCV_64:
-  case R_RISCV_HI20:
-  case R_RISCV_LO12_I:
-  case R_RISCV_LO12_S:
     return R_ABS;
   case R_RISCV_ADD8:
   case R_RISCV_ADD16:
@@ -305,59 +302,201 @@ RelExpr RISCV::getRelExpr(const RelType type, const Symbol &s,
   case R_RISCV_SUB32:
   case R_RISCV_SUB64:
     return RE_RISCV_ADD;
-  case R_RISCV_JAL:
-  case R_RISCV_BRANCH:
-  case R_RISCV_PCREL_HI20:
-  case R_RISCV_RVC_BRANCH:
-  case R_RISCV_RVC_JUMP:
   case R_RISCV_32_PCREL:
     return R_PC;
-  case R_RISCV_CALL:
-  case R_RISCV_CALL_PLT:
-  case R_RISCV_PLT32:
-    return R_PLT_PC;
-  case R_RISCV_GOT_HI20:
-  case R_RISCV_GOT32_PCREL:
-    return R_GOT_PC;
-  case R_RISCV_PCREL_LO12_I:
-  case R_RISCV_PCREL_LO12_S:
-    return RE_RISCV_PC_INDIRECT;
-  case R_RISCV_TLSDESC_HI20:
-  case R_RISCV_TLSDESC_LOAD_LO12:
-  case R_RISCV_TLSDESC_ADD_LO12:
-    return R_TLSDESC_PC;
-  case R_RISCV_TLSDESC_CALL:
-    return R_TLSDESC_CALL;
-  case R_RISCV_TLS_GD_HI20:
-    return R_TLSGD_PC;
-  case R_RISCV_TLS_GOT_HI20:
-    return R_GOT_PC;
-  case R_RISCV_TPREL_HI20:
-  case R_RISCV_TPREL_LO12_I:
-  case R_RISCV_TPREL_LO12_S:
-    return R_TPREL;
-  case R_RISCV_ALIGN:
-    return R_RELAX_HINT;
-  case R_RISCV_TPREL_ADD:
-  case R_RISCV_RELAX:
-    return ctx.arg.relax ? R_RELAX_HINT : R_NONE;
   case R_RISCV_SET_ULEB128:
   case R_RISCV_SUB_ULEB128:
     return RE_RISCV_LEB128;
   default:
-    if (type.v & INTERNAL_RISCV_VENDOR_MASK) {
-      Err(ctx) << getErrorLoc(ctx, loc)
-               << "unsupported vendor-specific relocation " << type
-               << " against symbol " << &s;
-      return R_NONE;
-    }
-    Err(ctx) << getErrorLoc(ctx, loc) << "unknown relocation ("
-             << (type.v & ~INTERNAL_RISCV_VENDOR_MASK) << ") against symbol "
-             << &s;
+    Err(ctx) << getErrorLoc(ctx, loc) << "unknown relocation (" << type.v
+             << ") against symbol " << &s;
     return R_NONE;
   }
 }
 
+template <class ELFT, class RelTy>
+void RISCV::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
+  RelocScan rs(ctx, &sec);
+  // Many relocations end up in sec.relocations.
+  sec.relocations.reserve(rels.size());
+
+  StringRef rvVendor;
+  for (auto it = rels.begin(); it != rels.end(); ++it) {
+    RelType type = it->getType(false);
+    uint32_t symIndex = it->getSymbol(false);
+    Symbol &sym = sec.getFile<ELFT>()->getSymbol(symIndex);
+    uint64_t offset = it->r_offset;
+
+    if (type == R_RISCV_VENDOR) {
+      if (!rvVendor.empty())
+        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+                 << "malformed consecutive R_RISCV_VENDOR relocations";
+      rvVendor = sym.getName();
+      continue;
+    } else if (!rvVendor.empty()) {
+      uint32_t VendorFlag = getRISCVVendorRelMarker(rvVendor);
+      if (!VendorFlag) {
+        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+                 << "unknown vendor-specific relocation (" << type.v
+                 << ") in namespace '" << rvVendor << "' against symbol '"
+                 << &sym << "'";
+        rvVendor = "";
+        continue;
+      }
+
+      rvVendor = "";
+      assert((type.v < 256) && "Out of range relocation detected!");
+      type.v |= VendorFlag;
+    }
+
+    if (sym.isUndefined() && symIndex != 0 &&
+        rs.maybeReportUndefined(cast<Undefined>(sym), offset))
+      continue;
+    int64_t addend = rs.getAddend<ELFT>(*it, type);
+    RelExpr expr;
+    // Relocation types that only need a RelExpr set `expr` and break out of
+    // the switch to reach rs.process(). Types that need special handling
+    // (fast-path helpers, TLS) call a handler and use `continue`.
+    switch (type) {
+    case R_RISCV_NONE:
+      continue;
+
+    // Absolute relocations:
+    case R_RISCV_32:
+    case R_RISCV_64:
+    case R_RISCV_HI20:
+    case R_RISCV_LO12_I:
+    case R_RISCV_LO12_S:
+      expr = R_ABS;
+      break;
+
+    // ADD/SET/SUB:
+    case R_RISCV_ADD8:
+    case R_RISCV_ADD16:
+    case R_RISCV_ADD32:
+    case R_RISCV_ADD64:
+    case R_RISCV_SET6:
+    case R_RISCV_SET8:
+    case R_RISCV_SET16:
+    case R_RISCV_SET32:
+    case R_RISCV_SUB6:
+    case R_RISCV_SUB8:
+    case R_RISCV_SUB16:
+    case R_RISCV_SUB32:
+    case R_RISCV_SUB64:
+      expr = RE_RISCV_ADD;
+      break;
+
+    // PC-relative:
+    case R_RISCV_JAL:
+    case R_RISCV_BRANCH:
+    case R_RISCV_PCREL_HI20:
+    case R_RISCV_RVC_BRANCH:
+    case R_RISCV_RVC_JUMP:
+    case R_RISCV_32_PCREL:
+      rs.processR_PC(type, offset, addend, sym);
+      continue;
+    case R_RISCV_PCREL_LO12_I:
+    case R_RISCV_PCREL_LO12_S:
+      expr = RE_RISCV_PC_INDIRECT;
+      break;
+
+    // GOT-generating relocations:
+    case R_RISCV_GOT_HI20:
+    case R_RISCV_GOT32_PCREL:
+      expr = R_GOT_PC;
+      break;
+
+    // PLT-generating relocations:
+    case R_RISCV_CALL:
+    case R_RISCV_CALL_PLT:
+    case R_RISCV_PLT32:
+      rs.processR_PLT_PC(type, offset, addend, sym);
+      continue;
+
+    // TLS relocations:
+    case R_RISCV_TPREL_HI20:
+    case R_RISCV_TPREL_LO12_I:
+    case R_RISCV_TPREL_LO12_S:
+      if (rs.checkTlsLe(offset, sym, type))
+        continue;
+      expr = R_TPREL;
+      break;
+    case R_RISCV_TLS_GOT_HI20:
+      // There is no IE to LE optimization.
+      ctx.hasTlsIe.store(true, std::memory_order_relaxed);
+      sym.setFlags(NEEDS_TLSIE);
+      sec.addReloc({R_GOT_PC, type, offset, addend, &sym});
+      continue;
+    case R_RISCV_TLS_GD_HI20:
+      // There is no GD to IE/LE optimization.
+      sym.setFlags(NEEDS_TLSGD);
+      sec.addReloc({R_TLSGD_PC, type, offset, addend, &sym});
+      continue;
+
+    // TLSDESC relocations:
+    case R_RISCV_TLSDESC_HI20:
+      rs.handleTlsDesc(R_TLSDESC_PC, R_GOT_PC, type, offset, addend, sym);
+      continue;
+    case R_RISCV_TLSDESC_LOAD_LO12:
+    case R_RISCV_TLSDESC_ADD_LO12:
+      // R_RISCV_TLSDESC_{LOAD_LO12,ADD_LO12,CALL} reference a label, not the
+      // TLS symbol, so we cannot use handleTlsDesc (which sets NEEDS_TLSDESC).
+      // For TLSDESC->IE, use R_TPREL as well, but relocateAlloc uses isToLe
+      // (from HI20) to select the correct transform.
+      sec.addReloc({ctx.arg.shared ? R_TLSDESC_PC : R_TPREL, type, offset,
+                    addend, &sym});
+      continue;
+    case R_RISCV_TLSDESC_CALL:
+      if (!ctx.arg.shared)
+        sec.addReloc({R_TPREL, type, offset, addend, &sym});
+      continue;
+
+    // Relaxation hints:
+    case R_RISCV_ALIGN:
+      sec.addReloc({R_RELAX_HINT, type, offset, addend, &sym});
+      continue;
+    case R_RISCV_TPREL_ADD:
+    case R_RISCV_RELAX:
+      if (ctx.arg.relax)
+        sec.addReloc({R_RELAX_HINT, type, offset, addend, &sym});
+      continue;
+
+    case R_RISCV_SET_ULEB128:
+    case R_RISCV_SUB_ULEB128:
+      expr = RE_RISCV_LEB128;
+      break;
+
+    default:
+      if (type.v & INTERNAL_RISCV_VENDOR_MASK) {
+        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+                 << "unsupported vendor-specific relocation " << type
+                 << " against symbol " << &sym;
+        continue;
+      }
+      Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+               << "unknown relocation (" << type.v << ") against symbol "
+               << &sym;
+      continue;
+    }
+    rs.process(expr, type, offset, sym, addend);
+  }
+
+  // Sort relocations by offset for more efficient searching for
+  // R_RISCV_PCREL_HI20.
+  llvm::stable_sort(sec.relocs(),
+                    [](const Relocation &lhs, const Relocation &rhs) {
+                      return lhs.offset < rhs.offset;
+                    });
+}
+
+void RISCV::scanSection(InputSectionBase &sec) {
+  if (ctx.arg.is64)
+    elf::scanSection1<RISCV, ELF64LE>(*this, sec);
+  else
+    elf::scanSection1<RISCV, ELF32LE>(*this, sec);
+}
+
 void RISCV::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
   const unsigned bits = ctx.arg.wordsize * 8;
 
@@ -637,43 +776,48 @@ void RISCV::relocateAlloc(InputSection &sec, uint8_t *buf) const {
       else
         val = tlsdescVal;
       break;
-    case R_RELAX_TLS_GD_TO_IE:
-      // Only R_RISCV_TLSDESC_HI20 reaches here. tlsdescVal will be finalized
-      // after we see R_RISCV_TLSDESC_ADD_LO12 in the R_RELAX_TLS_GD_TO_LE case.
-      // The net effect is that tlsdescVal will be smaller than `val` to take
-      // into account of NOP instructions (in the absence of R_RISCV_RELAX)
-      // before AUIPC.
-      tlsdescVal = val + rel.offset;
-      isToLe = false;
-      tlsdescRelax = relaxable(relocs, i);
-      if (!tlsdescRelax)
-        tlsdescToIe(ctx, loc, rel, val);
-      continue;
-    case R_RELAX_TLS_GD_TO_LE:
-      // See the comment in handleTlsRelocation. For TLSDESC=>IE,
-      // R_RISCV_TLSDESC_{LOAD_LO12,ADD_LO12,CALL} also reach here. If isToLe is
-      // false, this is actually TLSDESC=>IE optimization.
+    case R_GOT_PC:
+    case R_TPREL:
+      // TLSDESC->IE/LE: R_GOT_PC is TLSDESC->IE, R_TPREL is TLSDESC->LE.
       if (rel.type == R_RISCV_TLSDESC_HI20) {
-        tlsdescVal = val;
-        isToLe = true;
+        isToLe = rel.expr == R_TPREL;
+        if (isToLe) {
+          tlsdescVal = val;
+        } else {
+          // tlsdescVal will be finalized after we see R_RISCV_TLSDESC_ADD_LO12.
+          // The net effect is that tlsdescVal will be smaller than `val` to
+          // take into account of NOP instructions (in the absence of
+          // R_RISCV_RELAX) before AUIPC.
+          tlsdescVal = val + rel.offset;
+        }
         tlsdescRelax = relaxable(relocs, i);
-      } else {
+        if (!tlsdescRelax) {
+          if (isToLe)
+            tlsdescToLe(loc, rel, val);
+          else
+            tlsdescToIe(ctx, loc, rel, val);
+        }
+        continue;
+      }
+      if (rel.type == R_RISCV_TLSDESC_LOAD_LO12 ||
+          rel.type == R_RISCV_TLSDESC_ADD_LO12 ||
+          rel.type == R_RISCV_TLSDESC_CALL) {
         if (!isToLe && rel.type == R_RISCV_TLSDESC_ADD_LO12)
           tlsdescVal -= rel.offset;
         val = tlsdescVal;
-      }
-      // When NOP conversion is eligible and relaxation applies, don't write a
-      // NOP in case an unrelated instruction follows the current instruction.
-      if (tlsdescRelax &&
-          (rel.type == R_RISCV_TLSDESC_HI20 ||
-           rel.type == R_RISCV_TLSDESC_LOAD_LO12 ||
-           (rel.type == R_RISCV_TLSDESC_ADD_LO12 && isToLe && !hi20(val))))
+        // When NOP conversion is eligible and relaxation applies, don't write a
+        // NOP in case an unrelated instruction follows the current instruction.
+        if (tlsdescRelax &&
+            (rel.type == R_RISCV_TLSDESC_LOAD_LO12 ||
+             (rel.type == R_RISCV_TLSDESC_ADD_LO12 && isToLe && !hi20(val))))
+          continue;
+        if (isToLe)
+          tlsdescToLe(loc, rel, val);
+        else
+          tlsdescToIe(ctx, loc, rel, val);
         continue;
-      if (isToLe)
-        tlsdescToLe(loc, rel, val);
-      else
-        tlsdescToIe(ctx, loc, rel, val);
-      continue;
+      }
+      break;
     case RE_RISCV_LEB128:
       if (i + 1 < size) {
         const Relocation &rel1 = relocs[i + 1];
@@ -918,7 +1062,7 @@ static bool relax(Ctx &ctx, int pass, InputSection &sec) {
     case R_RISCV_TLSDESC_HI20:
       // For TLSDESC=>LE, we can use the short form if hi20 is zero.
       tlsdescRelax = relaxable(relocs, i);
-      toLeShortForm = tlsdescRelax && r.expr == R_RELAX_TLS_GD_TO_LE &&
+      toLeShortForm = tlsdescRelax && r.expr == R_TPREL &&
                       !hi20(r.sym->getVA(ctx, r.addend));
       [[fallthrough]];
     case R_RISCV_TLSDESC_LOAD_LO12:
@@ -1495,59 +1639,6 @@ void elf::mergeRISCVAttributesSections(Ctx &ctx) {
 
 void elf::setRISCVTargetInfo(Ctx &ctx) { ctx.target.reset(new RISCV(ctx)); }
 
-template <class ELFT, class RelTy>
-void RISCV::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
-  RelocScan rs(ctx, &sec);
-  // Many relocations end up in sec.relocations.
-  sec.relocations.reserve(rels.size());
-
-  StringRef rvVendor;
-  for (auto it = rels.begin(); it != rels.end(); ++it) {
-    RelType type = it->getType(false);
-    uint32_t symIndex = it->getSymbol(false);
-    Symbol &sym = sec.getFile<ELFT>()->getSymbol(symIndex);
-    const uint8_t *loc = sec.content().data() + it->r_offset;
-
-    if (type == R_RISCV_VENDOR) {
-      if (!rvVendor.empty())
-        Err(ctx) << getErrorLoc(ctx, loc)
-                 << "malformed consecutive R_RISCV_VENDOR relocations";
-      rvVendor = sym.getName();
-      continue;
-    } else if (!rvVendor.empty()) {
-      uint32_t VendorFlag = getRISCVVendorRelMarker(rvVendor);
-      if (!VendorFlag) {
-        Err(ctx) << getErrorLoc(ctx, loc)
-                 << "unknown vendor-specific relocation (" << type.v
-                 << ") in namespace '" << rvVendor << "' against symbol '"
-                 << &sym << "'";
-        rvVendor = "";
-        continue;
-      }
-
-      rvVendor = "";
-      assert((type.v < 256) && "Out of range relocation detected!");
-      type.v |= VendorFlag;
-    }
-
-    rs.scan<ELFT, RelTy>(it, type, rs.getAddend<ELFT>(*it, type));
-  }
-
-  // Sort relocations by offset for more efficient searching for
-  // R_RISCV_PCREL_HI20.
-  llvm::stable_sort(sec.relocs(),
-                    [](const Relocation &lhs, const Relocation &rhs) {
-                      return lhs.offset < rhs.offset;
-                    });
-}
-
-void RISCV::scanSection(InputSectionBase &sec) {
-  if (ctx.arg.is64)
-    elf::scanSection1<RISCV, ELF64LE>(*this, sec);
-  else
-    elf::scanSection1<RISCV, ELF32LE>(*this, sec);
-}
-
 uint32_t elf::getRISCVVendorRelMarker(StringRef rvVendor) {
   return StringSwitch<uint32_t>(rvVendor)
       .Case("QUALCOMM", INTERNAL_RISCV_VENDOR_QUALCOMM)
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 797acb08b0506..17d405dccc7d9 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -1183,17 +1183,13 @@ unsigned RelocScan::handleTlsRelocation(RelExpr expr, RelType type,
   if (expr == R_TPREL || expr == R_TPREL_NEG)
     return checkTlsLe(offset, sym, type) ? 1 : 0;
 
-  bool isRISCV = ctx.arg.emachine == EM_RISCV;
-
   if (oneof<RE_AARCH64_TLSDESC_PAGE, R_TLSDESC, R_TLSDESC_CALL, R_TLSDESC_PC,
             R_TLSDESC_GOTPLT, RE_LOONGARCH_TLSDESC_PAGE_PC>(expr) &&
       ctx.arg.shared) {
-    // R_RISCV_TLSDESC_{LOAD_LO12,ADD_LO12_I,CALL} reference a label. Do not
-    // set NEEDS_TLSDESC on the label.
     if (expr != R_TLSDESC_CALL) {
       if (isAArch64)
         sym.setFlags(NEEDS_TLSDESC | NEEDS_TLSDESC_NONAUTH);
-      else if (!isRISCV || type == R_RISCV_TLSDESC_HI20)
+      else
         sym.setFlags(NEEDS_TLSDESC);
       sec->addReloc({expr, type, offset, addend, &sym});
     }
@@ -1209,25 +1205,22 @@ unsigned RelocScan::handleTlsRelocation(RelExpr expr, RelType type,
        type == R_LARCH_TLS_DESC_LD || type == R_LARCH_TLS_DESC_CALL ||
        type == R_LARCH_TLS_DESC_PCREL20_S2);
 
-  // ARM, Hexagon, LoongArch and RISC-V do not support GD/LD to IE/LE
-  // optimizations.
-  // RISC-V supports TLSDESC to IE/LE optimizations.
+  // ARM, Hexagon, and LoongArch do not support GD/LD to IE/LE optimizations.
   // For PPC64, if the file has missing R_PPC64_TLSGD/R_PPC64_TLSLD, disable
   // optimization as well.
   bool execOptimize =
       !ctx.arg.shared && ctx.arg.emachine != EM_ARM &&
       ctx.arg.emachine != EM_HEXAGON &&
       (ctx.arg.emachine != EM_LOONGARCH || execOptimizeInLoongArch) &&
-      !(isRISCV && expr != R_TLSDESC_PC && expr != R_TLSDESC_CALL) &&
       !sec->file->ppc64DisableTLSRelax;
 
   // If we are producing an executable and the symbol is non-preemptable, it
   // must be defined and the code sequence can be optimized to use Local-Exec.
   //
-  // ARM and RISC-V do not support any relaxations for TLS relocations, however,
-  // we can omit the DTPMOD dynamic relocations and resolve them at link time
-  // because them are always 1. This may be necessary for static linking as
-  // DTPMOD may not be expected at load time.
+  // While ARM does not have TLS optimizations, we can omit the DTPMOD
+  // dynamic relocations and resolve them at link time because them are
+  // always 1. This may be necessary for static linking as DTPMOD may not be
+  // expected at load time.
   bool isLocalInExecutable = !sym.isPreemptible && !ctx.arg.shared;
 
   // Local Dynamic is for access to module local TLS variables, while still
@@ -1290,10 +1283,6 @@ unsigned RelocScan::handleTlsRelocation(RelExpr expr, RelType type,
 
     // Global-Dynamic/TLSDESC can be optimized to Initial-Exec or Local-Exec
     // depending on the symbol being locally defined or not.
-    //
-    // R_RISCV_TLSDESC_{LOAD_LO12,ADD_LO12_I,CALL} reference a non-preemptible
-    // label, so TLSDESC=>IE will be categorized as R_RELAX_TLS_GD_TO_LE. We fix
-    // the categorization in RISCV::relocateAlloc.
     if (sym.isPreemptible) {
       sym.setFlags(NEEDS_TLSIE);
       sec->addReloc({ctx.target->adjustTlsExpr(type, R_RELAX_TLS_GD_TO_IE),

>From 65c277803fb9f76c5af23c55b2ccef9920051fb7 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Fri, 13 Feb 2026 22:58:41 -0800
Subject: [PATCH 2/4] comment

---
 lld/ELF/Arch/RISCV.cpp | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index b1c7f55bead68..d9abbd87d62dc 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -319,12 +319,28 @@ void RISCV::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
   // Many relocations end up in sec.relocations.
   sec.relocations.reserve(rels.size());
 
+  StringRef rvVendor;
   for (auto it = rels.begin(); it != rels.end(); ++it) {
     RelType type = it->getType(false);
     uint32_t symIndex = it->getSymbol(false);
     Symbol &sym = sec.getFile<ELFT>()->getSymbol(symIndex);
     uint64_t offset = it->r_offset;
 
+    if (type == R_RISCV_VENDOR) {
+      if (!rvVendor.empty())
+        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+                 << "malformed consecutive R_RISCV_VENDOR relocations";
+      rvVendor = sym.getName();
+      continue;
+    } else if (!rvVendor.empty()) {
+      Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+               << "unknown vendor-specific relocation (" << type.v
+               << ") in namespace '" << rvVendor << "' against symbol '" << &sym
+               << "'";
+      rvVendor = "";
+      continue;
+    }
+
     if (sym.isUndefined() && symIndex != 0 &&
         rs.maybeReportUndefined(cast<Undefined>(sym), offset))
       continue;

>From 82ad4adcb921412b8af092a66ae9f5f68a6e94f6 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 17 Feb 2026 22:54:56 -0800
Subject: [PATCH 3/4] rebase

---
 lld/ELF/Arch/RISCV.cpp                  | 70 ++++++++++++-------------
 lld/test/ELF/riscv-vendor-relocations.s | 17 ++++--
 2 files changed, 45 insertions(+), 42 deletions(-)

diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 46a8bb5b0485b..9ac394e365f48 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -320,28 +320,12 @@ void RISCV::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
   // Many relocations end up in sec.relocations.
   sec.relocations.reserve(rels.size());
 
-  StringRef rvVendor;
+  StringRef vendor;
   for (auto it = rels.begin(); it != rels.end(); ++it) {
     RelType type = it->getType(false);
     uint32_t symIndex = it->getSymbol(false);
     Symbol &sym = sec.getFile<ELFT>()->getSymbol(symIndex);
     uint64_t offset = it->r_offset;
-
-    if (type == R_RISCV_VENDOR) {
-      if (!rvVendor.empty())
-        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
-                 << "malformed consecutive R_RISCV_VENDOR relocations";
-      rvVendor = sym.getName();
-      continue;
-    } else if (!rvVendor.empty()) {
-      Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
-               << "unknown vendor-specific relocation (" << type.v
-               << ") in namespace '" << rvVendor << "' against symbol '" << &sym
-               << "'";
-      rvVendor = "";
-      continue;
-    }
-
     if (sym.isUndefined() && symIndex != 0 &&
         rs.maybeReportUndefined(cast<Undefined>(sym), offset))
       continue;
@@ -363,23 +347,6 @@ void RISCV::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
       expr = R_ABS;
       break;
 
-    // ADD/SET/SUB:
-    case R_RISCV_ADD8:
-    case R_RISCV_ADD16:
-    case R_RISCV_ADD32:
-    case R_RISCV_ADD64:
-    case R_RISCV_SET6:
-    case R_RISCV_SET8:
-    case R_RISCV_SET16:
-    case R_RISCV_SET32:
-    case R_RISCV_SUB6:
-    case R_RISCV_SUB8:
-    case R_RISCV_SUB16:
-    case R_RISCV_SUB32:
-    case R_RISCV_SUB64:
-      expr = RE_RISCV_ADD;
-      break;
-
     // PC-relative relocations:
     case R_RISCV_JAL:
     case R_RISCV_BRANCH:
@@ -454,15 +421,44 @@ void RISCV::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
         sec.addReloc({R_RELAX_HINT, type, offset, addend, &sym});
       continue;
 
+    // Misc relocations:
+    case R_RISCV_ADD8:
+    case R_RISCV_ADD16:
+    case R_RISCV_ADD32:
+    case R_RISCV_ADD64:
+    case R_RISCV_SET6:
+    case R_RISCV_SET8:
+    case R_RISCV_SET16:
+    case R_RISCV_SET32:
+    case R_RISCV_SUB6:
+    case R_RISCV_SUB8:
+    case R_RISCV_SUB16:
+    case R_RISCV_SUB32:
+    case R_RISCV_SUB64:
+      expr = RE_RISCV_ADD;
+      break;
     case R_RISCV_SET_ULEB128:
     case R_RISCV_SUB_ULEB128:
       expr = RE_RISCV_LEB128;
       break;
 
+    case R_RISCV_VENDOR:
+      if (!vendor.empty())
+        Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
+                 << "malformed consecutive R_RISCV_VENDOR relocations";
+      vendor = sym.getName();
+      continue;
     default:
-      Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
-               << "unknown relocation (" << type.v << ") against symbol "
-               << &sym;
+      auto diag = Err(ctx);
+      diag << getErrorLoc(ctx, sec.content().data() + offset);
+      if (!vendor.empty()) {
+        diag << "unknown vendor-specific relocation (" << type.v
+             << ") in namespace '" << vendor << "' against symbol '" << &sym
+             << "'";
+        vendor = "";
+      } else {
+        diag << "unknown relocation (" << type.v << ") against symbol " << &sym;
+      }
       continue;
     }
     rs.process(expr, type, offset, sym, addend);
diff --git a/lld/test/ELF/riscv-vendor-relocations.s b/lld/test/ELF/riscv-vendor-relocations.s
index b0f3c4a30d060..62e6d07cf77a4 100644
--- a/lld/test/ELF/riscv-vendor-relocations.s
+++ b/lld/test/ELF/riscv-vendor-relocations.s
@@ -8,12 +8,19 @@
 TARGET:
   nop
 
-.global INVALID_VENDOR
-.reloc 1f, R_RISCV_VENDOR, INVALID_VENDOR+0
-.reloc 1f, R_RISCV_VENDOR, INVALID_VENDOR+0
-.reloc 1f, R_RISCV_CUSTOM255, TARGET
-1:
+INVALID_VENDOR:
+.reloc ., R_RISCV_VENDOR, INVALID_VENDOR+0
+.reloc ., R_RISCV_VENDOR, INVALID_VENDOR+0
+.reloc ., R_RISCV_CUSTOM255, TARGET
   nop
 
 # CHECK: error: {{.*}}:(.text+0x4): malformed consecutive R_RISCV_VENDOR relocations
 # CHECK: error: {{.*}}:(.text+0x4): unknown vendor-specific relocation (255) in namespace 'INVALID_VENDOR' against symbol 'TARGET'
+
+## The vendor symbol must be defined. If not, don't bother with a better diagnostic.
+# CHECK: error: a.o:(.text1+0x0): unknown relocation (255) against symbol TARGET
+# CHECK: error: undefined symbol: undef
+.section .text1,"ax"
+.reloc ., R_RISCV_VENDOR, undef
+.reloc ., R_RISCV_CUSTOM255, TARGET
+nop

>From 7088c38290f18ebe42775887f3830e82a6f1a0c9 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Wed, 18 Feb 2026 21:22:27 -0800
Subject: [PATCH 4/4] improve vendor relocation handling

---
 lld/ELF/Arch/RISCV.cpp                      | 12 ++++++--
 lld/test/ELF/riscv-vendor-relocations.s     | 29 +++++++++++++++++--
 lld/test/ELF/riscv-vendor-relocations2.test | 32 +++++++++++++++++++++
 3 files changed, 67 insertions(+), 6 deletions(-)
 create mode 100644 lld/test/ELF/riscv-vendor-relocations2.test

diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 9ac394e365f48..dce94b948bea2 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -442,11 +442,17 @@ void RISCV::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
       expr = RE_RISCV_LEB128;
       break;
 
-    case R_RISCV_VENDOR:
-      if (!vendor.empty())
+    case R_RISCV_VENDOR: {
+      auto it1 = it;
+      ++it1;
+      if (it1 == rels.end() || it1->getType(false) - 192u > 63u) {
         Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
-                 << "malformed consecutive R_RISCV_VENDOR relocations";
+                 << "R_RISCV_VENDOR is not followed by a relocation of code "
+                    "192 to 255";
+        continue;
+      }
       vendor = sym.getName();
+    }
       continue;
     default:
       auto diag = Err(ctx);
diff --git a/lld/test/ELF/riscv-vendor-relocations.s b/lld/test/ELF/riscv-vendor-relocations.s
index 62e6d07cf77a4..892154825beec 100644
--- a/lld/test/ELF/riscv-vendor-relocations.s
+++ b/lld/test/ELF/riscv-vendor-relocations.s
@@ -14,13 +14,36 @@ INVALID_VENDOR:
 .reloc ., R_RISCV_CUSTOM255, TARGET
   nop
 
-# CHECK: error: {{.*}}:(.text+0x4): malformed consecutive R_RISCV_VENDOR relocations
+# CHECK: error: {{.*}}:(.text+0x4): R_RISCV_VENDOR is not followed by a relocation of code 192 to 255
 # CHECK: error: {{.*}}:(.text+0x4): unknown vendor-specific relocation (255) in namespace 'INVALID_VENDOR' against symbol 'TARGET'
 
+## R_RISCV_VENDOR followed by a standard relocation (not in 192-255 range).
+# CHECK: error: {{.*}}:(.text1+0x0): R_RISCV_VENDOR is not followed by a relocation of code 192 to 255
+.section .text1,"ax"
+.reloc ., R_RISCV_VENDOR, INVALID_VENDOR
+.reloc ., R_RISCV_32, TARGET
+nop
+
+## R_RISCV_VENDOR at end of section (no following relocation).
+# CHECK: error: {{.*}}:(.text2+0x0): R_RISCV_VENDOR is not followed by a relocation of code 192 to 255
+.section .text2,"ax"
+.reloc ., R_RISCV_VENDOR, INVALID_VENDOR
+nop
+
+## Code 192 and 255 are in the valid range and reach the default case.
+# CHECK: error: {{.*}}:(.text3+0x0): unknown vendor-specific relocation (192) in namespace 'INVALID_VENDOR' against symbol 'TARGET'
+# CHECK: error: {{.*}}:(.text3+0x0): unknown vendor-specific relocation (255) in namespace 'INVALID_VENDOR' against symbol 'TARGET'
+.section .text3,"ax"
+.reloc ., R_RISCV_VENDOR, INVALID_VENDOR
+.reloc ., R_RISCV_CUSTOM192, TARGET
+.reloc ., R_RISCV_VENDOR, INVALID_VENDOR
+.reloc ., R_RISCV_CUSTOM255, TARGET
+nop
+
 ## The vendor symbol must be defined. If not, don't bother with a better diagnostic.
-# CHECK: error: a.o:(.text1+0x0): unknown relocation (255) against symbol TARGET
+# CHECK: error: {{.*}}:(.text4+0x0): unknown relocation (255) against symbol TARGET
 # CHECK: error: undefined symbol: undef
-.section .text1,"ax"
+.section .text4,"ax"
 .reloc ., R_RISCV_VENDOR, undef
 .reloc ., R_RISCV_CUSTOM255, TARGET
 nop
diff --git a/lld/test/ELF/riscv-vendor-relocations2.test b/lld/test/ELF/riscv-vendor-relocations2.test
new file mode 100644
index 0000000000000..1e7ab1f776c6d
--- /dev/null
+++ b/lld/test/ELF/riscv-vendor-relocations2.test
@@ -0,0 +1,32 @@
+# REQUIRES: riscv
+## R_RISCV_VENDOR followed by a relocation of code 256 (outside 192-255).
+# RUN: yaml2obj %s -o %t.o
+# RUN: not ld.lld -pie %t.o -o /dev/null 2>&1 | FileCheck %s
+
+# CHECK: error: {{.*}}:(.text+0x0): R_RISCV_VENDOR is not followed by a relocation of code 192 to 255
+
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_REL
+  Machine: EM_RISCV
+Sections:
+  - Name:    .text
+    Type:    SHT_PROGBITS
+    Flags:   [SHF_ALLOC, SHF_EXECINSTR]
+    Content: '13000000'
+  - Name:    .rela.text
+    Type:    SHT_RELA
+    Info:    .text
+    Relocations:
+      - Type:   R_RISCV_VENDOR
+        Symbol: vendor
+      - Type:   0x100  # 256, outside 192-255
+        Symbol: foo
+Symbols:
+  - Name:    vendor
+    Section: .text
+  - Name:    foo
+    Section: .text
+    Binding: STB_GLOBAL



More information about the llvm-commits mailing list