blob: e962db79756402f4f058aa52448a0d163acdf6a6 [file] [log] [blame]
diff --git lld/ELF/Arch/RISCV.cpp lld/ELF/Arch/RISCV.cpp
--- lld/ELF/Arch/RISCV.cpp
+++ lld/ELF/Arch/RISCV.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "InputFiles.h"
+#include "OutputSections.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
@@ -36,6 +37,7 @@
const uint8_t *loc) const override;
void relocate(uint8_t *loc, const Relocation &rel,
uint64_t val) const override;
+ void finalizeSections() const override;
};
} // end anonymous namespace
@@ -54,6 +56,7 @@
enum Reg {
X_RA = 1,
+ X_GP = 3,
X_T0 = 5,
X_T1 = 6,
X_T2 = 7,
@@ -267,16 +270,11 @@
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_RELAX:
case R_RISCV_ALIGN:
- // Not just a hint; always padded to the worst-case number of NOPs, so may
- // not currently be aligned, and without linker relaxation support we can't
- // delete NOPs to realign.
- errorOrWarn(getErrorLocation(loc) + "relocation R_RISCV_ALIGN requires "
- "unimplemented linker relaxation; recompile with -mno-relax");
- return R_NONE;
+ return R_RELAX_HINT;
default:
error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) +
") against symbol " + toString(s));
@@ -469,6 +467,7 @@
break;
case R_RISCV_RELAX:
+ case R_RISCV_TPREL_ADD:
return; // Ignored (for now)
default:
@@ -476,6 +475,306 @@
}
}
+static uint64_t maxOutputSectionAlignment() {
+ uint64_t maxAlign = 1;
+ for (auto *os : outputSections) {
+ maxAlign = std::max<uint64_t>(maxAlign, os->alignment);
+ }
+
+ return maxAlign;
+}
+
+static void setRs1(uint8_t *buf, int rs1) {
+ write32le(buf, (read32le(buf) & 0xfff07fff) | rs1 << 15);
+}
+
+static int64_t addWorstCaseAlignment(int64_t offset, uint64_t alignment) {
+ return offset >= 0 ? offset + alignment : offset - alignment;
+}
+
+using DeleteRanges = std::vector<InputSectionBase::DeleteRange>;
+
+static void addDeleteRange(DeleteRanges &ranges, uint64_t offset,
+ uint64_t size) {
+ ranges.push_back({offset, size});
+}
+
+// Relax R_RISCV_CALL to jal or c.jal.
+//
+// We always assume during relaxation the symbols can only come closer modulo
+// the effects of alignment.
+static bool relaxCall(InputSection *is, Relocation &rel,
+ DeleteRanges &deleteRanges) {
+ auto *sym = dyn_cast_or_null<Defined>(rel.sym);
+ if (!sym || !sym->section)
+ return false;
+
+ uint64_t pc = is->getVA(rel.offset);
+ uint64_t target = sym->getVA(rel.addend);
+ int64_t offset = target - pc;
+
+ // As the call site and callee may reside in different sections, we need to
+ // consider the worst case possible offset caused by alignment.
+ if (is->getOutputSection() != sym->getOutputSection()) {
+ offset = addWorstCaseAlignment(offset, maxOutputSectionAlignment());
+ } else if (is != sym->section) {
+ offset = addWorstCaseAlignment(offset, is->getOutputSection()->alignment);
+ }
+
+ bool rvc = config->eflags & EF_RISCV_RVC;
+ unsigned rd =
+ (read32le(is->data().data() + rel.offset + 4) & 0x00000fe0) >> 7;
+
+ // Convert to c.j or c.jal (RV32-only) if offset fits in 12 bits.
+ if (rvc && isInt<12>(offset) && rd == 0) {
+ write16le(is->mutableData().data() + rel.offset, 0xa001); // c.j 0
+ addDeleteRange(deleteRanges, rel.offset + 2, 6);
+ rel.type = R_RISCV_RVC_JUMP;
+ return true;
+ }
+
+ if (!config->is64 && rvc && isInt<12>(offset) && rd == 1) {
+ write16le(is->mutableData().data() + rel.offset, 0x2001); // c.jal 0
+ addDeleteRange(deleteRanges, rel.offset + 2, 6);
+ rel.type = R_RISCV_RVC_JUMP;
+ return true;
+ }
+
+ // Convert to jal if offset fits in 21 bits.
+ if (isInt<21>(offset)) {
+ write32le(is->mutableData().data() + rel.offset,
+ 0x0000006f | rd << 7); // jal rd, 0
+ addDeleteRange(deleteRanges, rel.offset + 4, 4);
+ rel.type = R_RISCV_JAL;
+ return true;
+ }
+
+ return false;
+}
+
+// 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 bool relaxHi20Lo12(InputSection *is, Relocation &rel,
+ DeleteRanges &deleteRanges) {
+ bool rvc = config->eflags & EF_RISCV_RVC;
+ uint64_t target = rel.sym->getVA(rel.addend);
+
+ Defined *gp = ElfSym::riscvGlobalPointer;
+
+ auto relaxToCLui = [&]() -> bool {
+ unsigned rd = (read32le(is->data().data() + rel.offset) & 0x00000fe0) >> 7;
+ if (rvc &&
+ isInt<6>(SignExtend64(target + 0x800, config->wordsize * 8) >> 12) &&
+ rd != 0 && rd != 2 && target != 0) {
+ write16le(is->mutableData().data() + rel.offset,
+ 0x6001 | rd << 7); // c.lui rd, 0
+ addDeleteRange(deleteRanges, rel.offset + 2, 2);
+ rel.type = R_RISCV_RVC_LUI;
+ return true;
+ }
+ return false;
+ };
+
+ if (!gp || rel.sym->getOutputSection() != gp->section->getOutputSection())
+ return rel.type == R_RISCV_HI20 ? relaxToCLui() : false;
+
+ uint64_t offset = target - gp->getVA();
+
+ if (isInt<12>(offset)) {
+ if (rel.type == R_RISCV_HI20) {
+ addDeleteRange(deleteRanges, rel.offset, 4);
+ rel.type = R_RISCV_NONE;
+ rel.expr = R_NONE;
+ } else { // R_RISCV_LO12_[IS]
+ setRs1(is->mutableData().data() + rel.offset, X_GP);
+ rel.expr = R_RISCV_GPREL;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+// Relaxing PCREL relocations requires two passes due to the linkage from
+// LO12 to HI20. The first pass only relaxes PCREL_LO12 and the second one
+// relaxes PCREL_HI20.
+static bool relaxPcrel(InputSection *is, Relocation &rel,
+ DeleteRanges &deleteRanges) {
+ Defined *gp = ElfSym::riscvGlobalPointer;
+ if (!gp)
+ return false;
+
+ const Relocation *hi20 = &rel;
+ if (rel.type == R_RISCV_PCREL_LO12_I || rel.type == R_RISCV_PCREL_LO12_S) {
+ hi20 = getRISCVPCRelHi20(rel.sym, rel.addend);
+ if (!hi20)
+ return false;
+ }
+
+ if (hi20->sym->getOutputSection() != gp->section->getOutputSection())
+ return false;
+
+ uint64_t target = hi20->sym->getVA(hi20->addend);
+ uint64_t offset = target - gp->getVA();
+
+ if (isInt<12>(offset)) {
+ if (rel.type == R_RISCV_PCREL_HI20) {
+ addDeleteRange(deleteRanges, rel.offset, 4);
+ rel.type = R_RISCV_NONE;
+ rel.expr = R_NONE;
+ } else {
+ setRs1(is->mutableData().data() + rel.offset, X_GP);
+ rel.sym = hi20->sym;
+ rel.addend = hi20->addend + rel.addend;
+ rel.type =
+ rel.type == R_RISCV_PCREL_LO12_I ? R_RISCV_LO12_I : R_RISCV_LO12_S;
+ rel.expr = R_RISCV_GPREL;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+template <typename F>
+void processRelaxations(MutableArrayRef<Relocation> rels, F f) {
+ if (rels.empty())
+ return;
+
+ for (auto *r = rels.begin() + 1, *e = rels.end(); r != e; ++r) {
+ if (r->type != R_RISCV_RELAX)
+ continue;
+
+ Relocation *rel = std::prev(r);
+ if (r->offset != rel->offset)
+ continue;
+
+ if (f(*rel)) {
+ r->type = R_RISCV_NONE;
+ r->expr = R_NONE;
+ }
+ }
+}
+
+static bool relax() {
+ bool changed = false;
+
+ for (OutputSection *os : outputSections) {
+ for (InputSection *is : getInputSections(*os)) {
+ if (!(is->flags & SHF_EXECINSTR))
+ continue;
+
+ DeleteRanges deleteRanges;
+ processRelaxations(is->relocations, [&](Relocation &rel) {
+ switch (rel.type) {
+ case R_RISCV_CALL:
+ case R_RISCV_CALL_PLT:
+ return relaxCall(is, rel, deleteRanges);
+ case R_RISCV_HI20:
+ case R_RISCV_LO12_I:
+ case R_RISCV_LO12_S:
+ return relaxHi20Lo12(is, rel, deleteRanges);
+ case R_RISCV_PCREL_LO12_I:
+ case R_RISCV_PCREL_LO12_S:
+ return relaxPcrel(is, rel, deleteRanges);
+ }
+
+ return false;
+ });
+
+ // The second-pass for PCREL_HI20 relaxation.
+ processRelaxations(is->relocations, [&](Relocation &rel) {
+ if (rel.type != R_RISCV_PCREL_HI20)
+ return false;
+
+ return relaxPcrel(is, rel, deleteRanges);
+ });
+
+ using DeleteRange = InputSectionBase::DeleteRange;
+ llvm::sort(deleteRanges,
+ [](const DeleteRange &lhs, const DeleteRange &rhs) {
+ return lhs.offset < rhs.offset;
+ });
+
+ is->deleteRanges(deleteRanges);
+ script->assignAddresses();
+ changed |= !deleteRanges.empty();
+ }
+ }
+
+ return changed;
+}
+
+static void relaxAlign() {
+ bool rvc = config->eflags & EF_RISCV_RVC;
+
+ for (OutputSection *os : outputSections) {
+ for (InputSection *is : getInputSections(*os)) {
+ if (!(is->flags & SHF_EXECINSTR))
+ continue;
+
+ uint64_t bytesDeleted = 0;
+ DeleteRanges deleteRanges;
+ for (auto &rel : is->relocations) {
+ if (rel.type == R_RISCV_ALIGN && rel.addend > 0) {
+ uint64_t pc = is->getVA(rel.offset) - bytesDeleted;
+ uint64_t alignment = PowerOf2Ceil(rel.addend + 2);
+ uint64_t nopBytes = alignTo(pc, alignment) - pc;
+
+ if (nopBytes % 2 != 0 || (!rvc && nopBytes % 4 != 0)) {
+ errorOrWarn(is->getObjMsg(rel.offset) + ": alignment requires " +
+ Twine(nopBytes) + " of nop");
+ break;
+ }
+
+ if (nopBytes > (uint64_t)rel.addend) {
+ errorOrWarn(is->getObjMsg(rel.offset) + ": alignment requires " +
+ Twine(nopBytes) + " of nop, but only " +
+ Twine(rel.addend) + " bytes are available");
+ break;
+ }
+ uint64_t bytesToDelete = rel.addend - nopBytes;
+
+ if (bytesToDelete > 0) {
+ addDeleteRange(deleteRanges, rel.offset + nopBytes, bytesToDelete);
+ bytesDeleted += bytesToDelete;
+ }
+
+ uint8_t *buf = is->mutableData().data() + rel.offset;
+ while (nopBytes != 0) {
+ if (nopBytes >= 4) {
+ write32le(buf, 0x00000013); // nop
+ nopBytes -= 4;
+ buf += 4;
+ } else if (rvc && nopBytes == 2) {
+ write16le(buf, 0x0001); // c.nop
+ nopBytes -= 2;
+ buf += 2;
+ }
+ }
+ }
+ }
+
+ is->deleteRanges(deleteRanges);
+ script->assignAddresses();
+ }
+ }
+}
+
+void RISCV::finalizeSections() const {
+ // Can't perform relaxation if it is not a final link.
+ if (config->relocatable)
+ return;
+
+ if (config->relax)
+ while (relax())
+ ;
+
+ relaxAlign();
+}
+
TargetInfo *elf::getRISCVTargetInfo() {
static RISCV target;
return &target;
diff --git lld/ELF/InputSection.h lld/ELF/InputSection.h
--- lld/ELF/InputSection.h
+++ lld/ELF/InputSection.h
@@ -12,7 +12,9 @@
#include "Config.h"
#include "Relocations.h"
#include "Thunks.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/LLVM.h"
+#include "lld/Common/Memory.h"
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/TinyPtrVector.h"
@@ -162,6 +164,29 @@
return rawData;
}
+ MutableArrayRef<uint8_t> mutableData() const {
+ if (!copiedData) {
+ size_t size = data().size();
+ uint8_t *mutData = context().bAlloc.Allocate<uint8_t>(size);
+ memcpy(mutData, data().data(), size);
+ rawData = llvm::makeArrayRef(mutData, size);
+ copiedData = true;
+ }
+
+ return llvm::makeMutableArrayRef(const_cast<uint8_t *>(rawData.data()),
+ rawData.size());
+ }
+
+ // A pair of range to delete in (offset, size)
+ struct DeleteRange {
+ uint64_t offset;
+ uint64_t size;
+ };
+
+ // Delete ranges and adjust section content, symbols and relocations.
+ // The deleteRanges must be sorted by offset and must not overlap.
+ void deleteRanges(ArrayRef<DeleteRange> deleteRanges);
+
// The next member in the section group if this section is in a group. This is
// used by --gc-sections.
InputSectionBase *nextInSectionGroup = nullptr;
@@ -229,6 +254,7 @@
void uncompress() const;
mutable ArrayRef<uint8_t> rawData;
+ mutable bool copiedData;
// This field stores the uncompressed size of the compressed data in rawData,
// or -1 if rawData is not compressed (either because the section wasn't
@@ -383,7 +409,7 @@
template <class ELFT> void copyShtGroup(uint8_t *buf);
};
-static_assert(sizeof(InputSection) <= 160, "InputSection is too big");
+static_assert(sizeof(InputSection) <= 168, "InputSection is too big");
inline bool isDebugSection(const InputSectionBase &sec) {
return (sec.flags & llvm::ELF::SHF_ALLOC) == 0 &&
@@ -398,6 +424,7 @@
// STT_SECTION symbol associated to the .toc input section.
extern llvm::DenseSet<std::pair<const Symbol *, uint64_t>> ppc64noTocRelax;
+Relocation *getRISCVPCRelHi20(const Symbol *sym, uint64_t addend);
} // namespace elf
std::string toString(const elf::InputSectionBase *);
diff --git lld/ELF/InputSection.cpp lld/ELF/InputSection.cpp
--- lld/ELF/InputSection.cpp
+++ lld/ELF/InputSection.cpp
@@ -56,7 +56,7 @@
StringRef name, Kind sectionKind)
: SectionBase(sectionKind, name, flags, entsize, alignment, type, info,
link),
- file(file), rawData(data) {
+ file(file), rawData(data), copiedData(false) {
// In order to reduce memory allocation, we assume that mergeable
// sections are smaller than 4 GiB, which is not an unreasonable
// assumption as of 2017.
@@ -150,6 +150,86 @@
return ret;
}
+void InputSectionBase::deleteRanges(ArrayRef<DeleteRange> ranges) {
+ if (ranges.empty())
+ return;
+
+ // Adjust all symbol offsets and sizes within the InputSection, using the
+ // following algorithm to avoid quadratic behavior.
+
+ // Gather all symbols within the section.
+ SmallVector<Defined *> symbols;
+ for (auto &sym : file->getSymbols()) {
+ auto *dr = dyn_cast<Defined>(sym);
+ if (!dr || dr->section != this)
+ continue;
+
+ symbols.push_back(dr);
+ }
+
+ // Sort symbols by their starting address.
+ llvm::sort(symbols, [](const Defined *a, const Defined *b) {
+ return a->value < b->value;
+ });
+
+ // Adjust each symbol's address by bytes deleted and also enlarge the symbol's
+ // size to keep its "end" fixed.
+ {
+ uint64_t removedBytes = 0;
+ const auto *r = ranges.begin(), *rend = ranges.end();
+ for (auto *dr : symbols) {
+ for (; r != rend && r->offset < dr->value; ++r)
+ removedBytes += r->size;
+
+ dr->value -= removedBytes;
+ dr->size += removedBytes;
+ }
+ }
+
+ const auto endOff = [](const Defined *dr) { return dr->value + dr->size; };
+
+ // Sort symbols by their "end" address before relaxation.
+ llvm::sort(symbols, [&](const Defined *a, const Defined *b) {
+ return endOff(a) < endOff(b);
+ });
+
+ // Adjust each symbol's end address to their actual end by reducing size.
+ {
+ uint64_t removedBytes = 0;
+ const auto *r = ranges.begin(), *rend = ranges.end();
+ for (auto *dr : symbols) {
+ for (; r != rend && r->offset < endOff(dr); ++r)
+ removedBytes += r->size;
+
+ dr->size -= removedBytes;
+ }
+ }
+
+ // Adjust relocation offsets within the section.
+ uint64_t removedBytes = 0;
+ const auto *r = ranges.begin(), *rend = ranges.end();
+ for (auto &rel : relocations) {
+ for (; r != rend && r->offset < rel.offset; ++r)
+ removedBytes += r->size;
+
+ rel.offset -= removedBytes;
+ }
+
+ // Adjust section content piece-wise and resize the section.
+ MutableArrayRef<uint8_t> buf = this->mutableData();
+ auto *dst = buf.begin() + ranges.begin()->offset;
+ for (auto it = ranges.begin(), e = ranges.end(); it != e; ++it) {
+ auto *from = buf.begin() + it->offset + it->size;
+ auto *to = std::next(it) != ranges.end()
+ ? (buf.begin() + std::next(it)->offset)
+ : buf.end();
+ dst = std::copy(from, to, dst);
+ }
+
+ // Resize the section
+ rawData = makeArrayRef(data().data(), dst);
+}
+
uint64_t SectionBase::getOffset(uint64_t offset) const {
switch (kind()) {
case Output: {
@@ -554,7 +634,7 @@
//
// 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) {
+Relocation *lld::elf::getRISCVPCRelHi20(const Symbol *sym, uint64_t addend) {
const Defined *d = cast<Defined>(sym);
if (!d->section) {
error("R_RISCV_PCREL_LO12 relocation points to an absolute symbol: " +
@@ -717,6 +797,13 @@
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))
return getRelocTargetVA(file, hiRel->type, hiRel->addend, sym.getVA(),
@@ -997,7 +1084,7 @@
AArch64Relaxer aarch64relaxer(relocations);
for (size_t i = 0, size = relocations.size(); i != size; ++i) {
const Relocation &rel = relocations[i];
- if (rel.expr == R_NONE)
+ if (rel.expr == R_NONE || rel.expr == R_RELAX_HINT)
continue;
uint64_t offset = rel.offset;
uint8_t *bufLoc = buf + offset;
diff --git lld/ELF/Relocations.h lld/ELF/Relocations.h
--- lld/ELF/Relocations.h
+++ lld/ELF/Relocations.h
@@ -46,6 +46,7 @@
R_PLT,
R_PLT_PC,
R_PLT_GOTPLT,
+ R_RELAX_HINT,
R_RELAX_GOT_PC,
R_RELAX_GOT_PC_NOPIC,
R_RELAX_TLS_GD_TO_IE,
@@ -101,6 +102,7 @@
R_PPC64_TOCBASE,
R_PPC64_RELAX_GOT_PC,
R_RISCV_ADD,
+ R_RISCV_GPREL,
R_RISCV_PC_INDIRECT,
};
diff --git lld/ELF/Relocations.cpp lld/ELF/Relocations.cpp
--- lld/ELF/Relocations.cpp
+++ lld/ELF/Relocations.cpp
@@ -960,7 +960,8 @@
R_MIPS_GOT_OFF, R_MIPS_GOT_OFF32, R_MIPS_GOT_GP_PC,
R_AARCH64_GOT_PAGE_PC, R_GOT_PC, R_GOTONLY_PC, R_GOTPLTONLY_PC,
R_PLT_PC, R_PLT_GOTPLT, R_PPC32_PLTREL, R_PPC64_CALL_PLT,
- R_PPC64_RELAX_TOC, R_RISCV_ADD, R_AARCH64_GOT_PAGE>(e))
+ R_PPC64_RELAX_TOC, R_RISCV_ADD, R_RELAX_HINT, R_AARCH64_GOT_PAGE>(
+ e))
return true;
// These never do, except if the entire file is position dependent or if
diff --git lld/ELF/Target.h lld/ELF/Target.h
--- lld/ELF/Target.h
+++ lld/ELF/Target.h
@@ -92,6 +92,8 @@
virtual void applyJumpInstrMod(uint8_t *loc, JumpModType type,
JumpModType val) const {}
+ virtual void finalizeSections() const {}
+
virtual ~TargetInfo();
// This deletes a jump insn at the end of the section if it is a fall thru to
diff --git lld/ELF/Writer.cpp lld/ELF/Writer.cpp
--- lld/ELF/Writer.cpp
+++ lld/ELF/Writer.cpp
@@ -1628,6 +1628,8 @@
if (config->emachine == EM_HEXAGON)
hexagonTLSSymbolUpdate(outputSections);
+ target->finalizeSections();
+
int assignPasses = 0;
for (;;) {
bool changed = target->needsThunks && tc.createThunks(outputSections);
diff --git lld/test/ELF/riscv-gp.s lld/test/ELF/riscv-gp.s
--- lld/test/ELF/riscv-gp.s
+++ 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 lld/test/ELF/riscv-relax-align-rvc.s lld/test/ELF/riscv-relax-align-rvc.s
new file mode 100644
--- /dev/null
+++ lld/test/ELF/riscv-relax-align-rvc.s
@@ -0,0 +1,31 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+c,+relax %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+c,+relax %s -o %t.rv64.o
+
+# RUN: ld.lld %t.rv32.o -o %t.rv32
+# RUN: ld.lld %t.rv64.o -o %t.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32 | FileCheck %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64 | FileCheck %s
+
+# CHECK: c.add a0, a1
+# CHECK-NEXT: addi zero, zero, 0
+# CHECK-NEXT: addi zero, zero, 0
+# CHECK-NEXT: addi zero, zero, 0
+# CHECK-NEXT: c.nop
+# CHECK-NEXT: c.add s0, s1
+# CHECK-NEXT: c.add s2, s3
+# CHECK-NEXT: c.add s4, s5
+# CHECK-NEXT: c.nop
+# CHECK-NEXT: c.add t0, t1
+
+.global _start
+_start:
+.balign 4
+ c.add a0, a1
+.balign 16
+ c.add s0, s1
+ c.add s2, s3
+ c.add s4, s5
+.balign 8
+ c.add t0, t1
diff --git lld/test/ELF/riscv-relax-align.s lld/test/ELF/riscv-relax-align.s
new file mode 100644
--- /dev/null
+++ lld/test/ELF/riscv-relax-align.s
@@ -0,0 +1,33 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax %s -o %t.rv64.o
+
+# RUN: ld.lld %t.rv32.o -o %t.rv32
+# RUN: ld.lld %t.rv64.o -o %t.rv64
+# RUN: llvm-objdump -d --no-show-raw-insn %t.rv32 | FileCheck %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t.rv64 | FileCheck %s
+
+# Check that alignment is always handled regardless of --relax option
+# RUN: ld.lld --no-relax %t.rv32.o -o %t-no-relax.rv32
+# RUN: ld.lld --no-relax %t.rv64.o -o %t-no-relax.rv64
+# RUN: llvm-objdump -d --no-show-raw-insn %t-no-relax.rv32 | FileCheck %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t-no-relax.rv64 | FileCheck %s
+
+# CHECK: add a0, a1, a2
+# CHECK-NEXT: add a3, a4, a5
+# CHECK-NEXT: nop
+# CHECK-NEXT: nop
+# CHECK-NEXT: add s0, s1, s2
+# CHECK-NEXT: add t0, t1, t2
+
+.global _start
+_start:
+.balign 4
+ add a0, a1, a2
+ add a3, a4, a5
+.balign 16
+ add s0, s1, s2
+.balign 4
+.balign 4
+ add t0, t1, t2
diff --git lld/test/ELF/riscv-relax-call.s lld/test/ELF/riscv-relax-call.s
new file mode 100644
--- /dev/null
+++ lld/test/ELF/riscv-relax-call.s
@@ -0,0 +1,72 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax %s -o %t.rv64.o
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+c,+relax %s -o %t.rv32c.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+c,+relax %s -o %t.rv64c.o
+
+# jal relaxation
+# RUN: ld.lld %t.rv32.o --defsym foo=_start+20 -o %t.rv32
+# RUN: ld.lld %t.rv64.o --defsym foo=_start+20 -o %t.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32 | FileCheck --check-prefix=JAL %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64 | FileCheck --check-prefix=JAL %s
+# JAL: jal ra, {{.*}} <foo>
+# JAL-NEXT: jal zero, {{.*}} <foo>
+
+# c.j and c.jal (RV32C-only) relaxation
+# RUN: ld.lld %t.rv32c.o --defsym foo=_start+20 -o %t.rv32c
+# RUN: ld.lld %t.rv64c.o --defsym foo=_start+20 -o %t.rv64c
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32c | FileCheck --check-prefix=RV32C %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64c | FileCheck --check-prefix=RV64C %s
+# RV32C: c.jal {{.*}} <foo>
+# RV32C-NEXT: c.j {{.*}} <foo>
+# RV64C: jal ra, {{.*}} <foo>
+# RV64C-NEXT: c.j {{.*}} <foo>
+
+# Don't relax to c.j/c.jal if out of range
+# RUN: ld.lld %t.rv32c.o --defsym foo=_start+0x1004 -o %t.rv32c
+# RUN: ld.lld %t.rv64c.o --defsym foo=_start+0x1004 -o %t.rv64c
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32c | FileCheck --check-prefix=JAL %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64c | FileCheck --check-prefix=JAL %s
+
+# Don't relax if out of range (for the first call)
+# RUN: ld.lld %t.rv32c.o --defsym foo=_start+0x100000 -o %t-boundary.rv32
+# RUN: ld.lld %t.rv64c.o --defsym foo=_start+0x100000 -o %t-boundary.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-boundary.rv32 | FileCheck --check-prefix=BOUNDARY %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-boundary.rv64 | FileCheck --check-prefix=BOUNDARY %s
+# BOUNDARY: auipc ra, 256
+# BOUNDARY-NEXT: jalr ra, 0(ra)
+# BOUNDARY-NEXT: jal zero, {{.*}} <foo>
+
+# Check relaxation works across output sections
+# RUN: echo 'SECTIONS { .text 0x100000 : { *(.text) } .foo : ALIGN(8) { foo = .; } }' > %t-cross-section.lds
+# RUN: ld.lld %t.rv32c.o %t-cross-section.lds -o %t-cross-section.rv32
+# RUN: ld.lld %t.rv64c.o %t-cross-section.lds -o %t-cross-section.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-cross-section.rv32 | FileCheck --check-prefix=RV32C %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-cross-section.rv64 | FileCheck --check-prefix=RV64C %s
+
+# Test for output section alignment checking during relaxation. The .foo section
+# cannot be moved closer due to alignment so lld must not relax the call, even
+# though it seems it may be in range before relaxation.
+
+# RUN: echo 'SECTIONS { .text 0x100000 : { *(.text); } .foo : ALIGN(0x100000) { foo = .; } }' > %t-cross-section-out-of-range.lds
+# RUN: ld.lld %t.rv32c.o %t-cross-section-out-of-range.lds -o %t-cross-section-out-of-range.rv32
+# RUN: ld.lld %t.rv64c.o %t-cross-section-out-of-range.lds -o %t-cross-section-out-of-range.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-cross-section-out-of-range.rv32 | FileCheck --check-prefix=NORELAX %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-cross-section-out-of-range.rv64 | FileCheck --check-prefix=NORELAX %s
+# NORELAX: auipc ra, {{.*}}
+# NORELAX-NEXT: jalr ra, {{.*}}(ra)
+# NORELAX: auipc t1, {{.*}}
+# NORELAX-NEXT: jalr zero, {{.*}}(t1)
+
+# Don't relax to absolute symbols
+# RUN: ld.lld %t.rv32c.o -Ttext=0x100000 --defsym foo=0x100000 -o %t-abs.rv32
+# RUN: ld.lld %t.rv64c.o -Ttext=0x100000 --defsym foo=0x100000 -o %t-abs.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-abs.rv32 | FileCheck --check-prefix=NORELAX %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-abs.rv64 | FileCheck --check-prefix=NORELAX %s
+
+.global _start
+.p2align 3
+_start:
+ call foo
+ tail foo
diff --git lld/test/ELF/riscv-relax-hi20-lo12.s lld/test/ELF/riscv-relax-hi20-lo12.s
new file mode 100644
--- /dev/null
+++ lld/test/ELF/riscv-relax-hi20-lo12.s
@@ -0,0 +1,51 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax %s -o %t.rv64.o
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+c,+relax %s -o %t.rv32c.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+c,+relax %s -o %t.rv64c.o
+
+# RUN: echo 'SECTIONS { .text : { *(.text) } .sdata 0x200000 : { foo = .; } }' > %t.lds
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv32.o %t.lds -o %t.rv32
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv64.o %t.lds -o %t.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32 | FileCheck --check-prefix=GP %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64 | FileCheck --check-prefix=GP %s
+# 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) } .sdata 0x200000 : { foo = . + 4096; } }' > %t-out-of-range.lds
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv32.o %t-out-of-range.lds -o %t.rv32-out-of-range
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv64.o %t-out-of-range.lds -o %t.rv64-out-of-range
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32-out-of-range | FileCheck --check-prefix=NORELAX %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64-out-of-range | FileCheck --check-prefix=NORELAX %s
+# NORELAX: lui a0, 513
+# NORELAX-NEXT: addi a0, a0, 0
+# NORELAX-NEXT: lw a0, 0(a0)
+# NORELAX-NEXT: sw a0, 0(a0)
+
+# RUN: ld.lld --defsym=foo=0x1000 %t.rv32c.o -o %t.rv32-clui
+# RUN: ld.lld --defsym=foo=0x1000 %t.rv64c.o -o %t.rv64-clui
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32-clui | FileCheck --check-prefix=CLUI %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64-clui | FileCheck --check-prefix=CLUI %s
+# 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 %t.rv32c.o -o %t.rv32-cli
+# RUN: ld.lld --defsym=foo=0x10 %t.rv64c.o -o %t.rv64-cli
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32-cli | FileCheck --check-prefix=CLI %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64-cli | FileCheck --check-prefix=CLI %s
+# 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 lld/test/ELF/riscv-relax-pcrel.s lld/test/ELF/riscv-relax-pcrel.s
new file mode 100644
--- /dev/null
+++ lld/test/ELF/riscv-relax-pcrel.s
@@ -0,0 +1,28 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax %s -o %t.rv64.o
+
+# RUN: echo 'SECTIONS { .text 0x100000 : { *(.text) } .sdata 0x200000 : { foo = .; } }' > %t.lds
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv32.o %t.lds -o %t.rv32
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv64.o %t.lds -o %t.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv32 | FileCheck --check-prefix=GP %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t.rv64 | FileCheck --check-prefix=GP %s
+# GP-NOT: auipc
+# GP: addi a0, gp, -2048
+# GP-NEXT: sw a0, -2048(gp)
+
+# RUN: echo 'SECTIONS { .text 0x100000 : { *(.text) } .sdata 0x200000 : { foo = . + 4096; } }' > %t-norelax.lds
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv32.o %t-norelax.lds -o %t-norelax.rv32
+# RUN: ld.lld --undefined=__global_pointer$ %t.rv64.o %t-norelax.lds -o %t-norelax.rv64
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-norelax.rv32 | FileCheck --check-prefix=NORELAX %s
+# RUN: llvm-objdump -d -M no-aliases --no-show-raw-insn %t-norelax.rv64 | FileCheck --check-prefix=NORELAX %s
+# NORELAX: auipc a0, 257
+# NORELAX-NEXT: addi a0, a0, 0
+# NORELAX-NEXT: sw a0, 0(a0)
+
+.global _start
+_start:
+ auipc a0, %pcrel_hi(foo)
+ addi a0, a0, %pcrel_lo(_start)
+ sw a0, %pcrel_lo(_start)(a0)
diff --git lld/test/ELF/riscv-relax-syms.s lld/test/ELF/riscv-relax-syms.s
new file mode 100644
--- /dev/null
+++ lld/test/ELF/riscv-relax-syms.s
@@ -0,0 +1,32 @@
+# REQUIRES: riscv
+
+// Check that relaxation correctly adjusts symbol addresses and sizes.
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax %s -o %t.rv64.o
+# RUN: ld.lld -Ttext=0x100000 %t.rv32.o -o %t.rv32
+# RUN: ld.lld -Ttext=0x100000 %t.rv64.o -o %t.rv64
+
+# RUN: llvm-readelf -s %t.rv32 | FileCheck %s
+# RUN: llvm-readelf -s %t.rv64 | FileCheck %s
+
+# CHECK: 100000 4 NOTYPE LOCAL DEFAULT 1 a
+# CHECK: 100000 8 NOTYPE LOCAL DEFAULT 1 b
+# CHECK: 100004 4 NOTYPE LOCAL DEFAULT 1 c
+# CHECK: 100004 8 NOTYPE LOCAL DEFAULT 1 d
+# CHECK: 100000 12 NOTYPE GLOBAL DEFAULT 1 _start
+
+.global _start
+_start:
+a:
+b:
+ add a0, a1, a2
+.size a, . - a
+c:
+d:
+ call _start
+.size b, . - b
+.size c, . - c
+ add a0, a1, a2
+.size d, . - d
+.size _start, . - _start
diff --git lld/test/ELF/riscv-reloc-align.s lld/test/ELF/riscv-reloc-align.s
deleted file mode 100644
--- lld/test/ELF/riscv-reloc-align.s
+++ /dev/null
@@ -1,12 +0,0 @@
-# REQUIRES: riscv
-
-# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax %s -o %t.o
-# RUN: not ld.lld %t.o -o /dev/null 2>&1 | FileCheck %s
-
-# CHECK: relocation R_RISCV_ALIGN requires unimplemented linker relaxation
-
-.global _start
-_start:
- nop
- .balign 8
- nop