Skip to content

Commit

Permalink
[ELF] Implement R_RISCV_TLSDESC for RISC-V
Browse files Browse the repository at this point in the history
Support
R_RISCV_TLSDESC_HI20/R_RISCV_TLSDESC_LOAD_LO12_I/R_RISCV_TLSDESC_ADD_LO12_I/R_RISCV_TLSDESC_CALL.
LOAD_LO12_I/ADD_LO12_I/CALL relocations reference a label at the HI20
location, which requires special handling. We save the value of HI20 to
be reused.

For -no-pie/-pie links, TLSDESC to initial-exec or local-exec
optimizations are eligible. Implement the relevant hooks
(R_RELAX_TLS_GD_TO_LE, R_RELAX_TLS_GD_TO_IE). AUIPC/L[DW] for
initial-exec uses GOT offsets, which may be adjusted in the presence of
linker relaxation.

```
// TLSDESC to LE/IE optimization
.Ltlsdesc_hi2:
  auipc a4, %tlsdesc_hi(c)                      # if R_RISCV_RELAX: remove; otherwise, NOP
  load  a5, %tlsdesc_load_lo(.Ltlsdesc_hi2)(a4) # if R_RISCV_RELAX: remove; otherwise, NOP
  addi  a0, a4, %tlsdesc_add_lo(.Ltlsdesc_hi2)
  jalr  t0, 0(a5), %tlsdesc_call(.Ltlsdesc_hi2)
  add   a0, a0, tp
```

* `riscv64-tlsdesc.s` is inspired by `i386-tlsdesc-gd.s` (https://reviews.llvm.org/D112582).
* `riscv64-tlsdesc-relax.s` tests linker relaxation.

DO NOT SUBMIT: rebase after llvm#66915 lands
  • Loading branch information
MaskRay committed Jan 23, 2024
1 parent cdf3804 commit 803d140
Show file tree
Hide file tree
Showing 5 changed files with 492 additions and 34 deletions.
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

0 comments on commit 803d140

Please sign in to comment.