Skip to content

[ELF] Implement R_RISCV_TLSDESC for RISC-V #79099

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 132 additions & 26 deletions lld/ELF/Arch/RISCV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ enum Op {
AUIPC = 0x17,
JALR = 0x67,
LD = 0x3003,
LUI = 0x37,
LW = 0x2003,
SRLI = 0x5013,
SUB = 0x40000033,
Expand All @@ -73,6 +74,7 @@ enum Reg {
X_T0 = 5,
X_T1 = 6,
X_T2 = 7,
X_A0 = 10,
X_T3 = 28,
};

Expand Down Expand Up @@ -102,6 +104,26 @@ static uint32_t setLO12_S(uint32_t insn, uint32_t imm) {
(extractBits(imm, 4, 0) << 7);
}

namespace {
struct SymbolAnchor {
uint64_t offset;
Defined *d;
bool end; // true for the anchor of st_value+st_size
};
} // namespace

struct elf::RISCVRelaxAux {
// This records symbol start and end offsets which will be adjusted according
// to the nearest relocDeltas element.
SmallVector<SymbolAnchor, 0> anchors;
// For relocations[i], the actual offset is r_offset - (i ? relocDeltas[i-1] :
// 0).
std::unique_ptr<uint32_t[]> relocDeltas;
// For relocations[i], the actual type is relocTypes[i].
std::unique_ptr<RelType[]> relocTypes;
SmallVector<uint32_t, 0> writes;
};

RISCV::RISCV() {
copyRel = R_RISCV_COPY;
pltRel = R_RISCV_JUMP_SLOT;
Expand All @@ -119,6 +141,7 @@ RISCV::RISCV() {
tlsGotRel = R_RISCV_TLS_TPREL32;
}
gotRel = symbolicRel;
tlsDescRel = R_RISCV_TLSDESC;

// .got[0] = _DYNAMIC
gotHeaderEntriesNum = 1;
Expand Down Expand Up @@ -187,6 +210,8 @@ int64_t RISCV::getImplicitAddend(const uint8_t *buf, RelType type) const {
case R_RISCV_JUMP_SLOT:
// These relocations are defined as not having an implicit addend.
return 0;
case R_RISCV_TLSDESC:
return config->is64 ? read64le(buf + 8) : read32le(buf + 4);
}
}

Expand Down Expand Up @@ -295,6 +320,12 @@ RelExpr RISCV::getRelExpr(const RelType type, const Symbol &s,
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
return R_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:
Expand Down Expand Up @@ -419,6 +450,7 @@ void RISCV::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {

case R_RISCV_GOT_HI20:
case R_RISCV_PCREL_HI20:
case R_RISCV_TLSDESC_HI20:
case R_RISCV_TLS_GD_HI20:
case R_RISCV_TLS_GOT_HI20:
case R_RISCV_TPREL_HI20:
Expand All @@ -430,6 +462,8 @@ void RISCV::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
}

case R_RISCV_PCREL_LO12_I:
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_LO12_I: {
uint64_t hi = (val + 0x800) >> 12;
Expand Down Expand Up @@ -513,29 +547,113 @@ void RISCV::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
break;

case R_RISCV_RELAX:
return; // Ignored (for now)

return;
case R_RISCV_TLSDESC:
// The addend is stored in the second word.
if (config->is64)
write64le(loc + 8, val);
else
write32le(loc + 4, val);
break;
default:
llvm_unreachable("unknown relocation");
}
}

static void tlsdescToIe(uint8_t *loc, const Relocation &rel, uint64_t val) {
switch (rel.type) {
case R_RISCV_TLSDESC_HI20:
case R_RISCV_TLSDESC_LOAD_LO12:
write32le(loc, 0x00000013); // nop
return;
case R_RISCV_TLSDESC_ADD_LO12:
write32le(loc, utype(AUIPC, X_A0, hi20(val))); // auipc a0,<hi20>
return;
case R_RISCV_TLSDESC_CALL:
if (config->is64)
write32le(loc, itype(LD, X_A0, X_A0, lo12(val))); // ld a0,<lo12>(a0)
else
write32le(loc, itype(LW, X_A0, X_A0, lo12(val))); // lw a0,<lo12>(a0)
return;
default:
llvm_unreachable("unsupported relocation for TLSDESC to IE relaxation");
}
}

static void tlsdescToLe(uint8_t *loc, const Relocation &rel, uint64_t val) {
switch (rel.type) {
case R_RISCV_TLSDESC_HI20:
case R_RISCV_TLSDESC_LOAD_LO12:
write32le(loc, 0x00000013); // nop
return;
case R_RISCV_TLSDESC_ADD_LO12:
write32le(loc, utype(LUI, X_A0, hi20(val))); // lui a0,<hi20>
return;
case R_RISCV_TLSDESC_CALL:
if (isInt<12>(val))
write32le(loc, itype(ADDI, X_A0, 0, val)); // addi a0,zero,<lo12>
else
write32le(loc, itype(ADDI, X_A0, X_A0, lo12(val))); // addi a0,a0,<lo12>
return;
default:
llvm_unreachable("unsupported relocation for TLSDESC to LE relaxation");
}
}

void RISCV::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
uint64_t secAddr = sec.getOutputSection()->addr;
if (auto *s = dyn_cast<InputSection>(&sec))
secAddr += s->outSecOff;
else if (auto *ehIn = dyn_cast<EhInputSection>(&sec))
secAddr += ehIn->getParent()->outSecOff;
for (size_t i = 0, size = sec.relocs().size(); i != size; ++i) {
const Relocation &rel = sec.relocs()[i];
uint64_t tlsdescVal = 0;
bool isToIe = true;
const ArrayRef<Relocation> relocs = sec.relocs();
for (size_t i = 0, size = relocs.size(); i != size; ++i) {
const Relocation &rel = relocs[i];
uint8_t *loc = buf + rel.offset;
const uint64_t val =
uint64_t val =
sec.getRelocTargetVA(sec.file, rel.type, rel.addend,
secAddr + rel.offset, *rel.sym, rel.expr);

switch (rel.expr) {
case R_RELAX_HINT:
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)
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;
isToIe = true;
tlsdescToIe(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 isToIe is
// true, this is actually TLSDESC=>IE optimization.
if (rel.type == R_RISCV_TLSDESC_HI20) {
tlsdescVal = val;
isToIe = false;
} else {
if (isToIe && rel.type == R_RISCV_TLSDESC_ADD_LO12)
tlsdescVal -= rel.offset;
val = tlsdescVal;
}
if (isToIe)
tlsdescToIe(loc, rel, val);
else
tlsdescToLe(loc, rel, val);
continue;
case R_RISCV_LEB128:
if (i + 1 < size) {
const Relocation &rel1 = sec.relocs()[i + 1];
Expand All @@ -554,32 +672,12 @@ void RISCV::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
": R_RISCV_SET_ULEB128 not paired with R_RISCV_SUB_SET128");
return;
default:
relocate(loc, rel, val);
break;
}
relocate(loc, rel, val);
}
}

namespace {
struct SymbolAnchor {
uint64_t offset;
Defined *d;
bool end; // true for the anchor of st_value+st_size
};
} // namespace

struct elf::RISCVRelaxAux {
// This records symbol start and end offsets which will be adjusted according
// to the nearest relocDeltas element.
SmallVector<SymbolAnchor, 0> anchors;
// For relocations[i], the actual offset is r_offset - (i ? relocDeltas[i-1] :
// 0).
std::unique_ptr<uint32_t[]> relocDeltas;
// For relocations[i], the actual type is relocTypes[i].
std::unique_ptr<RelType[]> relocTypes;
SmallVector<uint32_t, 0> writes;
};

static void initSymbolAnchors() {
SmallVector<InputSection *, 0> storage;
for (OutputSection *osec : outputSections) {
Expand Down Expand Up @@ -762,6 +860,14 @@ static bool relax(InputSection &sec) {
sec.relocs()[i + 1].type == R_RISCV_RELAX)
relaxHi20Lo12(sec, i, loc, r, remove);
break;
case R_RISCV_TLSDESC_HI20:
case R_RISCV_TLSDESC_LOAD_LO12:
// For LE or IE optimization, AUIPC and L[DW] are converted to a removable
// NOP.
if (r.expr != R_TLSDESC_PC && i + 1 != sec.relocs().size() &&
sec.relocs()[i + 1].type == R_RISCV_RELAX)
remove = 4;
break;
}

// For all anchors whose offsets are <= r.offset, they are preceded by
Expand Down
25 changes: 17 additions & 8 deletions lld/ELF/Relocations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1274,26 +1274,31 @@ static unsigned handleTlsRelocation(RelType type, Symbol &sym,

if (config->emachine == EM_MIPS)
return handleMipsTlsRelocation(type, sym, c, offset, addend, expr);
bool isRISCV = config->emachine == EM_RISCV;

if (oneof<R_AARCH64_TLSDESC_PAGE, R_TLSDESC, R_TLSDESC_CALL, R_TLSDESC_PC,
R_TLSDESC_GOTPLT>(expr) &&
config->shared) {
// R_RISCV_TLSDESC_{LOAD_LO12_I,ADD_LO12_I,CALL} reference a label. Do not
// set NEEDS_TLSDESC on the label.
if (expr != R_TLSDESC_CALL) {
sym.setFlags(NEEDS_TLSDESC);
if (!isRISCV || type == R_RISCV_TLSDESC_HI20)
sym.setFlags(NEEDS_TLSDESC);
c.addReloc({expr, type, offset, addend, &sym});
}
return 1;
}

// ARM, Hexagon, LoongArch and RISC-V do not support GD/LD to IE/LE
// relaxation.
// optimizations.
// RISC-V supports TLSDESC to IE/LE optimizations.
// For PPC64, if the file has missing R_PPC64_TLSGD/R_PPC64_TLSLD, disable
// relaxation as well.
bool toExecRelax = !config->shared && config->emachine != EM_ARM &&
config->emachine != EM_HEXAGON &&
config->emachine != EM_LOONGARCH &&
config->emachine != EM_RISCV &&
!c.file->ppc64DisableTLSRelax;
bool toExecRelax =
!config->shared && config->emachine != EM_ARM &&
config->emachine != EM_HEXAGON && config->emachine != EM_LOONGARCH &&
!(isRISCV && expr != R_TLSDESC_PC && expr != R_TLSDESC_CALL) &&
!c.file->ppc64DisableTLSRelax;

// If we are producing an executable and the symbol is non-preemptable, it
// must be defined and the code sequence can be relaxed to use Local-Exec.
Expand Down Expand Up @@ -1347,8 +1352,12 @@ static unsigned handleTlsRelocation(RelType type, Symbol &sym,
return 1;
}

// Global-Dynamic relocs can be relaxed to Initial-Exec or Local-Exec
// Global-Dynamic/TLSDESC can be relaxed to Initial-Exec or Local-Exec
// depending on the symbol being locally defined or not.
//
// R_RISCV_TLSDESC_{LOAD_LO12_I,ADD_LO12_I,CALL} reference a non-preemptible
// label, so the LE transition will be categorized as R_RELAX_TLS_GD_TO_LE.
// We fix the categorization in RISCV::relocateAlloc.
if (sym.isPreemptible) {
sym.setFlags(NEEDS_TLSGD_TO_IE);
c.addReloc({target->adjustTlsExpr(type, R_RELAX_TLS_GD_TO_IE), type,
Expand Down
26 changes: 26 additions & 0 deletions lld/test/ELF/riscv-tlsdesc-gd-mixed.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# REQUIRES: riscv
# RUN: llvm-mc -filetype=obj -triple=riscv64 %s -o %t.o
# RUN: ld.lld -shared %t.o -o %t.so
# RUN: llvm-readobj -r %t.so | FileCheck %s --check-prefix=RELA

## Both TLSDESC and DTPMOD64/DTPREL64 should be present.
# RELA: .rela.dyn {
# RELA-NEXT: 0x[[#%X,ADDR:]] R_RISCV_TLSDESC a 0x0
# RELA-NEXT: 0x[[#ADDR+16]] R_RISCV_TLS_DTPMOD64 a 0x0
# RELA-NEXT: 0x[[#ADDR+24]] R_RISCV_TLS_DTPREL64 a 0x0
# RELA-NEXT: }

la.tls.gd a0,a
call __tls_get_addr@plt

.Ltlsdesc_hi0:
auipc a2, %tlsdesc_hi(a)
ld a3, %tlsdesc_load_lo(.Ltlsdesc_hi0)(a2)
addi a0, a2, %tlsdesc_add_lo(.Ltlsdesc_hi0)
jalr t0, 0(a3), %tlsdesc_call(.Ltlsdesc_hi0)

.section .tbss,"awT",@nobits
.globl a
.zero 8
a:
.zero 4
Loading