diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp --- a/lld/ELF/Arch/RISCV.cpp +++ b/lld/ELF/Arch/RISCV.cpp @@ -56,6 +56,9 @@ enum Reg { X_RA = 1, + X_SP = 2, + X_GP = 3, + X_TP = 4, X_T0 = 5, X_T1 = 6, X_T2 = 7, @@ -466,6 +469,7 @@ break; case R_RISCV_RELAX: + case R_RISCV_TPREL_ADD: return; // Processed in a later pass default: @@ -473,6 +477,10 @@ } } +static void setRs1(uint8_t *buf, int rs1) { + write32le(buf, (read32le(buf) & 0xfff07fff) | rs1 << 15); +} + using AdjustRanges = SmallVector; // Relax R_RISCV_CALL to jal, c.j, or c.jal @@ -549,6 +557,149 @@ return false; } +// The addend of the R_RISCV_RELAX bound to R_RISCV_*_HI20 is unused, so we +// are free to repurpose it to hold the original 4-byte LUI or AUIPC +// instruction. We keep the original instruction, plus the reloc type & expr +// in case we must undo the relaxation. +union HiLoAddend { + struct { + uint32_t insn; + RelType type : 16; + RelExpr expr : 16; + } x; + uint64_t u64; + HiLoAddend(uint64_t u) : u64(u) {} + HiLoAddend(uint32_t insn, RelType type, RelExpr expr) : x{insn, type, expr} {} +}; + +// For R_RISCV_HI20 and R_RISCV_LO12_[IS], only relax to GP-relative form if +// __global_pointer$ symbol is defined and the target symbol is within the +// same section as gp. This assumes the offset between gp and the target +// symbol is static during relaxation. + +static uint64_t relaxHi20(InputSection *isec, Relocation &r, int64_t &delta, + AdjustRanges &adjustRanges) { + uint64_t target = r.sym->getVA(r.addend); + Defined *gp = ElfSym::riscvGlobalPointer; + if (gp && r.sym->getOutputSection() == gp->section->getOutputSection()) { + uint64_t displace = target - gp->getVA(); + if (isInt<12>(displace)) { + // delete unnecessary instruction + uint32_t insn = read32le(isec->data().data() + r.offset); + HiLoAddend hla(insn, r.type, r.expr); + adjustRanges.push_back({r.offset, -4}); + delta -= 4; + r.type = R_RISCV_NONE; + r.expr = R_NONE; + return hla.u64; + } + } else { + uint32_t insn = read32le(isec->data().data() + r.offset); + unsigned rd = extractBits(insn, 11, 7); + if (config->eflags & EF_RISCV_RVC && rd != 0 && rd != X_SP && target != 0 && + isInt<6>(SignExtend64(target + 0x800, config->wordsize * 8) >> 12)) { + // c.lui rd, 0 + HiLoAddend hla(insn, r.type, r.expr); + write16le(isec->mutableData().data() + r.offset, 0x6001 | rd << 7); + adjustRanges.push_back({r.offset + 2, -2}); + delta -= 2; + r.type = R_RISCV_RVC_LUI; + return hla.u64; + } + } + return 0; +} + +static uint64_t relaxLo12(InputSection *isec, Relocation &r) { + Defined *gp = ElfSym::riscvGlobalPointer; + if (!gp || r.sym->getOutputSection() != gp->section->getOutputSection()) + return 0; + uint64_t target = r.sym->getVA(r.addend); + uint64_t displace = target - gp->getVA(); + if (isInt<12>(displace)) { + uint8_t *loc = isec->mutableData().data() + r.offset; + uint32_t insn = read32le(loc); + HiLoAddend hla(insn, r.type, r.expr); + // make GP the base register + setRs1(loc, X_GP); + r.expr = R_RISCV_GPREL; + return hla.u64; + } + return 0; +} + +static bool isPCRelHi20Type(RelType type) { + return type == R_RISCV_PCREL_HI20 || type == R_RISCV_GOT_HI20 || + type == R_RISCV_TLS_GD_HI20 || type == R_RISCV_TLS_GOT_HI20; +} + +// For R_RISCV_PC_INDIRECT (R_RISCV_PCREL_LO12_{I,S}), the symbol actually +// points the corresponding R_RISCV_*_HI20 relocation, and the target VA +// is calculated using PCREL_HI20's symbol. +// +// This function returns the R_RISCV_*_HI20 relocation from +// R_RISCV_PCREL_LO12's symbol and addend. +Relocation *lld::elf::getRISCVPCRelHi20(const Symbol *sym, uint64_t addend) { + const Defined *d = cast(sym); + if (!d->section) { + error("R_RISCV_PCREL_LO12 relocation points to an absolute symbol: " + + sym->getName()); + return nullptr; + } + InputSection *isec = cast(d->section); + + if (addend != 0) + warn("non-zero addend in R_RISCV_PCREL_LO12 relocation to " + + isec->getObjMsg(d->value) + " is ignored"); + + // Relocations are sorted by offset, so we can use std::equal_range to do + // binary search. + Relocation r; + r.offset = d->value; + auto range = + std::equal_range(isec->relocations.begin(), isec->relocations.end(), r, + [](const Relocation &lhs, const Relocation &rhs) { + return lhs.offset < rhs.offset; + }); + + for (auto it = range.first; it != range.second; ++it) { + if (isPCRelHi20Type(it->type)) + return &*it; + if (it->type != R_RISCV_NONE || it + 1 == range.second || + it[1].type != R_RISCV_RELAX) + continue; + HiLoAddend hla(it[1].addend); + if (isPCRelHi20Type(hla.x.type)) + return &*it; + } + + error("R_RISCV_PCREL_LO12 relocation points to " + isec->getObjMsg(d->value) + + " without an associated R_RISCV_PCREL_HI20 relocation"); + return nullptr; +} + +static uint64_t relaxPcrelLo12(InputSection *isec, Relocation &r, + int64_t &delta, AdjustRanges &adjustRanges) { + Defined *gp = ElfSym::riscvGlobalPointer; + if (!gp) + return false; + const Relocation *hi20 = getRISCVPCRelHi20(r.sym, r.addend); + if (!hi20 || hi20->sym->getOutputSection() != gp->section->getOutputSection()) + return false; + uint64_t target = hi20->sym->getVA(hi20->addend); + uint64_t displace = target - gp->getVA(); + if (!isInt<12>(displace)) + return false; + + // make GP the base register + setRs1(isec->mutableData().data() + r.offset, X_GP); + r.expr = R_RISCV_GPREL; + r.sym = hi20->sym; + r.addend += hi20->addend; + r.type = r.type == R_RISCV_PCREL_LO12_I ? R_RISCV_LO12_I : R_RISCV_LO12_S; + return true; +} + // As input, the addend of R_RISCV_ALIGN holds the number of NOP bytes emitted // by the compiler. We derive the desired alignment boundary by rounding this up // to the nearest power of two. The multi-pass relaxation algorithm needs two @@ -713,18 +864,18 @@ break; case R_RISCV_HI20: case R_RISCV_PCREL_HI20: - // TODO: relax hi20 + it[1].addend = relaxHi20(isec, r, delta, ranges); break; case R_RISCV_RVC_LUI: // TODO: undo HI20 relaxation break; case R_RISCV_LO12_I: case R_RISCV_LO12_S: - // TODO: relax lo12 + it[1].addend = relaxLo12(isec, r); break; case R_RISCV_PCREL_LO12_I: case R_RISCV_PCREL_LO12_S: - // TODO: relax pcrel lo12 + it[1].addend = relaxPcrelLo12(isec, r, delta, ranges); break; case R_RISCV_TPREL_HI20: case R_RISCV_TPREL_ADD: diff --git a/lld/ELF/InputSection.h b/lld/ELF/InputSection.h --- a/lld/ELF/InputSection.h +++ b/lld/ELF/InputSection.h @@ -446,6 +446,7 @@ // STT_SECTION symbol associated to the .toc input section. extern llvm::DenseSet> ppc64noTocRelax; +extern Relocation *getRISCVPCRelHi20(const Symbol *sym, uint64_t addend); } // namespace elf std::string toString(const elf::InputSectionBase *); diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -626,45 +626,6 @@ return os->ptLoad->firstSec->addr; } -// For R_RISCV_PC_INDIRECT (R_RISCV_PCREL_LO12_{I,S}), the symbol actually -// points the corresponding R_RISCV_PCREL_HI20 relocation, and the target VA -// is calculated using PCREL_HI20's symbol. -// -// This function returns the R_RISCV_PCREL_HI20 relocation from -// R_RISCV_PCREL_LO12's symbol and addend. -static Relocation *getRISCVPCRelHi20(const Symbol *sym, uint64_t addend) { - const Defined *d = cast(sym); - if (!d->section) { - error("R_RISCV_PCREL_LO12 relocation points to an absolute symbol: " + - sym->getName()); - return nullptr; - } - InputSection *isec = cast(d->section); - - if (addend != 0) - warn("non-zero addend in R_RISCV_PCREL_LO12 relocation to " + - isec->getObjMsg(d->value) + " is ignored"); - - // Relocations are sorted by offset, so we can use std::equal_range to do - // binary search. - Relocation r; - r.offset = d->value; - auto range = - std::equal_range(isec->relocations.begin(), isec->relocations.end(), r, - [](const Relocation &lhs, const Relocation &rhs) { - return lhs.offset < rhs.offset; - }); - - for (auto it = range.first; it != range.second; ++it) - if (it->type == R_RISCV_PCREL_HI20 || it->type == R_RISCV_GOT_HI20 || - it->type == R_RISCV_TLS_GD_HI20 || it->type == R_RISCV_TLS_GOT_HI20) - return &*it; - - error("R_RISCV_PCREL_LO12 relocation points to " + isec->getObjMsg(d->value) + - " without an associated R_RISCV_PCREL_HI20 relocation"); - return nullptr; -} - // A TLS symbol's virtual address is relative to the TLS segment. Add a // target-specific adjustment to produce a thread-pointer-relative offset. static int64_t getTlsTpOffset(const Symbol &s) { @@ -795,8 +756,15 @@ uint64_t val = sym.isUndefWeak() ? p + a : sym.getVA(a); return getAArch64Page(val) - getAArch64Page(p); } + case R_RISCV_GPREL: { + if (!ElfSym::riscvGlobalPointer) + llvm_unreachable( + "Cannot compute R_RISCV_GPREL if __global_pointer$ is not set"); + + return sym.getVA(a) - ElfSym::riscvGlobalPointer->getVA(); + } case R_RISCV_PC_INDIRECT: { - if (const Relocation *hiRel = getRISCVPCRelHi20(&sym, a)) + if (const Relocation *hiRel = elf::getRISCVPCRelHi20(&sym, a)) return getRelocTargetVA(file, hiRel->type, hiRel->addend, sym.getVA(), *hiRel->sym, hiRel->expr); return 0; diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h --- a/lld/ELF/Relocations.h +++ b/lld/ELF/Relocations.h @@ -102,6 +102,7 @@ R_PPC64_TOCBASE, R_PPC64_RELAX_GOT_PC, R_RISCV_ADD, + R_RISCV_GPREL, R_RISCV_PC_INDIRECT, }; diff --git a/lld/test/ELF/riscv-gp.s b/lld/test/ELF/riscv-gp.s --- a/lld/test/ELF/riscv-gp.s +++ b/lld/test/ELF/riscv-gp.s @@ -1,14 +1,16 @@ # REQUIRES: riscv -# RUN: llvm-mc -filetype=obj -triple=riscv32 %s -o %t.32.o +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax %s -o %t.32.o # RUN: ld.lld -pie %t.32.o -o %t.32 # RUN: llvm-readelf -s %t.32 | FileCheck --check-prefix=SYM32 %s # RUN: llvm-readelf -S %t.32 | FileCheck --check-prefix=SEC32 %s +# RUN: llvm-objdump -d --print-imm-hex %t.32 | FileCheck --check-prefix=DIS32 %s # RUN: not ld.lld -shared %t.32.o -o /dev/null 2>&1 | FileCheck --check-prefix=ERR %s -# RUN: llvm-mc -filetype=obj -triple=riscv64 %s -o %t.64.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax %s -o %t.64.o # RUN: ld.lld -pie %t.64.o -o %t.64 # RUN: llvm-readelf -s %t.64 | FileCheck --check-prefix=SYM64 %s # RUN: llvm-readelf -S %t.64 | FileCheck --check-prefix=SEC64 %s +# RUN: llvm-objdump -d %t.64 | FileCheck --check-prefix=DIS64 %s # RUN: not ld.lld -shared %t.64.o -o /dev/null 2>&1 | FileCheck --check-prefix=ERR %s ## __global_pointer$ = .sdata+0x800 = 0x39c0 @@ -18,12 +20,15 @@ # SEC64: [ 7] .sdata PROGBITS {{0*}}000032e0 # SYM64: {{0*}}00003ae0 0 NOTYPE GLOBAL DEFAULT 7 __global_pointer$ -## __global_pointer$ - 0x1000 = 4096*3-2048 -# DIS: 1000: auipc gp, 3 -# DIS-NEXT: addi gp, gp, -2048 +# DIS32: auipc gp, 3 +# DIS32-NEXT: addi gp, gp, -1968 + +# DIS64: auipc gp, 3 +# DIS64-NEXT: addi gp, gp, -1896 # ERR: error: relocation R_RISCV_PCREL_HI20 cannot be used against symbol '__global_pointer$'; recompile with -fPIC +.option norelax lla gp, __global_pointer$ .section .sdata,"aw" diff --git a/lld/test/ELF/riscv-relax-hi20-lo12.s b/lld/test/ELF/riscv-relax-hi20-lo12.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/riscv-relax-hi20-lo12.s @@ -0,0 +1,62 @@ +# REQUIRES: riscv +# RUN: rm -rf %t && mkdir -p %t && cd %t + +# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax %s -o rv32.o +# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax %s -o rv64.o +# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+c,+relax %s -o rv32c.o +# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+c,+relax %s -o rv64c.o + +# RUN: echo 'SECTIONS { .text : { *(.text) } \ +# RUN: .sdata 0x200000 : { foo = .; } }' > gp.lds +# RUN: ld.lld --undefined=__global_pointer$ rv32.o gp.lds -o gp.rv32 +# RUN: ld.lld --undefined=__global_pointer$ rv64.o gp.lds -o gp.rv64 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn gp.rv32 > gp.dis.rv32 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn gp.rv64 > gp.dis.rv64 +# RUN: FileCheck --check-prefix=GP %s < gp.dis.rv32 +# RUN: FileCheck --check-prefix=GP %s < gp.dis.rv64 +# GP-NOT: lui +# GP: addi a0, gp, -2048 +# GP-NEXT: lw a0, -2048(gp) +# GP-NEXT: sw a0, -2048(gp) + +# RUN: echo 'SECTIONS { .text : { *(.text) } \ +# RUN: .sdata 0x200000 : { foo = . + 4096; } }' > nogp.lds +# RUN: ld.lld --undefined=__global_pointer$ rv32.o nogp.lds -o nogp.rv32 +# RUN: ld.lld --undefined=__global_pointer$ rv64.o nogp.lds -o nogp.rv64 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn nogp.rv32 > nogp.dis.rv32 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn nogp.rv64 > nogp.dis.rv64 +# RUN: FileCheck --check-prefix=NOGP %s < nogp.dis.rv32 +# RUN: FileCheck --check-prefix=NOGP %s < nogp.dis.rv64 +# NOGP: lui a0, 513 +# NOGP-NEXT: addi a0, a0, 0 +# NOGP-NEXT: lw a0, 0(a0) +# NOGP-NEXT: sw a0, 0(a0) + +# RUN: ld.lld --defsym=foo=0x1000 rv32c.o -o clui.rv32 +# RUN: ld.lld --defsym=foo=0x1000 rv64c.o -o clui.rv64 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn clui.rv32 > clui.dis.rv32 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn clui.rv64 > clui.dis.rv64 +# RUN: FileCheck --check-prefix=CLUI %s < clui.dis.rv32 +# RUN: FileCheck --check-prefix=CLUI %s < clui.dis.rv64 +# CLUI: c.lui a0, 1 +# CLUI-NEXT: addi a0, a0, 0 +# CLUI-NEXT: lw a0, 0(a0) +# CLUI-NEXT: sw a0, 0(a0) + +# RUN: ld.lld --defsym=foo=0x10 rv32c.o -o cli.rv32 +# RUN: ld.lld --defsym=foo=0x10 rv64c.o -o cli.rv64 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn cli.rv32 > cli.dis.rv32 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn cli.rv64 > cli.dis.rv64 +# RUN: FileCheck --check-prefix=CLI %s < cli.dis.rv32 +# RUN: FileCheck --check-prefix=CLI %s < cli.dis.rv64 +# CLI: c.li a0, 0 +# CLI-NEXT: addi a0, a0, 16 +# CLI-NEXT: lw a0, 16(a0) +# CLI-NEXT: sw a0, 16(a0) + +.global _start +_start: + lui a0, %hi(foo) + addi a0, a0, %lo(foo) + lw a0, %lo(foo)(a0) + sw a0, %lo(foo)(a0) diff --git a/lld/test/ELF/riscv-relax-pcrel.s b/lld/test/ELF/riscv-relax-pcrel.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/riscv-relax-pcrel.s @@ -0,0 +1,40 @@ +# REQUIRES: riscv +# RUN: rm -rf %t && mkdir -p %t && cd %t + +# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax %s -o rv32.o +# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax %s -o rv64.o + +# RUN: echo 'SECTIONS { .text 0x100000 : { *(.text) } \ +# RUN: .sdata 0x200000 : { foo = .; } }' > gp.lds +# RUN: ld.lld --undefined=__global_pointer$ rv32.o gp.lds -o gp.rv32 +# RUN: ld.lld --undefined=__global_pointer$ rv64.o gp.lds -o gp.rv64 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn gp.rv32 > gp.rv32.dis +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn gp.rv64 > gp.rv64.dis +# RUN: FileCheck --check-prefix=GP %s < gp.rv32.dis +# RUN: FileCheck --check-prefix=GP %s < gp.rv64.dis + +# GP-NOT: auipc +# GP: addi a0, gp, -2048 +# GP-DAG: a1, -2048(gp) + +# RUN: echo 'SECTIONS { .text 0x100000 : { *(.text) } \ +# RUN: .sdata 0x200000 : { foo = . + 4096; } }' > nogp.lds +# RUN: ld.lld --undefined=__global_pointer$ rv32.o nogp.lds -o nogp.rv32 +# RUN: ld.lld --undefined=__global_pointer$ rv64.o nogp.lds -o nogp.rv64 +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn nogp.rv32 > nogp.rv32.dis +# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn nogp.rv64 > nogp.rv64.dis +# RUN: FileCheck --check-prefix=NOGP %s < nogp.rv32.dis +# RUN: FileCheck --check-prefix=NOGP %s < nogp.rv64.dis + +# NOGP: auipc a0, 257 +# NOGP-DAG: addi a0, a0, -4 +# NOGP-DAG: auipc a1, 257 +# NOGP-DAG: sw a1, -12(a1) + +.global _start +_start: + nop + 0: auipc a0, %pcrel_hi(foo) + addi a0, a0, %pcrel_lo(0b) + 1: auipc a1, %pcrel_hi(foo) + sw a1, %pcrel_lo(1b)(a1)