[lld] [ELF] -r: Synthesize R_RISCV_ALIGN at input section start (PR #151639)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 7 08:56:16 PDT 2025
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/151639
>From 047c1a53fe1c1b64ad40c17365ab57fe6c725571 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Thu, 31 Jul 2025 21:44:14 -0700
Subject: [PATCH 1/2] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
=?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Created using spr 1.3.5-bogner
---
lld/ELF/Arch/RISCV.cpp | 122 +++++++++++++++++++++++
lld/ELF/LinkerScript.cpp | 12 ++-
lld/ELF/OutputSections.cpp | 11 ++-
lld/ELF/Target.h | 3 +
lld/ELF/Writer.cpp | 2 +
lld/test/ELF/riscv-relocatable-align.s | 130 +++++++++++++++++++++++++
6 files changed, 276 insertions(+), 4 deletions(-)
create mode 100644 lld/test/ELF/riscv-relocatable-align.s
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 <class ELFT, class RelTy>
+ bool synthesizeAlignOne(uint64_t &dot, InputSection *sec, Relocs<RelTy> rels);
+ template <class ELFT, class RelTy>
+ void synthesizeAlignEnd(uint64_t &dot, InputSection *sec, Relocs<RelTy> rels);
+ template <class ELFT>
+ 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<std::pair<uint64_t, uint64_t>, 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 <class ELFT, class RelTy>
+bool RISCV::synthesizeAlignOne(uint64_t &dot, InputSection *sec,
+ Relocs<RelTy> 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 <class ELFT, class RelTy>
+void RISCV::synthesizeAlignEnd(uint64_t &dot, InputSection *sec,
+ Relocs<RelTy> rels) {
+ auto *f = cast<ObjFile<ELFT>>(baseSec->file);
+ auto shdr = f->template getELFShdrs<ELFT>()[baseSec->relSecIdx];
+ // Create a copy of InputSection.
+ sec = make<InputSection>(*f, shdr, baseSec->name);
+ auto *baseRelSec = cast<InputSection>(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<typename ELFT::Rela>(newSize);
+ sec->size = newSize * sizeof(typename ELFT::Rela);
+ sec->content_ = reinterpret_cast<uint8_t *>(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<InputSectionDescription>(cmd);
+ if (!isd)
+ continue;
+ for (auto *&isec : isd->sections)
+ if (isec == baseRelSec)
+ isec = sec;
+ }
+}
+
+template <class ELFT>
+bool RISCV::synthesizeAlignAux(uint64_t &dot, InputSection *sec) {
+ bool ret = false;
+ if (sec) {
+ invokeOnRelocs(*sec, ret = synthesizeAlignOne<ELFT>, dot, sec);
+ } else if (baseSec) {
+ invokeOnRelocs(*baseSec, synthesizeAlignEnd<ELFT>, 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<ELF64LE>(dot, sec);
+ return synthesizeAlignAux<ELF32LE>(dot, sec);
+}
+
void RISCV::finalizeRelax(int passes) const {
llvm::TimeTraceScope timeScope("Finalize RISC-V relaxation");
Log(ctx) << "relaxation passes: " << passes;
SmallVector<InputSection *, 0> 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<PotentialSpillSection>(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<uint8_t, 4> 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 <class ELFT> void Writer<ELFT>::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: <b0>:
+# CHECK-NEXT: 0: 00158513 addi a0, a1, 0x1
+# CHECK-NEXT: 4: 0001 c.nop
+# CHECK-NEXT: 6: 0001 c.nop
+# CHECK-EMPTY:
+# CHECK-NEXT: <b0>:
+# 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: <b0>:
+# 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: <b0>:
+# CHECK-NEXT: 28: 00158513 addi a0, a1, 0x1
+# CHECK-EMPTY:
+# CHECK-NEXT: <c0>:
+# 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) <c0>
+# CHECK-NEXT: 34: 0001 c.nop
+# CHECK-NEXT: 0000000000000034: R_RISCV_ALIGN *ABS*+0x2
+# CHECK-EMPTY:
+# CHECK-NEXT: <d0>:
+# CHECK-NEXT: 36: 00258513 addi a0, a1, 0x2
+
+# CHECK1: <_start>:
+# CHECK1-NEXT: 010000ef jal ra, 0x10010 <foo>
+# CHECK1-NEXT: 00000013 addi zero, zero, 0x0
+# CHECK1-EMPTY:
+# CHECK1-NEXT: <b0>:
+# CHECK1-NEXT: 00158513 addi a0, a1, 0x1
+# CHECK1-EMPTY:
+# CHECK1-NEXT: <d0>:
+# 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: <b0>:
+# 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
>From f4680623e3a140d45f95af0069c05016828c0047 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Thu, 31 Jul 2025 21:52:19 -0700
Subject: [PATCH 2/2] .
Created using spr 1.3.5-bogner
---
lld/ELF/Arch/RISCV.cpp | 1 -
lld/ELF/LinkerScript.cpp | 8 +++++---
lld/ELF/OutputSections.cpp | 3 +++
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index cea6e31ee15dd..cc15b8079055b 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -1084,7 +1084,6 @@ void RISCV::finalizeRelax(int passes) const {
llvm::TimeTraceScope timeScope("Finalize RISC-V relaxation");
Log(ctx) << "relaxation passes: " << passes;
SmallVector<InputSection *, 0> 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 95830aacf45ce..86d078ee34e71 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -1230,9 +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);
+ bool synthesizeAlign = ctx.arg.relocatable && ctx.arg.relax &&
+ (sec->flags & SHF_EXECINSTR) &&
+ ctx.arg.emachine == EM_RISCV;
// We visited SectionsCommands from processSectionCommands to
// layout sections. Now, we visit SectionsCommands again to fix
// section offsets.
@@ -1263,6 +1263,8 @@ bool LinkerScript::assignOffsets(OutputSection *sec) {
if (isa<PotentialSpillSection>(isec))
continue;
const uint64_t pos = dot;
+ // If synthesized ALIGN may be needed, call maybeSynthesizeAlign and
+ // disable the default handling if the return value is true.
if (!(synthesizeAlign && ctx.target->maybeSynthesizeAlign(dot, isec)))
dot = alignToPowerOf2(dot, isec->addralign);
isec->outSecOff = dot - sec->addr;
diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp
index 1ce4f2fd3b3f6..0b50f6d68ce5d 100644
--- a/lld/ELF/OutputSections.cpp
+++ b/lld/ELF/OutputSections.cpp
@@ -892,6 +892,9 @@ std::array<uint8_t, 4> OutputSection::getFiller(Ctx &ctx) {
if (!(flags & SHF_EXECINSTR))
return {0, 0, 0, 0};
if (ctx.arg.relocatable && ctx.arg.emachine == EM_RISCV) {
+ // See RISCV::maybeSynthesizeAlign: Synthesized NOP bytes and ALIGN
+ // relocations might be needed between two input sections. Use a NOP for the
+ // filler.
if (ctx.arg.eflags & EF_RISCV_RVC)
return {1, 0, 1, 0};
return {0x13, 0, 0, 0};
More information about the llvm-commits
mailing list