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). SmallVector relocDeltas; + // For relocations[i], the actual type is relocTypes[i]. + SmallVector relocTypes; + SmallVector writes; }; static void getSymbolAnchors() { @@ -498,6 +502,7 @@ for (InputSection *sec : getInputSections(*osec)) { sec->relaxAux = make(); sec->relaxAux->relocDeltas.resize(sec->relocations.size()); + sec->relaxAux->relocTypes.resize(sec->relocations.size()); } } // Store anchors (st_value and st_value+st_size) for symbols relative to text @@ -528,6 +533,36 @@ } } +// 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, int32_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 = + (!sym.isPreemptible && (!sym.isGnuIFunc() || config->zIfuncNoplt) + ? sym.getVA() + : sym.getPltVA()) + + 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; + } +} + // When relaxing just R_RISCV_ALIGN, relocDeltas is usually changed only once in // the absence of a linker script. For call and load/store R_RISCV_RELAX, code // shrinkage may reduce displacement and make more relocations eligible for @@ -566,6 +601,8 @@ sa = makeArrayRef(aux.anchors); delta = 0; + std::fill(aux.relocTypes.begin(), aux.relocTypes.end(), 0); + aux.writes.clear(); for (auto it : llvm::enumerate(sec->relocations)) { Relocation &r = it.value(); const size_t i = it.index(); @@ -580,6 +617,12 @@ assert(remove >= 0 && "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 @@ -627,7 +670,7 @@ auto &rels = sec->relocations; ArrayRef old = sec->rawData; - size_t newSize = old.size() - aux.relocDeltas.back(); + size_t newSize = old.size() - aux.relocDeltas.back(), writesIdx = 0; uint8_t *p = context().bAlloc.Allocate(newSize); uint64_t offset = 0; int32_t delta = 0; @@ -664,6 +707,20 @@ write16le(p + j, 0x0001); // c.nop } } + } else if (uint32_t 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; @@ -679,6 +736,8 @@ uint64_t cur = rels[i].offset; do { rels[i].offset -= delta; + if (aux.relocTypes[i]) + 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,136 @@ +# 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-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: <.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 +# RVC64: 0000000000010000 g .text 000000000000001a _start + +# 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: <.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 .high,"ax",@progbits + call _start@plt # relaxable for 32c + call _start@plt # not relaxed + +#--- b.s +.globl bar +bar: + ret + +#--- lds +SECTIONS { + .text 0x10000 : { *(.text) } + .plt 0x10400 : { *(.plt) } + .mid 0x10800 : { *(.mid) } + # 22 is the size of _start in 32c (RVC32). + .high 0x110000+(_start_end-_start)-22 : { *(.high) } +} 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 supports 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;