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 @@ -270,11 +270,12 @@ case R_RISCV_TPREL_LO12_I: case R_RISCV_TPREL_LO12_S: return R_TPREL; - case R_RISCV_RELAX: case R_RISCV_TPREL_ADD: return R_NONE; case R_RISCV_ALIGN: return R_RELAX_HINT; + case R_RISCV_RELAX: + return config->relax ? R_RELAX_HINT : R_NONE; default: error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) + ") against symbol " + toString(s)); @@ -489,6 +490,9 @@ // For relocations[i], the actual offset is r_offset - (i ? relocDeltas[i-1] : // 0). std::unique_ptr relocDeltas; + // For relocations[i], the actual type is relocTypes[i]. + std::unique_ptr relocTypes; + SmallVector writes; }; static void initSymbolAnchors() { @@ -498,9 +502,12 @@ continue; for (InputSection *sec : getInputSections(*osec, storage)) { sec->relaxAux = make(); - if (sec->relocations.size()) + if (sec->relocations.size()) { sec->relaxAux->relocDeltas = std::make_unique(sec->relocations.size()); + sec->relaxAux->relocTypes = + std::make_unique(sec->relocations.size()); + } } } // Store anchors (st_value and st_value+st_size) for symbols relative to text @@ -533,6 +540,33 @@ } } +// Relax R_RISCV_CALL/R_RISCV_CALL_PLT auipc+jalr to c.j, c.jal, or jal. +static void relaxCall(const InputSection &sec, size_t i, uint64_t loc, + Relocation &r, uint32_t &remove) { + const bool rvc = config->eflags & EF_RISCV_RVC; + const Symbol &sym = *r.sym; + const uint64_t insnPair = read64le(sec.rawData.data() + r.offset); + const uint32_t rd = extractBits(insnPair, 32 + 11, 32 + 7); + const uint64_t dest = + (r.expr == R_PLT_PC ? sym.getPltVA() : sym.getVA()) + r.addend; + const int64_t displace = dest - loc; + + if (rvc && isInt<12>(displace) && rd == 0) { + sec.relaxAux->relocTypes[i] = R_RISCV_RVC_JUMP; + sec.relaxAux->writes.push_back(0xa001); // c.j + remove = 6; + } else if (rvc && isInt<12>(displace) && rd == X_RA && + !config->is64) { // RV32C only + sec.relaxAux->relocTypes[i] = R_RISCV_RVC_JUMP; + sec.relaxAux->writes.push_back(0x2001); // c.jal + remove = 6; + } else if (isInt<21>(displace)) { + sec.relaxAux->relocTypes[i] = R_RISCV_JAL; + sec.relaxAux->writes.push_back(0x6f | rd << 7); // jal + remove = 4; + } +} + static bool relax(InputSection &sec) { const uint64_t secAddr = sec.getVA(); auto &aux = *sec.relaxAux; @@ -553,6 +587,8 @@ sa = makeArrayRef(aux.anchors); delta = 0; + std::fill_n(aux.relocTypes.get(), sec.relocations.size(), R_RISCV_NONE); + aux.writes.clear(); for (auto it : llvm::enumerate(sec.relocations)) { Relocation &r = it.value(); const size_t i = it.index(); @@ -568,6 +604,12 @@ "R_RISCV_ALIGN needs expanding the content"); break; } + case R_RISCV_CALL: + case R_RISCV_CALL_PLT: + if (i + 1 != sec.relocations.size() && + sec.relocations[i + 1].type == R_RISCV_RELAX) + relaxCall(sec, i, loc, r, remove); + break; } // For all anchors whose offsets are <= r.offset, they are preceded by @@ -643,6 +685,7 @@ ArrayRef old = sec->rawData; size_t newSize = old.size() - aux.relocDeltas[sec->relocations.size() - 1]; + size_t writesIdx = 0; uint8_t *p = context().bAlloc.Allocate(newSize); uint64_t offset = 0; int64_t delta = 0; @@ -679,6 +722,20 @@ write16le(p + j, 0x0001); // c.nop } } + } else if (RelType newType = aux.relocTypes[i]) { + const uint32_t insn = aux.writes[writesIdx++]; + switch (newType) { + case R_RISCV_RVC_JUMP: + skip = 2; + write16le(p, insn); + break; + case R_RISCV_JAL: + skip = 4; + write32le(p, insn); + break; + default: + llvm_unreachable("unsupported type"); + } } p += skip; @@ -694,6 +751,8 @@ uint64_t cur = rels[i].offset; do { rels[i].offset -= delta; + if (aux.relocTypes[i] != R_RISCV_NONE) + rels[i].type = aux.relocTypes[i]; } while (++i != e && rels[i].offset == cur); delta = aux.relocDeltas[i - 1]; } diff --git a/lld/test/ELF/riscv-relax-call.s b/lld/test/ELF/riscv-relax-call.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/riscv-relax-call.s @@ -0,0 +1,160 @@ +# REQUIRES: riscv +## Relax R_RISCV_CALL and R_RISCV_CALL_PLT. + +# RUN: rm -rf %t && split-file %s %t && cd %t + +## Without RVC +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax a.s -o a.32.o +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax b.s -o b.32.o +# RUN: ld.lld -shared -soname=b.so b.32.o -o b.32.so +# RUN: ld.lld -T lds a.32.o b.32.so -o 32 +# RUN: llvm-objdump -td --no-show-raw-insn -M no-aliases 32 | FileCheck %s --check-prefix=NORVC + +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax a.s -o a.64.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax b.s -o b.64.o +# RUN: ld.lld -shared -soname=b.so b.64.o -o b.64.so +# RUN: ld.lld -T lds a.64.o b.64.so -o 64 +# RUN: llvm-objdump -td --no-show-raw-insn -M no-aliases 64 | FileCheck %s --check-prefix=NORVC + +## RVC +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+c,+relax a.s -o a.32c.o +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+c,+relax b.s -o b.32c.o +# RUN: ld.lld -shared -soname=b.so b.32c.o -o b.32c.so +# RUN: ld.lld -T lds a.32c.o b.32c.so -o 32c +# RUN: llvm-objdump -td --no-show-raw-insn -M no-aliases 32c | FileCheck %s --check-prefixes=RVC,RVC32 + +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax a.s -o a.64c.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax b.s -o b.64c.o +# RUN: ld.lld -shared -soname=b.so b.64c.o -o b.64c.so +# RUN: ld.lld -T lds a.64c.o b.64c.so -o 64c +# RUN: llvm-objdump -td --no-show-raw-insn -M no-aliases 64c | FileCheck %s --check-prefixes=RVC,RVC64 + +## --no-relax disables relaxation. +# RUN: ld.lld -T lds a.64.o b.64.so --no-relax -o 64.norelax +# RUN: llvm-objdump -td --no-show-raw-insn -M no-aliases 64.norelax | FileCheck %s --check-prefixes=NORELAX + +# NORVC: 00010000 g .text {{0*}}0000001c _start +# NORVC: 0001001c g .text {{0*}}00000000 _start_end +# NORVC: 00010808 g .mid {{0*}}00000000 mid_end +# NORVC: 00110016 g .high {{0*}}00000000 high_end + +# NORVC-LABEL: <_start>: +# NORVC-NEXT: 10000: jal zero, {{.*}} +# NORVC-NEXT: jal zero, {{.*}} +# NORVC-NEXT: addi zero, zero, 0 +# NORVC-NEXT: addi zero, zero, 0 +# NORVC-NEXT: 10010: jal ra, {{.*}} +# NORVC-NEXT: jal ra, 0x10420 +# NORVC-EMPTY: + +# NORVC-LABEL: <.mid>: +# NORVC-NEXT: 10800: jal ra, {{.*}} <_start> +# NORVC-NEXT: jal ra, {{.*}} <_start> +# NORVC-EMPTY: + +# NORVC-LABEL: <.mid2>: +# NORVC-NEXT: 1080c: jal ra, {{.*}} <_start> +# NORVC-EMPTY: + +# NORVC-LABEL: <.high>: +# NORVC-NEXT: 110006: auipc ra, 1048320 +# NORVC-NEXT: jalr ra, -6(ra) +# NORVC-NEXT: auipc ra, 1048320 +# NORVC-NEXT: jalr ra, -14(ra) +# NORVC-EMPTY: + +# RVC32: 00010000 g .text 00000016 _start +# RVC32: 00010016 g .text 00000000 _start_end +# RVC32: 00010806 g .mid 00000000 mid_end +# RVC32: 0011000c g .high 00000000 high_end +# RVC64: 0000000000010000 g .text 000000000000001a _start +# RVC64: 000000000001001a g .text 0000000000000000 _start_end +# RVC64: 0000000000010808 g .mid 0000000000000000 mid_end +# RVC64: 0000000000110014 g .high 0000000000000000 high_end + +# RVC-LABEL: <_start>: +# RVC-NEXT: 10000: c.j {{.*}} +# RVC-NEXT: c.j {{.*}} +# RVC-NEXT: addi zero, zero, 0 +# RVC-NEXT: addi zero, zero, 0 +# RVC-NEXT: addi zero, zero, 0 +# RVC32-NEXT: 10010: c.jal {{.*}} +# RVC32-NEXT: c.jal 0x10420 +# RVC64-NEXT: 10010: jal ra, {{.*}} +# RVC64-NEXT: jal ra, 0x10420 +# RVC-EMPTY: +# RVC-NEXT: : +# RVC-NEXT: c.jr ra +# RVC-EMPTY: + +# RVC-LABEL: <.mid>: +# RVC32-NEXT: 10800: c.jal {{.*}} <_start> +# RVC64-NEXT: 10800: jal ra, {{.*}} <_start> +# RVC-NEXT: jal ra, {{.*}} <_start> +# RVC-EMPTY: + +# RVC-LABEL: <.mid2>: +# RVC32-NEXT: 1080a: jal ra, {{.*}} <_start> +# RVC64-NEXT: 1080c: jal ra, {{.*}} <_start> +# RVC-EMPTY: + +# RVC-LABEL: <.high>: +# RVC32-NEXT: 110000: jal ra, 0x10000 <_start> +# RVC32-NEXT: auipc ra, 1048320 +# RVC32-NEXT: jalr ra, -4(ra) +# RVC64-NEXT: 110004: auipc ra, 1048320 +# RVC64-NEXT: jalr ra, -4(ra) +# RVC64-NEXT: auipc ra, 1048320 +# RVC64-NEXT: jalr ra, -12(ra) +# RVC-EMPTY: + +# NORELAX-LABEL: <_start>: +# NORELAX-NEXT: 10000: auipc t1, 0 +# NORELAX-NEXT: jalr zero, 32(t1) +# NORELAX-NEXT: auipc t0, 0 +# NORELAX-NEXT: jalr zero, 24(t0) +# NORELAX-NEXT: 10010: auipc ra, 0 +# NORELAX-NEXT: jalr ra, 16(ra) +# NORELAX-NEXT: auipc ra, 0 +# NORELAX-NEXT: jalr ra, 1032(ra) +# NORELAX-EMPTY: + +#--- a.s +.global _start, _start_end +_start: + tail a@plt + jump a, t0 +.balign 16 + call a # rv32c: c.jal; rv64c: jal + call bar # PLT call can be relaxed. rv32c: c.jal; rv64c: jal + +a: + ret +.size _start, . - _start +_start_end: + +.section .mid,"ax",@progbits + call _start@plt # rv32c: c.jal; rv64c: jal + call _start@plt + +.section .mid2,"ax",@progbits + call _start@plt + +.section .high,"ax",@progbits + call _start@plt # relaxable for %t/32c + call _start@plt # not relaxed + +#--- b.s +.globl bar +bar: + ret + +#--- lds +SECTIONS { + .text 0x10000 : { *(.text) } + .plt 0x10400 : { *(.plt) } + .mid 0x10800 : { *(.mid); mid_end = .; } + .mid2 mid_end+4 : { *(.mid2) } + # 22 is the size of _start in %t/32c (RVC32). + .high 0x110000+(_start_end-_start)-22 : { *(.high); high_end = .; } +} diff --git a/lld/test/ELF/riscv-relax-call2.s b/lld/test/ELF/riscv-relax-call2.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/riscv-relax-call2.s @@ -0,0 +1,58 @@ +# REQUIRES: riscv +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax a.s -o a.o + +# RUN: ld.lld -T lds a.o -o a +# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases a | FileCheck %s + +## Unsure whether this needs a diagnostic. GNU ld allows this. +# RUN: ld.lld -T lds -pie a.o -o a.pie +# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases a.pie | FileCheck %s + +# RUN: ld.lld -T lds -pie -z notext -z ifunc-noplt a.o -o a.ifunc-noplt +# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases a.ifunc-noplt | FileCheck %s --check-prefix=CHECK2 + +# CHECK-LABEL: <_start>: +# CHECK-NEXT: jal zero, 0x8 +# CHECK-NEXT: jal zero, 0x8 +# CHECK-NEXT: jal ra, 0x8 +# CHECK-NEXT: auipc t1, 1048320 +# CHECK-NEXT: jalr zero, -4(t1) +# CHECK-EMPTY: + +# CHECK-LABEL: <.mid>: +# CHECK-NEXT: jal zero, 0x101000 +# CHECK-NEXT: c.j 0x101000 +# CHECK-EMPTY: + +# CHECK2-LABEL: <.mid>: +# CHECK2-NEXT: auipc t1, 0 +# CHECK2-NEXT: jalr zero, 0(t1) +# CHECK2-NEXT: auipc t1, 0 +# CHECK2-NEXT: jalr zero, 0(t1) +# CHECK2-EMPTY: + +#--- a.s +.global _start, ifunc +_start: + jump abs, t2 + jump abs, t3 + call abs + tail abs # not relaxed + +.section .mid,"ax",@progbits +.balign 16 + tail ifunc@plt # not relaxed + tail ifunc@plt + +.type ifunc, @gnu_indirect_function +ifunc: + ret + +#--- lds +SECTIONS { + .text 0x100000 : { *(.text) } + .mid 0x100800 : { *(.mid) } + .iplt 0x101000 : { *(.iplt) } +} +abs = 8;