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

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

- Inline relocation classification into scanSectionImpl with a switch
  on relocation type, replacing the generic rs.scan() path.
- Use processR_PC/processR_PLT_PC for common PC-relative and PLT
  relocations.
- Handle TLS IE and GD directly (RISC-V does not optimize GD/LD/IE).
- Replace TLS-optimization-specific expressions for TLSDESC, following
  the x86 pattern: R_RELAX_TLS_GD_TO_IE -> R_GOT_PC,
  R_RELAX_TLS_GD_TO_LE -> R_TPREL. Update relocateAlloc and relax()
  to dispatch on relocation type instead of RelExpr for TLSDESC.
- Simplify getRelExpr to only handle relocations needed by
  relocateNonAlloc and preprocessRelocs.
- Remove RISC-V-specific checks from handleTlsRelocation (isRISCV
  variable, TLSDESC label special cases).
- Move R_RISCV_VENDOR handling into the relocation type switch. An
  undefined vendor symbol now gets the standard undefined symbol error
  instead of a vendor-specific diagnostic.
This commit is contained in:
Fangrui Song 2026-03-05 20:08:40 -08:00 committed by GitHub
parent f7ca74f600
commit 4ea72c1e8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 262 additions and 124 deletions

View File

@ -278,17 +278,15 @@ RelType RISCV::getDynRel(RelType type) const {
: static_cast<RelType>(R_RISCV_NONE);
}
// Only needed to support relocations used by relocateNonAlloc and
// preprocessRelocs.
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:
@ -304,42 +302,8 @@ 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;
@ -356,29 +320,152 @@ 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);
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();
uint64_t offset = it->r_offset;
if (sym.isUndefined() && symIndex != 0 &&
rs.maybeReportUndefined(cast<Undefined>(sym), offset))
continue;
} else if (!rvVendor.empty()) {
Err(ctx) << getErrorLoc(ctx, loc)
<< "unknown vendor-specific relocation (" << type.v
<< ") in namespace '" << rvVendor << "' against symbol '" << &sym
<< "'";
rvVendor = "";
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;
// PC-relative relocations:
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;
// 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;
// GOT-generating relocations:
case R_RISCV_GOT_HI20:
case R_RISCV_GOT32_PCREL:
expr = R_GOT_PC;
break;
// 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.
rs.handleTlsIe<false>(R_GOT_PC, type, offset, addend, sym);
continue;
case R_RISCV_TLS_GD_HI20:
// There is no GD to IE/LE optimization.
rs.handleTlsGd(R_TLSGD_PC, R_NONE, R_NONE, 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;
// 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: {
auto it1 = it;
++it1;
if (it1 == rels.end() || it1->getType(false) - 192u > 63u) {
Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
<< "R_RISCV_VENDOR is not followed by a relocation of code "
"192 to 255";
continue;
}
vendor = sym.getName();
}
continue;
default:
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.scan<ELFT, RelTy>(it, type, rs.getAddend<ELFT>(*it, type));
rs.process(expr, type, offset, sym, addend);
}
// Sort relocations by offset for more efficient searching for
@ -664,47 +751,52 @@ void RISCV::relocateAlloc(InputSection &sec, uint8_t *buf) const {
uint8_t *loc = buf + rel.offset;
uint64_t val = sec.getRelocTargetVA(ctx, rel, secAddr + rel.offset);
switch (rel.expr) {
case R_RELAX_HINT:
switch (rel.type) {
case R_RISCV_ALIGN:
case R_RISCV_RELAX:
case R_RISCV_TPREL_ADD:
continue;
case R_TLSDESC_PC:
// For R_RISCV_TLSDESC_HI20, store &got(sym)-PC to be used by the
// following two instructions L[DW] and ADDI.
if (rel.type == R_RISCV_TLSDESC_HI20)
case R_RISCV_TLSDESC_HI20:
if (rel.expr == R_TLSDESC_PC) {
// Shared object: store &got(sym)-PC for the following L[DW]/ADDI.
tlsdescVal = val;
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.
if (rel.type == R_RISCV_TLSDESC_HI20) {
tlsdescVal = val;
isToLe = true;
tlsdescRelax = relaxable(relocs, i);
} else {
if (!isToLe && rel.type == R_RISCV_TLSDESC_ADD_LO12)
tlsdescVal -= rel.offset;
val = tlsdescVal;
break;
}
// Executable: TLSDESC->LE (R_TPREL) or TLSDESC->IE (R_GOT_PC).
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);
if (!tlsdescRelax) {
if (isToLe)
tlsdescToLe(loc, rel, val);
else
tlsdescToIe(ctx, loc, rel, val);
}
continue;
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL:
if (rel.expr == R_TLSDESC_PC) {
// Shared object: propagate the stored GOT value.
val = tlsdescVal;
break;
}
// Executable: IE or LE instruction rewrite.
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_LOAD_LO12 ||
(rel.type == R_RISCV_TLSDESC_ADD_LO12 && isToLe && !hi20(val))))
continue;
if (isToLe)
@ -712,11 +804,10 @@ void RISCV::relocateAlloc(InputSection &sec, uint8_t *buf) const {
else
tlsdescToIe(ctx, loc, rel, val);
continue;
case RE_RISCV_LEB128:
case R_RISCV_SET_ULEB128:
if (i + 1 < size) {
const Relocation &rel1 = relocs[i + 1];
if (rel.type == R_RISCV_SET_ULEB128 &&
rel1.type == R_RISCV_SUB_ULEB128 && rel.offset == rel1.offset) {
if (rel1.type == R_RISCV_SUB_ULEB128 && rel.offset == rel1.offset) {
auto val = rel.sym->getVA(ctx, rel.addend) -
rel1.sym->getVA(ctx, rel1.addend);
if (overwriteULEB128(loc, val) >= 0x80)
@ -728,7 +819,7 @@ void RISCV::relocateAlloc(InputSection &sec, uint8_t *buf) const {
}
}
Err(ctx) << sec.getLocation(rel.offset)
<< ": R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_SET128";
<< ": R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_ULEB128";
return;
default:
break;
@ -956,7 +1047,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:

View File

@ -1101,7 +1101,7 @@ void InputSection::relocateNonAlloc(Ctx &ctx, uint8_t *buf,
continue;
}
Err(ctx) << getLocation(offset)
<< ": R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_SET128";
<< ": R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_ULEB128";
return;
}

View File

@ -1136,35 +1136,24 @@ 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<R_TLSDESC, R_TLSDESC_CALL, R_TLSDESC_PC, R_TLSDESC_GOTPLT>(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 (!isRISCV || type == R_RISCV_TLSDESC_HI20)
sym.setFlags(NEEDS_TLSDESC);
sym.setFlags(NEEDS_TLSDESC);
sec->addReloc({expr, type, offset, addend, &sym});
}
return 1;
}
// RISC-V does not support GD/LD to IE/LE optimizations.
// RISC-V supports TLSDESC 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 &&
!(isRISCV && expr != R_TLSDESC_PC && expr != R_TLSDESC_CALL);
bool execOptimize = !ctx.arg.shared;
// 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.
//
// RISC-V does 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 some targets do 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
@ -1203,10 +1192,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),

View File

@ -64,8 +64,8 @@
# RUN: not ld.lld -shared --threads=1 unpaired2.o 2>&1 | FileCheck %s --check-prefix=UNPAIRED
# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax unpaired3.s -o unpaired3.o
# RUN: not ld.lld -shared --threads=1 unpaired3.o 2>&1 | FileCheck %s --check-prefix=UNPAIRED
# UNPAIRED: error: {{.*}}.o:(.alloc+0x8): R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_SET128
# UNPAIRED: error: {{.*}}.o:(.debug_rnglists+0x8): R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_SET128
# UNPAIRED: error: {{.*}}.o:(.alloc+0x8): R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_ULEB128
# UNPAIRED: error: {{.*}}.o:(.debug_rnglists+0x8): R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_ULEB128
# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax overflow.s -o overflow.o
# RUN: not ld.lld -shared --threads=1 overflow.o 2>&1 | FileCheck %s --check-prefix=OVERFLOW

View File

@ -8,12 +8,42 @@
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): 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: {{.*}}:(.text4+0x0): unknown relocation (255) against symbol TARGET
# CHECK: error: undefined symbol: undef
.section .text4,"ax"
.reloc ., R_RISCV_VENDOR, undef
.reloc ., R_RISCV_CUSTOM255, TARGET
nop

View File

@ -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