diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp index 72d83159ad8ac..cea6e31ee15dd 100644 --- a/lld/ELF/Arch/RISCV.cpp +++ b/lld/ELF/Arch/RISCV.cpp @@ -45,7 +45,18 @@ class RISCV final : public TargetInfo { uint64_t val) const override; void relocateAlloc(InputSectionBase &sec, uint8_t *buf) const override; bool relaxOnce(int pass) const override; + template + bool synthesizeAlignOne(uint64_t &dot, InputSection *sec, Relocs rels); + template + void synthesizeAlignEnd(uint64_t &dot, InputSection *sec, Relocs rels); + template + bool synthesizeAlignAux(uint64_t &dot, InputSection *sec); + bool maybeSynthesizeAlign(uint64_t &dot, InputSection *sec) override; void finalizeRelax(int passes) const override; + + // Used by synthesized ALIGN relocations. + InputSection *baseSec = nullptr; + SmallVector, 0> synthesizedAligns; }; } // end anonymous namespace @@ -959,10 +970,121 @@ bool RISCV::relaxOnce(int pass) const { return changed; } +// If the section alignment is >= 4, advance `dot` to insert NOPs and synthesize +// an ALIGN relocation. Otherwise, return false to use default handling. +template +bool RISCV::synthesizeAlignOne(uint64_t &dot, InputSection *sec, + Relocs rels) { + if (!baseSec) { + // Record the first section with RELAX relocations. + for (auto rel : rels) { + if (rel.getType(false) == R_RISCV_RELAX) { + baseSec = sec; + break; + } + } + } else if (sec->addralign >= 4) { + // If the alignment is >= 4 and the section does not start with an ALIGN + // relocation, synthesize one. + bool alignRel = false; + for (auto rel : rels) + if (rel.r_offset == 0 && rel.getType(false) == R_RISCV_ALIGN) + alignRel = true; + if (!alignRel) { + synthesizedAligns.emplace_back(dot - baseSec->getVA(), + sec->addralign - 2); + dot += sec->addralign - 2; + return true; + } + } + return false; +} + +// Finalize the relocation section by appending synthesized ALIGN relocations +// after processing all input sections. +template +void RISCV::synthesizeAlignEnd(uint64_t &dot, InputSection *sec, + Relocs rels) { + auto *f = cast>(baseSec->file); + auto shdr = f->template getELFShdrs()[baseSec->relSecIdx]; + // Create a copy of InputSection. + sec = make(*f, shdr, baseSec->name); + auto *baseRelSec = cast(f->getSections()[baseSec->relSecIdx]); + *sec = *baseRelSec; + baseSec = nullptr; + + // Allocate buffer for original and synthesized relocations in RELA format. + // If CREL is used, OutputSection::finalizeNonAllocCrel will convert RELA to + // CREL. + auto newSize = rels.size() + synthesizedAligns.size(); + auto *relas = makeThreadLocalN(newSize); + sec->size = newSize * sizeof(typename ELFT::Rela); + sec->content_ = reinterpret_cast(relas); + sec->type = SHT_RELA; + // Copy original relocations to the new buffer, potentially converting CREL to + // RELA. + for (auto [i, r] : llvm::enumerate(rels)) { + relas[i].r_offset = r.r_offset; + relas[i].setSymbolAndType(r.getSymbol(0), r.getType(0), false); + if constexpr (RelTy::HasAddend) + relas[i].r_addend = r.r_addend; + } + // Append synthesized ALIGN relocations to the buffer. + for (auto [i, r] : llvm::enumerate(synthesizedAligns)) { + auto &rela = relas[rels.size() + i]; + rela.r_offset = r.first; + rela.setSymbolAndType(0, R_RISCV_ALIGN, false); + rela.r_addend = r.second; + } + // Replace the old relocation section with the new one in the output section. + // addOrphanSections ensures that the output relocation section is processed + // after osec. + for (SectionCommand *cmd : sec->getParent()->commands) { + auto *isd = dyn_cast(cmd); + if (!isd) + continue; + for (auto *&isec : isd->sections) + if (isec == baseRelSec) + isec = sec; + } +} + +template +bool RISCV::synthesizeAlignAux(uint64_t &dot, InputSection *sec) { + bool ret = false; + if (sec) { + invokeOnRelocs(*sec, ret = synthesizeAlignOne, dot, sec); + } else if (baseSec) { + invokeOnRelocs(*baseSec, synthesizeAlignEnd, dot, sec); + } + return ret; +} + +// Without linker relaxation enabled for a particular relocatable file or +// section, the assembler will not generate R_RISCV_ALIGN relocations for +// alignment directives. This becomes problematic in a two-stage linking +// process: ld -r a.o b.o -o ab.o; ld ab.o -o ab. This function synthesizes an +// R_RISCV_ALIGN relocation at section start when needed. +// +// When called with an input section (`sec` is not null): If the section +// alignment is >= 4, advance `dot` to insert NOPs and synthesize an ALIGN +// relocation. +// +// When called after all input sections are processed (`sec` is null): The +// output relocation section is updated with all the newly synthesized ALIGN +// relocations. +bool RISCV::maybeSynthesizeAlign(uint64_t &dot, InputSection *sec) { + assert(ctx.arg.relocatable); + if (ctx.arg.is64) + return synthesizeAlignAux(dot, sec); + return synthesizeAlignAux(dot, sec); +} + void RISCV::finalizeRelax(int passes) const { llvm::TimeTraceScope timeScope("Finalize RISC-V relaxation"); Log(ctx) << "relaxation passes: " << passes; SmallVector storage; + for (OutputSection *osec : ctx.outputSections) { if (!(osec->flags & SHF_EXECINSTR)) continue; diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp index a5d08f4979dab..95830aacf45ce 100644 --- a/lld/ELF/LinkerScript.cpp +++ b/lld/ELF/LinkerScript.cpp @@ -1230,6 +1230,9 @@ bool LinkerScript::assignOffsets(OutputSection *sec) { if (sec->firstInOverlay) state->overlaySize = 0; + bool synthesizeAlign = + (sec->flags & SHF_EXECINSTR) && ctx.arg.relocatable && ctx.arg.relax && + is_contained({EM_RISCV, EM_LOONGARCH}, ctx.arg.emachine); // We visited SectionsCommands from processSectionCommands to // layout sections. Now, we visit SectionsCommands again to fix // section offsets. @@ -1260,7 +1263,8 @@ bool LinkerScript::assignOffsets(OutputSection *sec) { if (isa(isec)) continue; const uint64_t pos = dot; - dot = alignToPowerOf2(dot, isec->addralign); + if (!(synthesizeAlign && ctx.target->maybeSynthesizeAlign(dot, isec))) + dot = alignToPowerOf2(dot, isec->addralign); isec->outSecOff = dot - sec->addr; dot += isec->getSize(); @@ -1276,6 +1280,12 @@ bool LinkerScript::assignOffsets(OutputSection *sec) { if (ctx.in.relroPadding && sec == ctx.in.relroPadding->getParent()) expandOutputSection(alignToPowerOf2(dot, ctx.arg.commonPageSize) - dot); + if (synthesizeAlign) { + const uint64_t pos = dot; + ctx.target->maybeSynthesizeAlign(dot, nullptr); + expandOutputSection(dot - pos); + } + // Non-SHF_ALLOC sections do not affect the addresses of other OutputSections // as they are not part of the process image. if (!(sec->flags & SHF_ALLOC)) { diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp index 1020dd9f2569e..1ce4f2fd3b3f6 100644 --- a/lld/ELF/OutputSections.cpp +++ b/lld/ELF/OutputSections.cpp @@ -889,9 +889,14 @@ void OutputSection::sortInitFini() { std::array OutputSection::getFiller(Ctx &ctx) { if (filler) return *filler; - if (flags & SHF_EXECINSTR) - return ctx.target->trapInstr; - return {0, 0, 0, 0}; + if (!(flags & SHF_EXECINSTR)) + return {0, 0, 0, 0}; + if (ctx.arg.relocatable && ctx.arg.emachine == EM_RISCV) { + if (ctx.arg.eflags & EF_RISCV_RVC) + return {1, 0, 1, 0}; + return {0x13, 0, 0, 0}; + } + return ctx.target->trapInstr; } void OutputSection::checkDynRelAddends(Ctx &ctx) { diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h index fdc0c20f9cd02..1be2f04b5a726 100644 --- a/lld/ELF/Target.h +++ b/lld/ELF/Target.h @@ -96,6 +96,9 @@ class TargetInfo { // Do a linker relaxation pass and return true if we changed something. virtual bool relaxOnce(int pass) const { return false; } + virtual bool maybeSynthesizeAlign(uint64_t &dot, InputSection *sec) { + return false; + } // Do finalize relaxation after collecting relaxation infos. virtual void finalizeRelax(int passes) const {} diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp index 2b0e097766d2c..fdacc54282c2c 100644 --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -1543,6 +1543,8 @@ template void Writer::finalizeAddressDependentContent() { uint32_t pass = 0, assignPasses = 0; for (;;) { + if (ctx.arg.relocatable) + break; bool changed = ctx.target->needsThunks ? tc.createThunks(pass, ctx.outputSections) : ctx.target->relaxOnce(pass); diff --git a/lld/test/ELF/riscv-relocatable-align.s b/lld/test/ELF/riscv-relocatable-align.s new file mode 100644 index 0000000000000..9a782ed47850a --- /dev/null +++ b/lld/test/ELF/riscv-relocatable-align.s @@ -0,0 +1,130 @@ +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax a.s -o ac.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax b.s -o bc.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax b1.s -o b1c.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax c.s -o cc.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c d.s -o dc.o + +## No RELAX. Don't synthesize ALIGN. +# RUN: ld.lld -r bc.o dc.o -o bd.ro +# RUN: llvm-readelf -r bd.ro | FileCheck %s --check-prefix=NOREL + +# NOREL: no relocations + +# RUN: ld.lld -r bc.o bc.o ac.o bc.o b1c.o cc.o dc.o -o out.ro +# RUN: llvm-objdump -dr -M no-aliases out.ro | FileCheck %s + +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax a.s -o a.o +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax b.s -o b.o +# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax d.s -o d.o +# RUN: ld.lld -r a.o b.o d.o -o out0.ro +# RUN: ld.lld -Ttext=0x10000 out0.ro -o out0 +# RUN: llvm-objdump -dr -M no-aliases out0 | FileCheck %s --check-prefix=CHECK1 + +# CHECK: : +# CHECK-NEXT: 0: 00158513 addi a0, a1, 0x1 +# CHECK-NEXT: 4: 0001 c.nop +# CHECK-NEXT: 6: 0001 c.nop +# CHECK-EMPTY: +# CHECK-NEXT: : +# CHECK-NEXT: 8: 00158513 addi a0, a1, 0x1 +# CHECK-EMPTY: +# CHECK-NEXT: <_start>: +# CHECK-NEXT: c: 00000097 auipc ra, 0x0 +# CHECK-NEXT: 000000000000000c: R_RISCV_CALL_PLT foo +# CHECK-NEXT: 000000000000000c: R_RISCV_RELAX *ABS* +# CHECK-NEXT: 10: 000080e7 jalr ra, 0x0(ra) <_start> +# CHECK-NEXT: 14: 0001 c.nop +# CHECK-NEXT: 0000000000000014: R_RISCV_ALIGN *ABS*+0x6 +# CHECK-NEXT: 16: 0001 c.nop +# CHECK-NEXT: 18: 0001 c.nop +# CHECK-EMPTY: +# CHECK-NEXT: : +# CHECK-NEXT: 1a: 00158513 addi a0, a1, 0x1 +# CHECK-NEXT: 1e: 0001 c.nop +# CHECK-NEXT: 20: 0001 c.nop +# CHECK-NEXT: 0000000000000020: R_RISCV_ALIGN *ABS*+0x6 +# CHECK-NEXT: 22: 0001 c.nop +# CHECK-NEXT: 24: 00000013 addi zero, zero, 0x0 +# CHECK-EMPTY: +# CHECK-NEXT: : +# CHECK-NEXT: 28: 00158513 addi a0, a1, 0x1 +# CHECK-EMPTY: +# CHECK-NEXT: : +# CHECK-NEXT: 2c: 00000097 auipc ra, 0x0 +# CHECK-NEXT: 000000000000002c: R_RISCV_CALL_PLT foo +# CHECK-NEXT: 000000000000002c: R_RISCV_RELAX *ABS* +# CHECK-NEXT: 30: 000080e7 jalr ra, 0x0(ra) +# CHECK-NEXT: 34: 0001 c.nop +# CHECK-NEXT: 0000000000000034: R_RISCV_ALIGN *ABS*+0x2 +# CHECK-EMPTY: +# CHECK-NEXT: : +# CHECK-NEXT: 36: 00258513 addi a0, a1, 0x2 + +# CHECK1: <_start>: +# CHECK1-NEXT: 010000ef jal ra, 0x10010 +# CHECK1-NEXT: 00000013 addi zero, zero, 0x0 +# CHECK1-EMPTY: +# CHECK1-NEXT: : +# CHECK1-NEXT: 00158513 addi a0, a1, 0x1 +# CHECK1-EMPTY: +# CHECK1-NEXT: : +# CHECK1-NEXT: 00258513 addi a0, a1, 0x2 + +## Test CREL. +# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax --crel a.s -o acrel.o +# RUN: ld.lld -r acrel.o bc.o -o out1.ro +# RUN: llvm-objdump -dr -M no-aliases out1.ro | FileCheck %s --check-prefix=CHECK2 + +# CHECK2: <_start>: +# CHECK2-NEXT: 0: 00000097 auipc ra, 0x0 +# CHECK2-NEXT: 0000000000000000: R_RISCV_CALL_PLT foo +# CHECK2-NEXT: 0000000000000000: R_RISCV_RELAX *ABS* +# CHECK2-NEXT: 4: 000080e7 jalr ra, 0x0(ra) <_start> +# CHECK2-NEXT: 8: 0001 c.nop +# CHECK2-NEXT: 0000000000000008: R_RISCV_ALIGN *ABS*+0x6 +# CHECK2-NEXT: a: 0001 c.nop +# CHECK2-NEXT: c: 0001 c.nop +# CHECK2-EMPTY: +# CHECK2-NEXT: : +# CHECK2-NEXT: e: 00158513 addi a0, a1, 0x1 + +#--- a.s +.globl _start +_start: + call foo + +.section .text1,"ax" +.globl foo +foo: + +#--- b.s +## Needs synthesized ALIGN +.option push +.option norelax +.balign 8 +b0: + addi a0, a1, 1 +.option pop + +#--- b1.s +.option push +.option norelax + .reloc ., R_RISCV_ALIGN, 6 + addi x0, x0, 0 + c.nop +.balign 8 +b0: + addi a0, a1, 1 +.option pop + +#--- c.s +.balign 2 +c0: + call foo + +#--- d.s +## Needs synthesized ALIGN +.balign 4 +d0: + addi a0, a1, 2