[lld] [lld] Add target support for SystemZ (s390x) (PR #75643)
Ulrich Weigand via llvm-commits
llvm-commits at lists.llvm.org
Wed Dec 20 15:09:33 PST 2023
https://github.com/uweigand updated https://github.com/llvm/llvm-project/pull/75643
>From fd92a1bf08c3defafaa7cb3576a655531f82786a Mon Sep 17 00:00:00 2001
From: Ulrich Weigand <ulrich.weigand at de.ibm.com>
Date: Fri, 15 Dec 2023 20:04:03 +0100
Subject: [PATCH] [lld] Add target support for SystemZ (s390x)
This patch adds full support for linking SystemZ (ELF s390x) object
files. Support should be generally complete:
- All relocation types are supported.
- Full shared library support (DYNAMIC, GOT, PLT, ifunc).
- Relaxation of TLS and GOT relocations where appropriate.
- Platform-specific test cases.
In addition to new platform code and the obvious changes, there were
a few additional changes to common code:
- Add three new RelExpr members (R_GOTPLT_OFF, R_GOTPLT_PC, and
R_PLT_GOTREL) needed to support certain s390x relocations.
I chose not to use a platform-specific name since nothing in
the definition of these relocs is actually platform-specific;
it is well possible that other platforms will need the same.
- A couple of tweaks to TLS relocation handling, as the particular
semantics of the s390x versions differ slightly. See comments
in the code.
This was tested by building and testing >1500 Fedora packages,
with only a handful of failures; as these also have issues when
building with LLD on other architectures, they seem unrelated.
Co-authored-by: Tulio Magno Quites Machado Filho <tuliom at redhat.com>
---
lld/ELF/Arch/SystemZ.cpp | 587 ++++++++++++++++++++
lld/ELF/CMakeLists.txt | 1 +
lld/ELF/Driver.cpp | 3 +-
lld/ELF/InputFiles.cpp | 2 +
lld/ELF/InputSection.cpp | 7 +
lld/ELF/Relocations.cpp | 24 +-
lld/ELF/Relocations.h | 3 +
lld/ELF/ScriptParser.cpp | 1 +
lld/ELF/SyntheticSections.cpp | 3 +
lld/ELF/Target.cpp | 2 +
lld/ELF/Target.h | 1 +
lld/test/ELF/Inputs/systemz-init.s | 5 +
lld/test/ELF/basic-systemz.s | 63 +++
lld/test/ELF/emulation-systemz.s | 29 +
lld/test/ELF/lto/systemz.ll | 18 +
lld/test/ELF/systemz-gnu-ifunc.s | 72 +++
lld/test/ELF/systemz-got.s | 16 +
lld/test/ELF/systemz-gotent-relax-align.s | 48 ++
lld/test/ELF/systemz-gotent-relax-und-dso.s | 68 +++
lld/test/ELF/systemz-gotent-relax.s | 92 +++
lld/test/ELF/systemz-init-padding.s | 27 +
lld/test/ELF/systemz-pie.s | 38 ++
lld/test/ELF/systemz-plt.s | 83 +++
lld/test/ELF/systemz-reloc-abs.s | 29 +
lld/test/ELF/systemz-reloc-disp12.s | 21 +
lld/test/ELF/systemz-reloc-disp20.s | 21 +
lld/test/ELF/systemz-reloc-pc16.s | 39 ++
lld/test/ELF/systemz-reloc-pc32.s | 39 ++
lld/test/ELF/systemz-reloc-range12.s | 16 +
lld/test/ELF/systemz-reloc-range16.s | 16 +
lld/test/ELF/systemz-reloc-range24.s | 16 +
lld/test/ELF/systemz-reloc-range32.s | 16 +
lld/test/ELF/systemz-shared.s | 86 +++
lld/test/ELF/systemz-tls-gd.s | 142 +++++
lld/test/ELF/systemz-tls-ie.s | 74 +++
lld/test/ELF/systemz-tls-ld.s | 114 ++++
lld/test/ELF/systemz-tls-le.s | 61 ++
lld/test/lit.cfg.py | 1 +
38 files changed, 1881 insertions(+), 3 deletions(-)
create mode 100644 lld/ELF/Arch/SystemZ.cpp
create mode 100644 lld/test/ELF/Inputs/systemz-init.s
create mode 100644 lld/test/ELF/basic-systemz.s
create mode 100644 lld/test/ELF/emulation-systemz.s
create mode 100644 lld/test/ELF/lto/systemz.ll
create mode 100644 lld/test/ELF/systemz-gnu-ifunc.s
create mode 100644 lld/test/ELF/systemz-got.s
create mode 100644 lld/test/ELF/systemz-gotent-relax-align.s
create mode 100644 lld/test/ELF/systemz-gotent-relax-und-dso.s
create mode 100644 lld/test/ELF/systemz-gotent-relax.s
create mode 100644 lld/test/ELF/systemz-init-padding.s
create mode 100644 lld/test/ELF/systemz-pie.s
create mode 100644 lld/test/ELF/systemz-plt.s
create mode 100644 lld/test/ELF/systemz-reloc-abs.s
create mode 100644 lld/test/ELF/systemz-reloc-disp12.s
create mode 100644 lld/test/ELF/systemz-reloc-disp20.s
create mode 100644 lld/test/ELF/systemz-reloc-pc16.s
create mode 100644 lld/test/ELF/systemz-reloc-pc32.s
create mode 100644 lld/test/ELF/systemz-reloc-range12.s
create mode 100644 lld/test/ELF/systemz-reloc-range16.s
create mode 100644 lld/test/ELF/systemz-reloc-range24.s
create mode 100644 lld/test/ELF/systemz-reloc-range32.s
create mode 100644 lld/test/ELF/systemz-shared.s
create mode 100644 lld/test/ELF/systemz-tls-gd.s
create mode 100644 lld/test/ELF/systemz-tls-ie.s
create mode 100644 lld/test/ELF/systemz-tls-ld.s
create mode 100644 lld/test/ELF/systemz-tls-le.s
diff --git a/lld/ELF/Arch/SystemZ.cpp b/lld/ELF/Arch/SystemZ.cpp
new file mode 100644
index 00000000000000..3ceeb44487d5ec
--- /dev/null
+++ b/lld/ELF/Arch/SystemZ.cpp
@@ -0,0 +1,587 @@
+//===- SystemZ.cpp --------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "OutputSections.h"
+#include "Symbols.h"
+#include "SyntheticSections.h"
+#include "Target.h"
+#include "lld/Common/ErrorHandler.h"
+#include "llvm/BinaryFormat/ELF.h"
+#include "llvm/Support/Endian.h"
+
+using namespace llvm;
+using namespace llvm::object;
+using namespace llvm::support::endian;
+using namespace llvm::ELF;
+using namespace lld;
+using namespace lld::elf;
+
+namespace {
+class SystemZ : public TargetInfo {
+public:
+ SystemZ();
+ RelExpr getRelExpr(RelType type, const Symbol &s,
+ const uint8_t *loc) const override;
+ RelType getDynRel(RelType type) const override;
+ void writeGotHeader(uint8_t *buf) const override;
+ void writeGotPlt(uint8_t *buf, const Symbol &s) const override;
+ void writeIgotPlt(uint8_t *buf, const Symbol &s) const override;
+ void writePltHeader(uint8_t *buf) const override;
+ void addPltHeaderSymbols(InputSection &isd) const override;
+ void writePlt(uint8_t *buf, const Symbol &sym,
+ uint64_t pltEntryAddr) const override;
+ RelExpr adjustTlsExpr(RelType type, RelExpr expr) const override;
+ RelExpr adjustGotPcExpr(RelType type, int64_t addend,
+ const uint8_t *loc) const override;
+ bool relaxOnce(int pass) const override;
+ void relocate(uint8_t *loc, const Relocation &rel,
+ uint64_t val) const override;
+ int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;
+
+private:
+ void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
+ void relaxTlsGdToIe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
+ void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
+ void relaxTlsLdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
+};
+} // namespace
+
+SystemZ::SystemZ() {
+ copyRel = R_390_COPY;
+ gotRel = R_390_GLOB_DAT;
+ pltRel = R_390_JMP_SLOT;
+ relativeRel = R_390_RELATIVE;
+ iRelativeRel = R_390_IRELATIVE;
+ symbolicRel = R_390_64;
+ tlsGotRel = R_390_TLS_TPOFF;
+ tlsModuleIndexRel = R_390_TLS_DTPMOD;
+ tlsOffsetRel = R_390_TLS_DTPOFF;
+ gotHeaderEntriesNum = 3;
+ gotPltHeaderEntriesNum = 0;
+ gotEntrySize = 8;
+ pltHeaderSize = 32;
+ pltEntrySize = 32;
+ ipltEntrySize = 32;
+
+ // This "trap instruction" is used to fill gaps between sections.
+ // On SystemZ, the behavior of the GNU ld is to fill those gaps
+ // with nop instructions instead - and unfortunately the default
+ // glibc crt object files (used to) rely on that behavior since
+ // they use an alignment on the .init section fragments that causes
+ // gaps which must be filled with nops as they are being executed.
+ // Therefore, we provide a nop instruction as "trapInstr" here.
+ trapInstr = {0x07, 0x07, 0x07, 0x07};
+
+ defaultImageBase = 0x1000000;
+}
+
+RelExpr SystemZ::getRelExpr(RelType type, const Symbol &s,
+ const uint8_t *loc) const {
+ switch (type) {
+ case R_390_NONE:
+ return R_NONE;
+ // Relocations targeting the symbol value.
+ case R_390_8:
+ case R_390_12:
+ case R_390_16:
+ case R_390_20:
+ case R_390_32:
+ case R_390_64:
+ return R_ABS;
+ case R_390_PC16:
+ case R_390_PC32:
+ case R_390_PC64:
+ case R_390_PC12DBL:
+ case R_390_PC16DBL:
+ case R_390_PC24DBL:
+ case R_390_PC32DBL:
+ return R_PC;
+ case R_390_GOTOFF16:
+ case R_390_GOTOFF: // a.k.a. R_390_GOTOFF32
+ case R_390_GOTOFF64:
+ return R_GOTREL;
+ // Relocations targeting the PLT associated with the symbol.
+ case R_390_PLT32:
+ case R_390_PLT64:
+ case R_390_PLT12DBL:
+ case R_390_PLT16DBL:
+ case R_390_PLT24DBL:
+ case R_390_PLT32DBL:
+ return R_PLT_PC;
+ case R_390_PLTOFF16:
+ case R_390_PLTOFF32:
+ case R_390_PLTOFF64:
+ return R_PLT_GOTREL;
+ // Relocations targeting the GOT entry associated with the symbol.
+ case R_390_GOTENT:
+ return R_GOT_PC;
+ case R_390_GOT12:
+ case R_390_GOT16:
+ case R_390_GOT20:
+ case R_390_GOT32:
+ case R_390_GOT64:
+ return R_GOT_OFF;
+ // Relocations targeting the GOTPLT entry associated with the symbol.
+ case R_390_GOTPLTENT:
+ return R_GOTPLT_PC;
+ case R_390_GOTPLT12:
+ case R_390_GOTPLT16:
+ case R_390_GOTPLT20:
+ case R_390_GOTPLT32:
+ case R_390_GOTPLT64:
+ return R_GOTPLT_OFF;
+ // Relocations targeting _GLOBAL_OFFSET_TABLE_.
+ case R_390_GOTPC:
+ case R_390_GOTPCDBL:
+ return R_GOTONLY_PC;
+ // TLS-related relocations.
+ case R_390_TLS_LOAD:
+ return R_NONE;
+ case R_390_TLS_GDCALL:
+ return R_TLSGD_PC;
+ case R_390_TLS_LDCALL:
+ return R_TLSLD_PC;
+ case R_390_TLS_GD32:
+ case R_390_TLS_GD64:
+ return R_TLSGD_GOT;
+ case R_390_TLS_LDM32:
+ case R_390_TLS_LDM64:
+ return R_TLSLD_GOT;
+ case R_390_TLS_LDO32:
+ case R_390_TLS_LDO64:
+ return R_DTPREL;
+ case R_390_TLS_LE32:
+ case R_390_TLS_LE64:
+ return R_TPREL;
+ case R_390_TLS_IE32:
+ case R_390_TLS_IE64:
+ return R_GOT;
+ case R_390_TLS_GOTIE12:
+ case R_390_TLS_GOTIE20:
+ case R_390_TLS_GOTIE32:
+ case R_390_TLS_GOTIE64:
+ return R_GOT_OFF;
+ case R_390_TLS_IEENT:
+ return R_GOT_PC;
+
+ default:
+ error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) +
+ ") against symbol " + toString(s));
+ return R_NONE;
+ }
+}
+
+void SystemZ::writeGotHeader(uint8_t *buf) const {
+ // _GLOBAL_OFFSET_TABLE_[0] holds the value of _DYNAMIC.
+ // _GLOBAL_OFFSET_TABLE_[1] and [2] are reserved.
+ write64be(buf, mainPart->dynamic->getVA());
+}
+
+void SystemZ::writeGotPlt(uint8_t *buf, const Symbol &s) const {
+ write64be(buf, s.getPltVA() + 14);
+}
+
+void SystemZ::writeIgotPlt(uint8_t *buf, const Symbol &s) const {
+ if (config->writeAddends)
+ write64be(buf, s.getVA());
+}
+
+void SystemZ::writePltHeader(uint8_t *buf) const {
+ const uint8_t pltData[] = {
+ 0xe3, 0x10, 0xf0, 0x38, 0x00, 0x24, // stg %r1,56(%r15)
+ 0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, // larl %r1,_GLOBAL_OFFSET_TABLE_
+ 0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8,%r15),8(%r1)
+ 0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1,16(%r1)
+ 0x07, 0xf1, // br %r1
+ 0x07, 0x00, // nopr
+ 0x07, 0x00, // nopr
+ 0x07, 0x00, // nopr
+ };
+ memcpy(buf, pltData, sizeof(pltData));
+ uint64_t got = in.got->getVA();
+ uint64_t plt = in.plt->getVA();
+ write32be(buf + 8, (got - plt - 6) >> 1);
+}
+
+void SystemZ::addPltHeaderSymbols(InputSection &isec) const {
+ // The PLT header needs a reference to _GLOBAL_OFFSET_TABLE_, so we
+ // must ensure the .got section is created even if otherwise unused.
+ in.got->hasGotOffRel.store(true, std::memory_order_relaxed);
+}
+
+void SystemZ::writePlt(uint8_t *buf, const Symbol &sym,
+ uint64_t pltEntryAddr) const {
+ const uint8_t inst[] = {
+ 0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, // larl %r1,<.got.plt slot>
+ 0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1,0(%r1)
+ 0x07, 0xf1, // br %r1
+ 0x0d, 0x10, // basr %r1,%r0
+ 0xe3, 0x10, 0x10, 0x0c, 0x00, 0x14, // lgf %r1,12(%r1)
+ 0xc0, 0xf4, 0x00, 0x00, 0x00, 0x00, // jg <plt header>
+ 0x00, 0x00, 0x00, 0x00, // <relocation offset>
+ };
+ memcpy(buf, inst, sizeof(inst));
+
+ write32be(buf + 2, (sym.getGotPltVA() - pltEntryAddr) >> 1);
+ write32be(buf + 24, (in.plt->getVA() - pltEntryAddr - 22) >> 1);
+ write32be(buf + 28, in.relaPlt->entsize * sym.getPltIdx());
+}
+
+int64_t SystemZ::getImplicitAddend(const uint8_t *buf, RelType type) const {
+ switch (type) {
+ case R_390_8:
+ return SignExtend64<8>(*buf);
+ case R_390_16:
+ case R_390_PC16:
+ return SignExtend64<16>(read16be(buf));
+ case R_390_PC16DBL:
+ return SignExtend64<16>(read16be(buf)) << 1;
+ case R_390_32:
+ case R_390_PC32:
+ return SignExtend64<32>(read32be(buf));
+ case R_390_PC32DBL:
+ return SignExtend64<32>(read32be(buf)) << 1;
+ case R_390_64:
+ case R_390_PC64:
+ case R_390_TLS_DTPMOD:
+ case R_390_TLS_DTPOFF:
+ case R_390_TLS_TPOFF:
+ case R_390_GLOB_DAT:
+ case R_390_RELATIVE:
+ case R_390_IRELATIVE:
+ return read64be(buf);
+ case R_390_COPY:
+ case R_390_JMP_SLOT:
+ case R_390_NONE:
+ // These relocations are defined as not having an implicit addend.
+ return 0;
+ default:
+ internalLinkerError(getErrorLocation(buf),
+ "cannot read addend for relocation " + toString(type));
+ return 0;
+ }
+}
+
+RelType SystemZ::getDynRel(RelType type) const {
+ if (type == R_390_64 || type == R_390_PC64)
+ return type;
+ return R_390_NONE;
+}
+
+RelExpr SystemZ::adjustTlsExpr(RelType type, RelExpr expr) const {
+ if (expr == R_RELAX_TLS_GD_TO_IE)
+ return R_RELAX_TLS_GD_TO_IE_GOT_OFF;
+ return expr;
+}
+
+void SystemZ::relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
+ uint64_t val) const {
+ // The general-dynamic code sequence for a global `x`:
+ //
+ // Instruction Relocation Symbol
+ // ear %rX,%a0
+ // sllg %rX,%rX,32
+ // ear %rX,%a1
+ // larl %r12,_GLOBAL_OFFSET_TABLE_ R_390_GOTPCDBL _GLOBAL_OFFSET_TABLE_
+ // lgrl %r2,.LC0 R_390_PC32DBL .LC0
+ // brasl %r14,__tls_get_offset at plt R_390_TLS_GDCALL x
+ // :tls_gdcall:x R_390_PLT32DBL __tls_get_offset
+ // la %r2,0(%r2,%rX)
+ //
+ // .LC0:
+ // .quad x at TLSGD R_390_TLS_GD64 x
+ //
+ // Relaxing to initial-exec entails:
+ // 1) Replacing the call by a load from the GOT.
+ // 2) Replacing the relocation on the constant LC0 by R_390_TLS_GOTIE64.
+
+ switch (rel.type) {
+ case R_390_TLS_GDCALL:
+ /* brasl %r14,__tls_get_offset at plt -> lg %r2,0(%r2,%r12) */
+ write16be(loc, 0xe322);
+ write16be(loc + 2, 0xc000);
+ write16be(loc + 4, 0x0004);
+ break;
+ case R_390_TLS_GD64:
+ relocateNoSym(loc, R_390_TLS_GOTIE64, val);
+ break;
+ default:
+ llvm_unreachable("unsupported relocation for TLS GD to IE relaxation");
+ }
+}
+
+void SystemZ::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
+ uint64_t val) const {
+ // The general-dynamic code sequence for a global `x`:
+ //
+ // Instruction Relocation Symbol
+ // ear %rX,%a0
+ // sllg %rX,%rX,32
+ // ear %rX,%a1
+ // larl %r12,_GLOBAL_OFFSET_TABLE_ R_390_GOTPCDBL _GLOBAL_OFFSET_TABLE_
+ // lgrl %r2,.LC0 R_390_PC32DBL .LC0
+ // brasl %r14,__tls_get_offset at plt R_390_TLS_GDCALL x
+ // :tls_gdcall:x R_390_PLT32DBL __tls_get_offset
+ // la %r2,0(%r2,%rX)
+ //
+ // .LC0:
+ // .quad x at tlsgd R_390_TLS_GD64 x
+ //
+ // Relaxing to local-exec entails:
+ // 1) Replacing the call by a nop.
+ // 2) Replacing the relocation on the constant LC0 by R_390_TLS_LE64.
+
+ switch (rel.type) {
+ case R_390_TLS_GDCALL:
+ /* brasl %r14,__tls_get_offset at plt -> brcl 0,. */
+ write16be(loc, 0xc004);
+ write16be(loc + 2, 0x0000);
+ write16be(loc + 4, 0x0000);
+ break;
+ case R_390_TLS_GD64:
+ relocateNoSym(loc, R_390_TLS_LE64, val);
+ break;
+ default:
+ llvm_unreachable("unsupported relocation for TLS GD to LE relaxation");
+ }
+}
+
+void SystemZ::relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
+ uint64_t val) const {
+ // The local-dynamic code sequence for a global `x`:
+ //
+ // Instruction Relocation Symbol
+ // ear %rX,%a0
+ // sllg %rX,%rX,32
+ // ear %rX,%a1
+ // larl %r12,_GLOBAL_OFFSET_TABLE_ R_390_GOTPCDBL _GLOBAL_OFFSET_TABLE_
+ // lgrl %r2,.LC0 R_390_PC32DBL .LC0
+ // brasl %r14,__tls_get_offset at plt R_390_TLS_LDCALL <sym>
+ // :tls_ldcall:<sym> R_390_PLT32DBL __tls_get_offset
+ // la %r2,0(%r2,%rX)
+ // lgrl %rY,.LC1 R_390_PC32DBL .LC1
+ // la %r2,0(%r2,%rY)
+ //
+ // .LC0:
+ // .quad <sym>@tlsldm R_390_TLS_LDM64 <sym>
+ // .LC1:
+ // .quad x at dtpoff R_390_TLS_LDO64 x
+ //
+ // Relaxing to local-exec entails:
+ // 1) Replacing the call by a nop.
+ // 2) Replacing the constant LC0 by 0 (i.e. ignoring the relocation).
+ // 3) Replacing the relocation on the constant LC1 by R_390_TLS_LE64.
+
+ switch (rel.type) {
+ case R_390_TLS_LDCALL:
+ /* brasl %r14,__tls_get_offset at plt -> brcl 0,. */
+ write16be(loc, 0xc004);
+ write16be(loc + 2, 0x0000);
+ write16be(loc + 4, 0x0000);
+ break;
+ case R_390_TLS_LDM64:
+ break;
+ case R_390_TLS_LDO64:
+ relocateNoSym(loc, R_390_TLS_LE64, val);
+ break;
+ default:
+ llvm_unreachable("unsupported relocation for TLS LD to LE relaxation");
+ }
+}
+
+RelExpr SystemZ::adjustGotPcExpr(RelType type, int64_t addend,
+ const uint8_t *loc) const {
+ // Only R_390_GOTENT with addend 2 can be relaxed.
+ if (!config->relax || addend != 2 || type != R_390_GOTENT)
+ return R_GOT_PC;
+ const uint16_t op = read16be(loc - 2);
+
+ // lgrl rx,sym at GOTENT -> larl rx, sym
+ // This relaxation is legal if "sym" binds locally (which was
+ // already verified by our caller) and is in-range and properly
+ // aligned for a LARL instruction. We cannot verify the latter
+ // constraint here, so we assume it is true and revert the decision
+ // later on in relaxOnce if necessary.
+ if ((op & 0xff0f) == 0xc408)
+ return R_RELAX_GOT_PC;
+
+ return R_GOT_PC;
+}
+
+bool SystemZ::relaxOnce(int pass) const {
+ // If we decided in adjustGotPcExpr to relax a R_390_GOTENT,
+ // we need to validate the target symbol is in-range and aligned.
+ SmallVector<InputSection *, 0> storage;
+ bool changed = false;
+ for (OutputSection *osec : outputSections) {
+ if (!(osec->flags & SHF_EXECINSTR))
+ continue;
+ for (InputSection *sec : getInputSections(*osec, storage)) {
+ for (Relocation &rel : sec->relocs()) {
+ if (rel.expr != R_RELAX_GOT_PC)
+ continue;
+
+ uint64_t v = sec->getRelocTargetVA(
+ sec->file, rel.type, rel.addend,
+ sec->getOutputSection()->addr + rel.offset, *rel.sym, rel.expr);
+ if (isInt<33>(v) && !(v & 1))
+ continue;
+ if (rel.sym->auxIdx == 0) {
+ rel.sym->allocateAux();
+ addGotEntry(*rel.sym);
+ changed = true;
+ }
+ rel.expr = R_GOT_PC;
+ }
+ }
+ }
+ return changed;
+}
+
+void SystemZ::relaxGot(uint8_t *loc, const Relocation &rel,
+ uint64_t val) const {
+ assert(isInt<33>(val) &&
+ "R_390_GOTENT should not have been relaxed if it overflows");
+ assert(!(val & 1) &&
+ "R_390_GOTENT should not have been relaxed if it is misaligned");
+ const uint16_t op = read16be(loc - 2);
+
+ // lgrl rx,sym at GOTENT -> larl rx, sym
+ if ((op & 0xff0f) == 0xc408) {
+ write16be(loc - 2, 0xc000 | (op & 0x00f0));
+ write32be(loc, val >> 1);
+ return;
+ }
+}
+
+void SystemZ::relocate(uint8_t *loc, const Relocation &rel,
+ uint64_t val) const {
+ switch (rel.expr) {
+ case R_RELAX_GOT_PC:
+ return relaxGot(loc, rel, val);
+ case R_RELAX_TLS_GD_TO_IE_GOT_OFF:
+ return relaxTlsGdToIe(loc, rel, val);
+ case R_RELAX_TLS_GD_TO_LE:
+ return relaxTlsGdToLe(loc, rel, val);
+ case R_RELAX_TLS_LD_TO_LE:
+ return relaxTlsLdToLe(loc, rel, val);
+ default:
+ break;
+ }
+ switch (rel.type) {
+ case R_390_8:
+ checkIntUInt(loc, val, 8, rel);
+ *loc = val;
+ break;
+ case R_390_12:
+ case R_390_GOT12:
+ case R_390_GOTPLT12:
+ case R_390_TLS_GOTIE12:
+ checkUInt(loc, val, 12, rel);
+ write16be(loc, (read16be(loc) & 0xF000) | val);
+ break;
+ case R_390_PC12DBL:
+ case R_390_PLT12DBL:
+ checkInt(loc, val, 13, rel);
+ checkAlignment(loc, val, 2, rel);
+ write16be(loc, (read16be(loc) & 0xF000) | (val >> 1));
+ break;
+ case R_390_16:
+ case R_390_GOT16:
+ case R_390_GOTPLT16:
+ case R_390_GOTOFF16:
+ case R_390_PLTOFF16:
+ checkIntUInt(loc, val, 16, rel);
+ write16be(loc, val);
+ break;
+ case R_390_PC16:
+ checkInt(loc, val, 16, rel);
+ write16be(loc, val);
+ break;
+ case R_390_PC16DBL:
+ case R_390_PLT16DBL:
+ checkInt(loc, val, 17, rel);
+ checkAlignment(loc, val, 2, rel);
+ write16be(loc, val >> 1);
+ break;
+ case R_390_20:
+ case R_390_GOT20:
+ case R_390_GOTPLT20:
+ case R_390_TLS_GOTIE20:
+ checkInt(loc, val, 20, rel);
+ write32be(loc, (read32be(loc) & 0xF00000FF) | ((val & 0xFFF) << 16) |
+ ((val & 0xFF000) >> 4));
+ break;
+ case R_390_PC24DBL:
+ case R_390_PLT24DBL:
+ checkInt(loc, val, 25, rel);
+ loc[0] = val >> 17;
+ loc[1] = val >> 9;
+ loc[2] = val >> 1;
+ break;
+ case R_390_32:
+ case R_390_GOT32:
+ case R_390_GOTPLT32:
+ case R_390_GOTOFF:
+ case R_390_PLTOFF32:
+ case R_390_TLS_IE32:
+ case R_390_TLS_GOTIE32:
+ case R_390_TLS_GD32:
+ case R_390_TLS_LDM32:
+ case R_390_TLS_LDO32:
+ case R_390_TLS_LE32:
+ checkIntUInt(loc, val, 32, rel);
+ write32be(loc, val);
+ break;
+ case R_390_PC32:
+ case R_390_PLT32:
+ checkInt(loc, val, 32, rel);
+ write32be(loc, val);
+ break;
+ case R_390_PC32DBL:
+ case R_390_PLT32DBL:
+ case R_390_GOTPCDBL:
+ case R_390_GOTENT:
+ case R_390_GOTPLTENT:
+ case R_390_TLS_IEENT:
+ checkInt(loc, val, 33, rel);
+ checkAlignment(loc, val, 2, rel);
+ write32be(loc, val >> 1);
+ break;
+ case R_390_64:
+ case R_390_PC64:
+ case R_390_PLT64:
+ case R_390_GOT64:
+ case R_390_GOTPLT64:
+ case R_390_GOTOFF64:
+ case R_390_PLTOFF64:
+ case R_390_GOTPC:
+ case R_390_TLS_IE64:
+ case R_390_TLS_GOTIE64:
+ case R_390_TLS_GD64:
+ case R_390_TLS_LDM64:
+ case R_390_TLS_LDO64:
+ case R_390_TLS_LE64:
+ case R_390_TLS_DTPMOD:
+ case R_390_TLS_DTPOFF:
+ case R_390_TLS_TPOFF:
+ write64be(loc, val);
+ break;
+ case R_390_TLS_LOAD:
+ case R_390_TLS_GDCALL:
+ case R_390_TLS_LDCALL:
+ break;
+ default:
+ llvm_unreachable("unknown relocation");
+ }
+}
+
+TargetInfo *elf::getSystemZTargetInfo() {
+ static SystemZ t;
+ return &t;
+}
diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt
index 475f7dea1dd7e9..83d816ddb0601e 100644
--- a/lld/ELF/CMakeLists.txt
+++ b/lld/ELF/CMakeLists.txt
@@ -33,6 +33,7 @@ add_lld_library(lldELF
Arch/PPC64.cpp
Arch/RISCV.cpp
Arch/SPARCV9.cpp
+ Arch/SystemZ.cpp
Arch/X86.cpp
Arch/X86_64.cpp
ARMErrataFix.cpp
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 6bef09eeca015a..d1514556f013f9 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -199,6 +199,7 @@ static std::tuple<ELFKind, uint16_t, uint8_t> parseEmulation(StringRef emul) {
.Case("msp430elf", {ELF32LEKind, EM_MSP430})
.Case("elf64_amdgpu", {ELF64LEKind, EM_AMDGPU})
.Case("elf64loongarch", {ELF64LEKind, EM_LOONGARCH})
+ .Case("elf64_s390", {ELF64BEKind, EM_S390})
.Default({ELFNoneKind, EM_NONE});
if (ret.first == ELFNoneKind)
@@ -1171,7 +1172,7 @@ static bool getIsRela(opt::InputArgList &args) {
uint16_t m = config->emachine;
return m == EM_AARCH64 || m == EM_AMDGPU || m == EM_HEXAGON ||
m == EM_LOONGARCH || m == EM_PPC || m == EM_PPC64 || m == EM_RISCV ||
- m == EM_X86_64;
+ m == EM_S390 || m == EM_X86_64;
}
static void parseClangOption(StringRef opt, const Twine &msg) {
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index cc2c5916e05c22..574e9c59fc6c9b 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1612,6 +1612,8 @@ static uint16_t getBitcodeMachineKind(StringRef path, const Triple &t) {
return EM_RISCV;
case Triple::sparcv9:
return EM_SPARCV9;
+ case Triple::systemz:
+ return EM_S390;
case Triple::x86:
return t.isOSIAMCU() ? EM_IAMCU : EM_386;
case Triple::x86_64:
diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index 81468a20dfb54d..6b3de2e4071456 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -652,6 +652,7 @@ static int64_t getTlsTpOffset(const Symbol &s) {
// Variant 2.
case EM_HEXAGON:
+ case EM_S390:
case EM_SPARCV9:
case EM_386:
case EM_X86_64:
@@ -705,6 +706,8 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
case R_GOT_OFF:
case R_RELAX_TLS_GD_TO_IE_GOT_OFF:
return sym.getGotOffset() + a;
+ case R_GOTPLT_OFF:
+ return sym.getGotPltOffset() + a;
case R_AARCH64_GOT_PAGE_PC:
case R_AARCH64_RELAX_TLS_GD_TO_IE_PAGE_PC:
return getAArch64Page(sym.getGotVA() + a) - getAArch64Page(p);
@@ -713,6 +716,8 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
case R_GOT_PC:
case R_RELAX_TLS_GD_TO_IE:
return sym.getGotVA() + a - p;
+ case R_GOTPLT_PC:
+ return sym.getGotPltVA() + a - p;
case R_LOONGARCH_GOT_PAGE_PC:
if (sym.hasFlag(NEEDS_TLSGD))
return getLoongArchPageDelta(in.got->getGlobalDynAddr(sym) + a, p);
@@ -804,6 +809,8 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
return getLoongArchPageDelta(sym.getPltVA() + a, p);
case R_PLT_GOTPLT:
return sym.getPltVA() + a - in.gotPlt->getVA();
+ case R_PLT_GOTREL:
+ return sym.getPltVA() + a - in.got->getVA();
case R_PPC32_PLTREL:
// R_PPC_PLTREL24 uses the addend (usually 0 or 0x8000) to indicate r30
// stores _GLOBAL_OFFSET_TABLE_ or .got2+0x8000. The addend is ignored for
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 210b4d1eb1a7a6..35055c9f979431 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -1357,8 +1357,8 @@ static unsigned handleTlsRelocation(RelType type, Symbol &sym,
R_LOONGARCH_GOT_PAGE_PC, R_GOT_OFF, R_TLSIE_HINT>(expr)) {
ctx.hasTlsIe.store(true, std::memory_order_relaxed);
// Initial-Exec relocs can be relaxed to Local-Exec if the symbol is locally
- // defined.
- if (toExecRelax && isLocalInExecutable) {
+ // defined. This is not supported on SystemZ.
+ if (toExecRelax && isLocalInExecutable && config->emachine != EM_S390) {
c.addReloc({R_RELAX_TLS_IE_TO_LE, type, offset, addend, &sym});
} else if (expr != R_TLSIE_HINT) {
sym.setFlags(NEEDS_TLSIE);
@@ -1404,6 +1404,26 @@ template <class ELFT, class RelTy> void RelocationScanner::scanOne(RelTy *&i) {
if (expr == R_NONE)
return;
+ // Like other platforms, calls to the TLS helper routine on SystemZ carry
+ // two relocations, one for the helper routine itself, and a TLS marker
+ // relocation. When relaxing the TLS model, the helper routine is no longer
+ // needed, and its relocation should be removed. Unlike other platforms,
+ // on SystemZ the TLS marker routine typically comes *after* the helper
+ // routine relocation, so the getTlsGdRelaxSkip mechanism used by
+ // handleTlsRelocation does not work on this platform.
+ //
+ // Instead, check for this case here: if we are building a main executable
+ // (i.e. TLS relaxation applies), and the relocation *after* the current one
+ // is a TLS call marker instruction matching the current instruction, then
+ // skip this relocation.
+ if (config->emachine == EM_S390 && !config->shared) {
+ if (i < end && getter.get(i->r_offset) == offset - 2) {
+ RelType nextType = i->getType(/*isMips64EL=*/false);
+ if (nextType == R_390_TLS_GDCALL || nextType == R_390_TLS_LDCALL)
+ return;
+ }
+ }
+
// Error if the target symbol is undefined. Symbol index 0 may be used by
// marker relocations, e.g. R_*_NONE and R_ARM_V4BX. Don't error on them.
if (sym.isUndefined() && symIndex != 0 &&
diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h
index cfb9092149f3e0..acfdf56e837694 100644
--- a/lld/ELF/Relocations.h
+++ b/lld/ELF/Relocations.h
@@ -40,11 +40,14 @@ enum RelExpr {
R_GOTPLT,
R_GOTPLTREL,
R_GOTREL,
+ R_GOTPLT_OFF,
+ R_GOTPLT_PC,
R_NONE,
R_PC,
R_PLT,
R_PLT_PC,
R_PLT_GOTPLT,
+ R_PLT_GOTREL,
R_RELAX_HINT,
R_RELAX_GOT_PC,
R_RELAX_GOT_PC_NOPIC,
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 55b10f0c59b516..4cda262c57a548 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -444,6 +444,7 @@ static std::pair<ELFKind, uint16_t> parseBfdName(StringRef s) {
.Case("elf32-msp430", {ELF32LEKind, EM_MSP430})
.Case("elf32-loongarch", {ELF32LEKind, EM_LOONGARCH})
.Case("elf64-loongarch", {ELF64LEKind, EM_LOONGARCH})
+ .Case("elf64-s390", {ELF64BEKind, EM_S390})
.Default({ELFNoneKind, EM_NONE});
}
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 2b32eb3a0fe355..724417707ebcf7 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -1419,6 +1419,9 @@ DynamicSection<ELFT>::computeContents() {
case EM_MIPS:
addInSec(DT_MIPS_PLTGOT, *in.gotPlt);
break;
+ case EM_S390:
+ addInSec(DT_PLTGOT, *in.got);
+ break;
case EM_SPARCV9:
addInSec(DT_PLTGOT, *in.plt);
break;
diff --git a/lld/ELF/Target.cpp b/lld/ELF/Target.cpp
index 41990f40f68b82..dd5acc56590297 100644
--- a/lld/ELF/Target.cpp
+++ b/lld/ELF/Target.cpp
@@ -87,6 +87,8 @@ TargetInfo *elf::getTarget() {
return getRISCVTargetInfo();
case EM_SPARCV9:
return getSPARCV9TargetInfo();
+ case EM_S390:
+ return getSystemZTargetInfo();
case EM_X86_64:
return getX86_64TargetInfo();
}
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 6264ab1a3da74a..70c882c69e8ad7 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -186,6 +186,7 @@ TargetInfo *getPPC64TargetInfo();
TargetInfo *getPPCTargetInfo();
TargetInfo *getRISCVTargetInfo();
TargetInfo *getSPARCV9TargetInfo();
+TargetInfo *getSystemZTargetInfo();
TargetInfo *getX86TargetInfo();
TargetInfo *getX86_64TargetInfo();
template <class ELFT> TargetInfo *getMipsTargetInfo();
diff --git a/lld/test/ELF/Inputs/systemz-init.s b/lld/test/ELF/Inputs/systemz-init.s
new file mode 100644
index 00000000000000..1611b69b4419e3
--- /dev/null
+++ b/lld/test/ELF/Inputs/systemz-init.s
@@ -0,0 +1,5 @@
+// glibc < 2.39 used to align .init and .fini code at a 4-byte boundary.
+// This file aims to recreate that behavior.
+ .section .init,"ax", at progbits
+ .align 4
+ lg %r4, 272(%r15)
diff --git a/lld/test/ELF/basic-systemz.s b/lld/test/ELF/basic-systemz.s
new file mode 100644
index 00000000000000..e65b73c91f6e14
--- /dev/null
+++ b/lld/test/ELF/basic-systemz.s
@@ -0,0 +1,63 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+# RUN: ld.lld --hash-style=sysv -discard-all -shared %t.o -o %t.so
+# RUN: llvm-readelf --file-header --program-headers --section-headers --dynamic-table %t.so | FileCheck %s
+
+# Exits with return code 55 on linux.
+.text
+ lghi 2,55
+ svc 1
+
+// CHECK: ELF Header:
+// CHECK-NEXT: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00
+// CHECK-NEXT: Class: ELF64
+// CHECK-NEXT: Data: 2's complement, big endian
+// CHECK-NEXT: Version: 1 (current)
+// CHECK-NEXT: OS/ABI: UNIX - System V
+// CHECK-NEXT: ABI Version: 0
+// CHECK-NEXT: Type: DYN (Shared object file)
+// CHECK-NEXT: Machine: IBM S/390
+// CHECK-NEXT: Version: 0x1
+// CHECK-NEXT: Entry point address: 0x0
+// CHECK-NEXT: Start of program headers: 64 (bytes into file)
+// CHECK-NEXT: Start of section headers: 768 (bytes into file)
+// CHECK-NEXT: Flags: 0x0
+// CHECK-NEXT: Size of this header: 64 (bytes)
+// CHECK-NEXT: Size of program headers: 56 (bytes)
+// CHECK-NEXT: Number of program headers: 7
+// CHECK-NEXT: Size of section headers: 64 (bytes)
+// CHECK-NEXT: Number of section headers: 11
+// CHECK-NEXT: Section header string table index: 9
+
+// CHECK: Section Headers:
+// CHECK-NEXT: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
+// CHECK-NEXT: [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
+// CHECK-NEXT: [ 1] .dynsym DYNSYM 00000000000001c8 0001c8 000018 18 A 3 1 8
+// CHECK-NEXT: [ 2] .hash HASH 00000000000001e0 0001e0 000010 04 A 1 0 4
+// CHECK-NEXT: [ 3] .dynstr STRTAB 00000000000001f0 0001f0 000001 00 A 0 0 1
+// CHECK-NEXT: [ 4] .text PROGBITS 00000000000011f4 0001f4 000006 00 AX 0 0 4
+// CHECK-NEXT: [ 5] .dynamic DYNAMIC 0000000000002200 000200 000060 10 WA 3 0 8
+// CHECK-NEXT: [ 6] .relro_padding NOBITS 0000000000002260 000260 000da0 00 WA 0 0 1
+// CHECK-NEXT: [ 7] .comment PROGBITS 0000000000000000 000260 000008 01 MS 0 0 1
+// CHECK-NEXT: [ 8] .symtab SYMTAB 0000000000000000 000268 000030 18 10 2 8
+// CHECK-NEXT: [ 9] .shstrtab STRTAB 0000000000000000 000298 000058 00 0 0 1
+// CHECK-NEXT: [10] .strtab STRTAB 0000000000000000 0002f0 00000a 00 0 0 1
+
+// CHECK: Program Headers:
+// CHECK-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
+// CHECK-NEXT: PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000188 0x000188 R 0x8
+// CHECK-NEXT: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0001f1 0x0001f1 R 0x1000
+// CHECK-NEXT: LOAD 0x0001f4 0x00000000000011f4 0x00000000000011f4 0x000006 0x000006 R E 0x1000
+// CHECK-NEXT: LOAD 0x000200 0x0000000000002200 0x0000000000002200 0x000060 0x000e00 RW 0x1000
+// CHECK-NEXT: DYNAMIC 0x000200 0x0000000000002200 0x0000000000002200 0x000060 0x000060 RW 0x8
+// CHECK-NEXT: GNU_RELRO 0x000200 0x0000000000002200 0x0000000000002200 0x000060 0x000e00 R 0x1
+// CHECK-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0
+
+// CHECK: Dynamic section at offset 0x200 contains 6 entries:
+// CHECK-NEXT: Tag Type Name/Value
+// CHECK-NEXT: 0x0000000000000006 (SYMTAB) 0x1c8
+// CHECK-NEXT: 0x000000000000000b (SYMENT) 24 (bytes)
+// CHECK-NEXT: 0x0000000000000005 (STRTAB) 0x1f0
+// CHECK-NEXT: 0x000000000000000a (STRSZ) 1 (bytes)
+// CHECK-NEXT: 0x0000000000000004 (HASH) 0x1e0
+// CHECK-NEXT: 0x0000000000000000 (NULL) 0x0
diff --git a/lld/test/ELF/emulation-systemz.s b/lld/test/ELF/emulation-systemz.s
new file mode 100644
index 00000000000000..12e2c231d41ee3
--- /dev/null
+++ b/lld/test/ELF/emulation-systemz.s
@@ -0,0 +1,29 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t
+# RUN: ld.lld -m elf64_s390 %t -o %t2
+# RUN: llvm-readelf --file-header %t2 | FileCheck %s
+# RUN: ld.lld %t -o %t3
+# RUN: llvm-readelf --file-header %t3 | FileCheck %s
+# RUN: echo 'OUTPUT_FORMAT(elf64-s390)' > %t.script
+# RUN: ld.lld %t.script %t -o %t4
+# RUN: llvm-readelf --file-header %t4 | FileCheck %s
+
+// CHECK: ELF Header:
+// CHECK-NEXT: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00
+// CHECK-NEXT: Class: ELF64
+// CHECK-NEXT: Data: 2's complement, big endian
+// CHECK-NEXT: Version: 1 (current)
+// CHECK-NEXT: OS/ABI: UNIX - System V
+// CHECK-NEXT: ABI Version: 0
+// CHECK-NEXT: Type: EXEC (Executable file)
+// CHECK-NEXT: Machine: IBM S/390
+// CHECK-NEXT: Version: 0x1
+// CHECK-NEXT: Entry point address:
+// CHECK-NEXT: Start of program headers: 64 (bytes into file)
+// CHECK-NEXT: Start of section headers:
+// CHECK-NEXT: Flags: 0x0
+// CHECK-NEXT: Size of this header: 64 (bytes)
+// CHECK-NEXT: Size of program headers: 56 (bytes)
+
+.globl _start
+_start:
diff --git a/lld/test/ELF/lto/systemz.ll b/lld/test/ELF/lto/systemz.ll
new file mode 100644
index 00000000000000..42bf4e32fb6d75
--- /dev/null
+++ b/lld/test/ELF/lto/systemz.ll
@@ -0,0 +1,18 @@
+; REQUIRES: systemz
+;; Test we can infer the e_machine value EM_S390 from a bitcode file.
+
+; RUN: llvm-as %s -o %t.o
+; RUN: ld.lld %t.o -o %t
+; RUN: llvm-readobj -h %t | FileCheck %s
+
+; CHECK: Class: 64-bit
+; CHECK: DataEncoding: BigEndian
+; CHECK: Machine: EM_S390
+
+target datalayout = "E-m:e-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64"
+target triple = "s390x-unknown-linux-gnu"
+
+define void @_start() {
+entry:
+ ret void
+}
diff --git a/lld/test/ELF/systemz-gnu-ifunc.s b/lld/test/ELF/systemz-gnu-ifunc.s
new file mode 100644
index 00000000000000..a9f7d995a90894
--- /dev/null
+++ b/lld/test/ELF/systemz-gnu-ifunc.s
@@ -0,0 +1,72 @@
+// REQUIRES: systemz
+// RUN: llvm-mc -filetype=obj -triple=s390x-none-linux-gnu %s -o %t.o
+// RUN: ld.lld -static %t.o -o %tout
+// RUN: llvm-objdump --no-print-imm-hex -d --no-show-raw-insn %tout | FileCheck %s --check-prefix=DISASM
+// RUN: llvm-readelf --section-headers --relocations --symbols %tout | FileCheck %s
+
+// CHECK: There are 9 section headers
+// CHECK: Section Headers:
+// CHECK-NEXT: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
+// CHECK-NEXT: [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
+// CHECK-NEXT: [ 1] .rela.dyn RELA 0000000001000158 000158 000030 18 AI 0 4 8
+// CHECK-NEXT: [ 2] .text PROGBITS 0000000001001188 000188 00001c 00 AX 0 0 4
+// CHECK-NEXT: [ 3] .iplt PROGBITS 00000000010011b0 0001b0 000040 00 AX 0 0 16
+// CHECK-NEXT: [ 4] .got.plt PROGBITS 00000000010021f0 0001f0 000010 00 WA 0 0 8
+// CHECK-NEXT: [ 5] .comment PROGBITS 0000000000000000 000200 000008 01 MS 0 0 1
+// CHECK-NEXT: [ 6] .symtab SYMTAB 0000000000000000 000208 000090 18 8 3 8
+// CHECK-NEXT: [ 7] .shstrtab STRTAB 0000000000000000 000298 000043 00 0 0 1
+// CHECK-NEXT: [ 8] .strtab STRTAB 0000000000000000 0002db 000032 00 0 0 1
+
+// CHECK: Relocation section '.rela.dyn' at offset 0x158 contains 2 entries:
+// CHECK-NEXT: Offset Info Type Symbol's Value Symbol's Name + Addend
+// CHECK-NEXT: 00000000010021f0 000000000000003d R_390_IRELATIVE 1001188
+// CHECK-NEXT: 00000000010021f8 000000000000003d R_390_IRELATIVE 100118a
+
+// CHECK: Symbol table '.symtab' contains 6 entries:
+// CHECK-NEXT: Num: Value Size Type Bind Vis Ndx Name
+// CHECK-NEXT: 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
+// CHECK-NEXT: 1: 0000000001000158 0 NOTYPE LOCAL HIDDEN 1 __rela_iplt_start
+// CHECK-NEXT: 2: 0000000001000188 0 NOTYPE LOCAL HIDDEN 1 __rela_iplt_end
+// CHECK-NEXT: 3: 0000000001001188 0 IFUNC GLOBAL DEFAULT 2 foo
+// CHECK-NEXT: 4: 000000000100118a 0 IFUNC GLOBAL DEFAULT 2 bar
+// CHECK-NEXT: 5: 000000000100118c 0 NOTYPE GLOBAL DEFAULT 2 _start
+
+// DISASM: Disassembly of section .text:
+// DISASM-EMPTY:
+// DISASM-NEXT: <foo>:
+// DISASM-NEXT: 1001188: br %r14
+// DISASM: <bar>:
+// DISASM-NEXT: 100118a: br %r14
+// DISASM: <_start>:
+// DISASM-NEXT: 100118c: brasl %r14, 0x10011b0
+// DISASM-NEXT: 1001192: brasl %r14, 0x10011d0
+// DISASM-NEXT: 1001198: larl %r2, 0x1000158
+// DISASM-NEXT: 100119e: larl %r2, 0x1000188
+// DISASM-EMPTY:
+// DISASM-NEXT: Disassembly of section .iplt:
+// DISASM-EMPTY:
+// DISASM-NEXT: <.iplt>:
+// DISASM: 10011b0: larl %r1, 0x10021f0
+// DISASM-NEXT: 10011b6: lg %r1, 0(%r1)
+// DISASM-NEXT: 10011bc: br %r1
+// DISASM: 10011d0: larl %r1, 0x10021f8
+// DISASM-NEXT: 10011d6: lg %r1, 0(%r1)
+// DISASM-NEXT: 10011dc: br %r1
+
+.text
+.type foo STT_GNU_IFUNC
+.globl foo
+foo:
+ br %r14
+
+.type bar STT_GNU_IFUNC
+.globl bar
+bar:
+ br %r14
+
+.globl _start
+_start:
+ brasl %r14, foo at plt
+ brasl %r14, bar at plt
+ larl %r2, __rela_iplt_start
+ larl %r2, __rela_iplt_end
diff --git a/lld/test/ELF/systemz-got.s b/lld/test/ELF/systemz-got.s
new file mode 100644
index 00000000000000..f89babad023669
--- /dev/null
+++ b/lld/test/ELF/systemz-got.s
@@ -0,0 +1,16 @@
+// REQUIRES: systemz
+// RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+// RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %p/Inputs/shared.s -o %t2.o
+// RUN: ld.lld -shared %t2.o -soname=%t2.so -o %t2.so
+
+// RUN: ld.lld -dynamic-linker /lib/ld64.so.1 %t.o %t2.so -o %t
+// RUN: llvm-readelf -S -r %t | FileCheck %s
+
+// CHECK: .got PROGBITS {{.*}} {{.*}} 000020 00 WA 0 0 8
+
+// CHECK: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
+// CHECK: {{.*}} 000000010000000a R_390_GLOB_DAT 0000000000000000 bar + 0
+
+.global _start
+_start:
+ lgrl %r1,bar at GOT
diff --git a/lld/test/ELF/systemz-gotent-relax-align.s b/lld/test/ELF/systemz-gotent-relax-align.s
new file mode 100644
index 00000000000000..c6326086f56db0
--- /dev/null
+++ b/lld/test/ELF/systemz-gotent-relax-align.s
@@ -0,0 +1,48 @@
+# REQUIRES: systemz
+## Verify that R_390_GOTENT optimization is not performed on misaligned symbols.
+
+# RUN: llvm-mc -filetype=obj -relax-relocations -triple=s390x-unknown-linux %s -o %t.o
+# RUN: ld.lld %t.o -o %t1
+# RUN: llvm-readelf -S -r -x .got -x .got.plt %t1 | FileCheck --check-prefixes=CHECK %s
+# RUN: llvm-objdump --no-print-imm-hex -d %t1 | FileCheck --check-prefix=DISASM %s
+
+## We retain one .got entry for the unaligned symbol.
+# CHECK: Name Type Address Off Size ES Flg Lk Inf Al
+# CHECK: .got PROGBITS 00000000010021e0 0001e0 000020 00 WA 0 0 8
+# CHECK-NEXT: .relro_padding NOBITS 0000000001002200 000200 000e00 00 WA 0 0 1
+# CHECK-NEXT: .data PROGBITS 0000000001003200 000200 000006 00 WA 0 0 2
+
+# CHECK-LABEL: Hex dump of section '.got':
+# CHECK-NEXT: 0x010021e0 00000000 00000000 00000000 00000000
+# CHECK-NEXT: 0x010021f0 00000000 00000000 00000000 01003205
+
+# DISASM: Disassembly of section .text:
+# DISASM: <_start>:
+# DISASM-NEXT: larl %r1, 0x1003200
+# DISASM-NEXT: larl %r1, 0x1003200
+# DISASM-NEXT: lgrl %r1, 0x10021f8
+# DISASM-NEXT: lgrl %r1, 0x10021f8
+
+.data
+.globl var_align
+.hidden var_align
+ .align 2
+var_align:
+ .long 0
+
+.data
+.globl var_unalign
+.hidden var_unalign
+ .align 2
+ .byte 0
+var_unalign:
+ .byte 0
+
+.text
+.globl _start
+.type _start, @function
+_start:
+ lgrl %r1, var_align at GOT
+ lgrl %r1, var_align at GOT
+ lgrl %r1, var_unalign at GOT
+ lgrl %r1, var_unalign at GOT
diff --git a/lld/test/ELF/systemz-gotent-relax-und-dso.s b/lld/test/ELF/systemz-gotent-relax-und-dso.s
new file mode 100644
index 00000000000000..57369a417fd445
--- /dev/null
+++ b/lld/test/ELF/systemz-gotent-relax-und-dso.s
@@ -0,0 +1,68 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -relax-relocations -triple=s390x-unknown-linux %s -o %t.o
+# RUN: llvm-mc -filetype=obj -relax-relocations -triple=s390x-unknown-linux %S/Inputs/gotpc-relax-und-dso.s -o %tdso.o
+# RUN: ld.lld -shared %tdso.o -soname=t.so -o %t.so
+# RUN: ld.lld --hash-style=sysv -shared %t.o %t.so -o %t
+# RUN: llvm-readelf -r %t | FileCheck --check-prefix=RELOC %s
+# RUN: llvm-objdump --no-print-imm-hex -d %t | FileCheck --check-prefix=DISASM %s
+
+# RELOC-LABEL: Relocation section '.rela.dyn' at offset {{.*}} contains 3 entries:
+# RELOC: 00000000000023f8 000000010000000a R_390_GLOB_DAT 00000000000012d8 foo + 0
+# RELOC: 0000000000002400 000000030000000a R_390_GLOB_DAT 0000000000000000 und + 0
+# RELOC: 0000000000002408 000000040000000a R_390_GLOB_DAT 0000000000000000 dsofoo + 0
+
+# DISASM: Disassembly of section .text:
+# DISASM-EMPTY:
+# DISASM-NEXT: <foo>:
+# DISASM-NEXT: bc 0, 0
+# DISASM: <hid>:
+# DISASM-NEXT: bc 0, 0
+# DISASM: <_start>:
+# DISASM-NEXT: lgrl %r1, 0x2400
+# DISASM-NEXT: lgrl %r1, 0x2400
+# DISASM-NEXT: lgrl %r1, 0x2408
+# DISASM-NEXT: lgrl %r1, 0x2408
+# DISASM-NEXT: larl %r1, 0x12dc
+# DISASM-NEXT: larl %r1, 0x12dc
+# DISASM-NEXT: lgrl %r1, 0x23f8
+# DISASM-NEXT: lgrl %r1, 0x23f8
+# DISASM-NEXT: lgrl %r1, 0x2400
+# DISASM-NEXT: lgrl %r1, 0x2400
+# DISASM-NEXT: lgrl %r1, 0x2408
+# DISASM-NEXT: lgrl %r1, 0x2408
+# DISASM-NEXT: larl %r1, 0x12dc
+# DISASM-NEXT: larl %r1, 0x12dc
+# DISASM-NEXT: lgrl %r1, 0x23f8
+# DISASM-NEXT: lgrl %r1, 0x23f8
+
+.text
+.globl foo
+.type foo, @function
+foo:
+ nop
+
+.globl hid
+.hidden hid
+.type hid, @function
+hid:
+ nop
+
+.globl _start
+.type _start, @function
+_start:
+ lgrl %r1, und at GOT
+ lgrl %r1, und at GOT
+ lgrl %r1, dsofoo at GOT
+ lgrl %r1, dsofoo at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, foo at GOT
+ lgrl %r1, foo at GOT
+ lgrl %r1, und at GOT
+ lgrl %r1, und at GOT
+ lgrl %r1, dsofoo at GOT
+ lgrl %r1, dsofoo at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, foo at GOT
+ lgrl %r1, foo at GOT
diff --git a/lld/test/ELF/systemz-gotent-relax.s b/lld/test/ELF/systemz-gotent-relax.s
new file mode 100644
index 00000000000000..08e6a0e81b9f4c
--- /dev/null
+++ b/lld/test/ELF/systemz-gotent-relax.s
@@ -0,0 +1,92 @@
+# REQUIRES: systemz
+## Test R_390_GOTENT optimization.
+
+# RUN: llvm-mc -filetype=obj -relax-relocations -triple=s390x-unknown-linux %s -o %t.o
+# RUN: ld.lld %t.o -o %t1 --no-apply-dynamic-relocs
+# RUN: llvm-readelf -S -r -x .got.plt %t1 | FileCheck --check-prefixes=CHECK,NOAPPLY %s
+# RUN: ld.lld %t.o -o %t1 --apply-dynamic-relocs
+# RUN: llvm-readelf -S -r -x .got.plt %t1 | FileCheck --check-prefixes=CHECK,APPLY %s
+# RUN: ld.lld %t.o -o %t1
+# RUN: llvm-objdump --no-print-imm-hex -d %t1 | FileCheck --check-prefix=DISASM %s
+
+## --no-relax disables GOT optimization.
+# RUN: ld.lld --no-relax %t.o -o %t2
+# RUN: llvm-objdump --no-print-imm-hex -d %t2 | FileCheck --check-prefix=NORELAX %s
+
+## In our implementation, .got is retained even if all GOT-generating relocations are optimized.
+# CHECK: Name Type Address Off Size ES Flg Lk Inf Al
+# CHECK: .iplt PROGBITS 0000000001001240 000240 000020 00 AX 0 0 16
+# CHECK-NEXT: .got PROGBITS 0000000001002260 000260 000018 00 WA 0 0 8
+# CHECK-NEXT: .relro_padding NOBITS 0000000001002278 000278 000d88 00 WA 0 0 1
+# CHECK-NEXT: .got.plt PROGBITS 0000000001003278 000278 000008 00 WA 0 0 8
+
+## There is one R_S390_IRELATIVE relocation.
+# CHECK-LABEL: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
+# CHECK: 0000000001003278 000000000000003d R_390_IRELATIVE 10011e8
+
+# CHECK-LABEL: Hex dump of section '.got.plt':
+# NOAPPLY-NEXT: 0x01003278 00000000 00000000
+# APPLY-NEXT: 0x01003278 00000000 010011e8
+
+# DISASM: Disassembly of section .text:
+# DISASM-EMPTY:
+# DISASM-NEXT: <foo>:
+# DISASM-NEXT: 10011e0: 47 00 00 00 bc 0, 0
+# DISASM: <hid>:
+# DISASM-NEXT: 10011e4: 47 00 00 00 bc 0, 0
+# DISASM: <ifunc>:
+# DISASM-NEXT: 10011e8: 07 fe br %r14
+# DISASM: <_start>:
+# DISASM-NEXT: larl %r1, 0x10011e0
+# DISASM-NEXT: larl %r1, 0x10011e0
+# DISASM-NEXT: larl %r1, 0x10011e4
+# DISASM-NEXT: larl %r1, 0x10011e4
+# DISASM-NEXT: lgrl %r1, 0x1003278
+# DISASM-NEXT: lgrl %r1, 0x1003278
+# DISASM-NEXT: larl %r1, 0x10011e0
+# DISASM-NEXT: larl %r1, 0x10011e0
+# DISASM-NEXT: larl %r1, 0x10011e4
+# DISASM-NEXT: larl %r1, 0x10011e4
+# DISASM-NEXT: lgrl %r1, 0x1003278
+# DISASM-NEXT: lgrl %r1, 0x1003278
+
+# NORELAX-LABEL: <_start>:
+# NORELAX-COUNT-12: lgrl
+
+.text
+.globl foo
+
+.text
+.globl foo
+.type foo, @function
+foo:
+ nop
+
+.globl hid
+.hidden hid
+.type hid, @function
+hid:
+ nop
+
+.text
+.type ifunc STT_GNU_IFUNC
+.globl ifunc
+.type ifunc, @function
+ifunc:
+ br %r14
+
+.globl _start
+.type _start, @function
+_start:
+ lgrl %r1, foo at GOT
+ lgrl %r1, foo at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, ifunc at GOT
+ lgrl %r1, ifunc at GOT
+ lgrl %r1, foo at GOT
+ lgrl %r1, foo at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, hid at GOT
+ lgrl %r1, ifunc at GOT
+ lgrl %r1, ifunc at GOT
diff --git a/lld/test/ELF/systemz-init-padding.s b/lld/test/ELF/systemz-init-padding.s
new file mode 100644
index 00000000000000..d522d4d7573e5c
--- /dev/null
+++ b/lld/test/ELF/systemz-init-padding.s
@@ -0,0 +1,27 @@
+// REQUIRES: systemz
+// RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %p/Inputs/systemz-init.s -o systemz-init.o
+// RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+// RUN: ld.lld -dynamic-linker /lib/ld64.so.1 %t.o systemz-init.o -o %t
+// RUN: llvm-objdump -d --no-show-raw-insn -j .init %t | FileCheck %s
+
+// glibc < 2.39 used to align .init and .fini code at a 4-byte boundary.
+// When that happens, the linker must not pad the code with invalid
+// instructions, e.g. null bytes.
+ .section .init,"ax", at progbits
+ brasl %r14, startup
+
+// CHECK: <.init>:
+// CHECK-NEXT: brasl %r14,
+// CHECK-NEXT: bcr 0, %r7
+// CHECK-NEXT: lg %r4, 272(%r15)
+
+ .text
+ .globl startup
+ .p2align 4
+startup:
+ br %r14
+
+ .globl main
+ .p2align 4
+main:
+ br %r14
diff --git a/lld/test/ELF/systemz-pie.s b/lld/test/ELF/systemz-pie.s
new file mode 100644
index 00000000000000..bb971a82fb8ced
--- /dev/null
+++ b/lld/test/ELF/systemz-pie.s
@@ -0,0 +1,38 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t1.o
+
+## Check -pie.
+# RUN: ld.lld -pie %t1.o -o %t
+# RUN: llvm-readelf --file-headers --program-headers --dynamic %t | FileCheck %s
+
+# CHECK: ELF Header:
+# CHECK-NEXT: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00
+# CHECK-NEXT: Class: ELF64
+# CHECK-NEXT: Data: 2's complement, big endian
+# CHECK-NEXT: Version: 1 (current)
+# CHECK-NEXT: OS/ABI: UNIX - System V
+# CHECK-NEXT: ABI Version: 0
+# CHECK-NEXT: Type: DYN (Shared object file)
+# CHECK-NEXT: Machine: IBM S/390
+# CHECK-NEXT: Version: 0x1
+
+# CHECK: Program Headers:
+# CHECK-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
+# CHECK-NEXT: PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000188 0x000188 R 0x8
+# CHECK-NEXT: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x00020d 0x00020d R 0x1000
+# CHECK-NEXT: LOAD 0x000210 0x0000000000002210 0x0000000000002210 0x000090 0x000df0 RW 0x1000
+# CHECK-NEXT: DYNAMIC 0x000210 0x0000000000002210 0x0000000000002210 0x000090 0x000090 RW 0x8
+# CHECK-NEXT: GNU_RELRO 0x000210 0x0000000000002210 0x0000000000002210 0x000090 0x000df0 R 0x1
+# CHECK-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0
+
+# CHECK: Dynamic section at offset 0x210 contains 9 entries:
+# CHECK-NEXT: Tag Type Name/Value
+# CHECK-NEXT: 0x000000006ffffffb (FLAGS_1) PIE
+
+## Check -nopie
+# RUN: ld.lld -no-pie %t1.o -o %t2
+# RUN: llvm-readelf --file-headers %t2 | FileCheck %s --check-prefix=NOPIE
+# NOPIE-NOT: Type: DYN
+
+.globl _start
+_start:
diff --git a/lld/test/ELF/systemz-plt.s b/lld/test/ELF/systemz-plt.s
new file mode 100644
index 00000000000000..416a60a6866892
--- /dev/null
+++ b/lld/test/ELF/systemz-plt.s
@@ -0,0 +1,83 @@
+# REQUIRES: systemz
+# RUN: echo '.globl bar, weak; .type bar, at function; .type weak, at function; bar: weak:' > %t1.s
+
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %t1.s -o %t1.o
+# RUN: ld.lld -shared %t1.o -soname=t1.so -o %t1.so
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+# RUN: ld.lld %t.o %t1.so -z separate-code -o %t
+# RUN: llvm-readelf -S -s -r -x .got.plt %t | FileCheck %s
+# RUN: llvm-objdump -d %t | FileCheck --check-prefixes=DIS %s
+
+# CHECK: Section Headers:
+# CHECK: .plt PROGBITS 0000000001001020 001020 000060 00 AX 0 0 16
+# CHECK: .got PROGBITS 00000000010020d0 0020d0 000018 00 WA 0 0 8
+# CHECK: .got.plt PROGBITS 00000000010030e8 0020e8 000010 00 WA 0 0 8
+
+# CHECK: Relocation section '.rela.plt' at offset {{.*}} contains 2 entries:
+# CHECK: 00000000010030e8 000000010000000b R_390_JMP_SLOT 0000000000000000 bar + 0
+# CHECK: 00000000010030f0 000000020000000b R_390_JMP_SLOT 0000000000000000 weak + 0
+
+## A canonical PLT has a non-zero st_value. bar and weak are called but their
+## addresses are not taken, so a canonical PLT is not necessary.
+# CHECK: Symbol table '.dynsym' contains 3 entries:
+# CHECK-NEXT: Num: Value Size Type Bind Vis Ndx Name
+# CHECK-NEXT: 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
+# CHECK-NEXT: 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bar
+# CHECK-NEXT: 2: 0000000000000000 0 FUNC WEAK DEFAULT UND weak
+
+## The .got.plt slots relocated by .rela.plt point to .plt
+## This is required by glibc.
+# CHECK: Hex dump of section '.got.plt':
+# CHECK-NEXT: 0x010030e8 00000000 0100104e 00000000 0100106e
+
+# DIS: Disassembly of section .text:
+
+# DIS: 0000000001001000 <_start>:
+# DIS-NEXT: 1001000: c0 e5 00 00 00 09 brasl %r14, 0x1001012
+# DIS-NEXT: 1001006: c0 e5 00 00 00 1d brasl %r14, 0x1001040
+# DIS-NEXT: 100100c: c0 e5 00 00 00 2a brasl %r14, 0x1001060
+
+# DIS: 0000000001001012 <foo>:
+# DIS-NEXT: 1001012: 07 fe br %r14
+
+# DIS: Disassembly of section .plt:
+
+# DIS: 0000000001001020 <.plt>:
+# DIS-NEXT: 1001020: e3 10 f0 38 00 24 stg %r1, 56(%r15)
+# DIS-NEXT: 1001026: c0 10 00 00 08 55 larl %r1, 0x10020d0
+# DIS-NEXT: 100102c: d2 07 f0 30 10 08 mvc 48(8,%r15), 8(%r1)
+# DIS-NEXT: 1001032: e3 10 10 10 00 04 lg %r1, 16(%r1)
+# DIS-NEXT: 1001038: 07 f1 br %r1
+# DIS-NEXT: 100103a: 07 00 bcr 0, %r0
+# DIS-NEXT: 100103c: 07 00 bcr 0, %r0
+# DIS-NEXT: 100103e: 07 00 bcr 0, %r0
+# DIS-NEXT: 1001040: c0 10 00 00 10 54 larl %r1, 0x10030e8
+# DIS-NEXT: 1001046: e3 10 10 00 00 04 lg %r1, 0(%r1)
+# DIS-NEXT: 100104c: 07 f1 br %r1
+# DIS-NEXT: 100104e: 0d 10 basr %r1, 0
+# DIS-NEXT: 1001050: e3 10 10 0c 00 14 lgf %r1, 12(%r1)
+# DIS-NEXT: 1001056: c0 f4 ff ff ff e5 jg 0x1001020
+# DIS-NEXT: 100105c: 00 00 <unknown>
+# DIS-NEXT: 100105e: 00 00 <unknown>
+# DIS-NEXT: 1001060: c0 10 00 00 10 48 larl %r1, 0x10030f0
+# DIS-NEXT: 1001066: e3 10 10 00 00 04 lg %r1, 0(%r1)
+# DIS-NEXT: 100106c: 07 f1 br %r1
+# DIS-NEXT: 100106e: 0d 10 basr %r1, 0
+# DIS-NEXT: 1001070: e3 10 10 0c 00 14 lgf %r1, 12(%r1)
+# DIS-NEXT: 1001076: c0 f4 ff ff ff d5 jg 0x1001020
+# DIS-NEXT: 100107c: 00 00 <unknown>
+# DIS-NEXT: 100107e: 00 18 <unknown>
+
+.global _start, foo, bar
+.weak weak
+
+_start:
+ ## Use @plt to avoid generating direct references that would force
+ ## allocation of a canonical PLT entry.
+ brasl %r14, foo at plt
+ brasl %r14, bar at plt
+ brasl %r14, weak at plt
+
+## foo is local and non-preemptable, no PLT is generated.
+foo:
+ br %r14
diff --git a/lld/test/ELF/systemz-reloc-abs.s b/lld/test/ELF/systemz-reloc-abs.s
new file mode 100644
index 00000000000000..3a01ed7df3519a
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-abs.s
@@ -0,0 +1,29 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x %s -o %t.o
+# RUN: llvm-mc -filetype=obj -triple=s390x %S/Inputs/abs255.s -o %t255.o
+# RUN: llvm-mc -filetype=obj -triple=s390x %S/Inputs/abs256.s -o %t256.o
+# RUN: llvm-mc -filetype=obj -triple=s390x %S/Inputs/abs257.s -o %t257.o
+
+# RUN: ld.lld %t.o %t256.o -o %t
+# RUN: llvm-readelf -x .data %t | FileCheck %s
+# CHECK: 0x{{[0-9a-f]+}} ff80ffff 8000
+
+# RUN: not ld.lld %t.o %t255.o -o /dev/null 2>&1 | FileCheck --check-prefix=OVERFLOW1 %s
+# OVERFLOW1: relocation R_390_8 out of range: -129 is not in [-128, 255]
+# OVERFLOW1: relocation R_390_16 out of range: -32769 is not in [-32768, 65535]
+# OVERFLOW1: relocation R_390_32 out of range: -2147483649 is not in [-2147483648, 4294967295]
+
+# RUN: not ld.lld %t.o %t257.o -o /dev/null 2>&1 | FileCheck --check-prefix=OVERFLOW2 %s
+# OVERFLOW2: relocation R_390_8 out of range: 256 is not in [-128, 255]
+# OVERFLOW2: relocation R_390_16 out of range: 65536 is not in [-32768, 65535]
+# OVERFLOW2: relocation R_390_32 out of range: 4294967296 is not in [-2147483648, 4294967295]
+
+.globl _start
+_start:
+.data
+.byte foo - 1
+.byte foo - 384
+.word foo + 0xfeff
+.word foo - 0x8100
+.long foo + 0xfffffeff
+.long foo - 0x80000100
diff --git a/lld/test/ELF/systemz-reloc-disp12.s b/lld/test/ELF/systemz-reloc-disp12.s
new file mode 100644
index 00000000000000..3d32707d149fe7
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-disp12.s
@@ -0,0 +1,21 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym DISP=291 %s -o %t1.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym DISP=4095 %s -o %t2.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym DISP=4096 %s -o %t3.o
+
+# RUN: ld.lld --section-start=.text=0x0 %t1.o -o %t1out
+# RUN: ld.lld --section-start=.text=0x0 %t2.o -o %t2out
+# RUN: not ld.lld --section-start=.text=0x0 %t3.o -o /dev/null 2>&1 | FileCheck %s --check-prefix RANGE
+
+# RANGE: relocation R_390_12 out of range: 4096 is not in [0, 4095]
+
+# RUN: llvm-readelf --hex-dump=.text %t1out | FileCheck %s -DINSN=58678123 --check-prefix DUMP
+# RUN: llvm-readelf --hex-dump=.text %t2out | FileCheck %s -DINSN=58678fff --check-prefix DUMP
+
+# DUMP: 0x00000000 [[INSN]]
+
+.text
+.globl _start
+_start:
+ .reloc .+2, R_390_12, DISP
+ l %r6, 0(%r7,%r8)
diff --git a/lld/test/ELF/systemz-reloc-disp20.s b/lld/test/ELF/systemz-reloc-disp20.s
new file mode 100644
index 00000000000000..88cd657c6ae3cb
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-disp20.s
@@ -0,0 +1,21 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym DISP=74565 %s -o %t1.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym DISP=524287 %s -o %t2.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym DISP=524288 %s -o %t3.o
+
+# RUN: ld.lld --section-start=.text=0x0 %t1.o -o %t1out
+# RUN: ld.lld --section-start=.text=0x0 %t2.o -o %t2out
+# RUN: not ld.lld --section-start=.text=0x0 %t3.o -o /dev/null 2>&1 | FileCheck %s --check-prefix RANGE
+
+# RANGE: relocation R_390_20 out of range: 524288 is not in [-524288, 524287]
+
+# RUN: llvm-readelf --hex-dump=.text %t1out | FileCheck %s -DINSN="e3678345 1204" --check-prefix DUMP
+# RUN: llvm-readelf --hex-dump=.text %t2out | FileCheck %s -DINSN="e3678fff 7f04" --check-prefix DUMP
+
+# DUMP: 0x00000000 [[INSN]]
+
+.text
+.globl _start
+_start:
+ .reloc .+2, R_390_20, DISP
+ lg %r6, 0(%r7,%r8)
diff --git a/lld/test/ELF/systemz-reloc-pc16.s b/lld/test/ELF/systemz-reloc-pc16.s
new file mode 100644
index 00000000000000..4e646bdc0cb996
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-pc16.s
@@ -0,0 +1,39 @@
+# REQUIRES: systemz
+# RUN: rm -rf %t && split-file %s %t
+
+## Check recompile with -fPIC error message
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %t/shared.s -o %t/shared.o
+# RUN: not ld.lld -shared %t/shared.o -o /dev/null 2>&1 | FileCheck %s
+
+# CHECK: error: relocation R_390_PC16 cannot be used against symbol '_shared'; recompile with -fPIC
+# CHECK: >>> defined in {{.*}}
+# CHECK: >>> referenced by {{.*}}:(.data+0x1)
+
+## Check patching of negative addends
+
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym ADDEND=1 %t/addend.s -o %t/1.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym ADDEND=32768 %t/addend.s -o %t/2.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym ADDEND=32769 %t/addend.s -o %t/3.o
+
+# RUN: ld.lld --section-start=.text=0x0 %t/1.o -o %t/1out
+# RUN: ld.lld --section-start=.text=0x0 %t/2.o -o %t/2out
+# RUN: not ld.lld --section-start=.text=0x0 %t/3.o -o /dev/null 2>&1 | FileCheck %s --check-prefix RANGE
+
+# RANGE: relocation R_390_PC16 out of range
+
+# RUN: llvm-readelf --hex-dump=.text %t/1out | FileCheck %s -DADDEND=ffff --check-prefix DUMP
+# RUN: llvm-readelf --hex-dump=.text %t/2out | FileCheck %s -DADDEND=8000 --check-prefix DUMP
+
+# DUMP: 0x00000000 [[ADDEND]]
+
+#--- shared.s
+.data
+ .byte 0xe8
+ .word _shared - .
+
+#--- addend.s
+.text
+.globl _start
+_start:
+ .reloc ., R_390_PC16, .text-ADDEND
+ .space 2
diff --git a/lld/test/ELF/systemz-reloc-pc32.s b/lld/test/ELF/systemz-reloc-pc32.s
new file mode 100644
index 00000000000000..49ff0bd3baa89f
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-pc32.s
@@ -0,0 +1,39 @@
+# REQUIRES: systemz
+# RUN: rm -rf %t && split-file %s %t
+
+## Check recompile with -fPIC error message
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %t/shared.s -o %t/shared.o
+# RUN: not ld.lld -shared %t/shared.o -o /dev/null 2>&1 | FileCheck %s
+
+# CHECK: error: relocation R_390_PC32 cannot be used against symbol '_shared'; recompile with -fPIC
+# CHECK: >>> defined in {{.*}}
+# CHECK: >>> referenced by {{.*}}:(.data+0x1)
+
+## Check patching of negative addends
+
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym ADDEND=1 %t/addend.s -o %t/1.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym ADDEND=2147483648 %t/addend.s -o %t/2.o
+# RUN: llvm-mc -filetype=obj -triple=s390x -defsym ADDEND=2147483649 %t/addend.s -o %t/3.o
+
+# RUN: ld.lld --section-start=.text=0x0 %t/1.o -o %t/1out
+# RUN: ld.lld --section-start=.text=0x0 %t/2.o -o %t/2out
+# RUN: not ld.lld --section-start=.text=0x0 %t/3.o -o /dev/null 2>&1 | FileCheck %s --check-prefix RANGE
+
+# RANGE: relocation R_390_PC32 out of range
+
+# RUN: llvm-readelf --hex-dump=.text %t/1out | FileCheck %s -DADDEND=ffffffff --check-prefix DUMP
+# RUN: llvm-readelf --hex-dump=.text %t/2out | FileCheck %s -DADDEND=80000000 --check-prefix DUMP
+
+# DUMP: 0x00000000 [[ADDEND]]
+
+#--- shared.s
+.data
+ .byte 0xe8
+ .long _shared - .
+
+#--- addend.s
+.text
+.globl _start
+_start:
+ .reloc ., R_390_PC32, .text-ADDEND
+ .space 4
diff --git a/lld/test/ELF/systemz-reloc-range12.s b/lld/test/ELF/systemz-reloc-range12.s
new file mode 100644
index 00000000000000..571ba60ee78442
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-range12.s
@@ -0,0 +1,16 @@
+// REQUIRES: systemz
+// RUN: llvm-mc %s -o %t.o -triple s390x-unknown-linux -mcpu=z13 -filetype=obj
+// RUN: not ld.lld %t.o -o /dev/null -shared 2>&1 | FileCheck %s
+// RUN: ld.lld --noinhibit-exec -shared %t.o -o %t 2>&1 | FileCheck %s
+// RUN: ls %t
+
+// CHECK: {{.*}}:(.text+0x1): relocation R_390_PC12DBL out of range: 4096 is not in [-4096, 4095]
+// CHECK-NOT: relocation
+
+ bprp 1,foo,0
+ bprp 1,foo,0
+
+ .hidden foo
+ .section .text.foo
+ .zero 0xff4
+foo:
diff --git a/lld/test/ELF/systemz-reloc-range16.s b/lld/test/ELF/systemz-reloc-range16.s
new file mode 100644
index 00000000000000..201eaead43fc83
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-range16.s
@@ -0,0 +1,16 @@
+// REQUIRES: systemz
+// RUN: llvm-mc %s -o %t.o -triple s390x-unknown-linux -filetype=obj
+// RUN: not ld.lld %t.o -o /dev/null -shared 2>&1 | FileCheck %s
+// RUN: ld.lld --noinhibit-exec -shared %t.o -o %t 2>&1 | FileCheck %s
+// RUN: ls %t
+
+// CHECK: {{.*}}:(.text+0x2): relocation R_390_PC16DBL out of range: 65536 is not in [-65536, 65535]
+// CHECK-NOT: relocation
+
+ j foo
+ j foo
+
+ .hidden foo
+ .bss
+ .zero 0xdf88
+foo:
diff --git a/lld/test/ELF/systemz-reloc-range24.s b/lld/test/ELF/systemz-reloc-range24.s
new file mode 100644
index 00000000000000..315fdff8a4c098
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-range24.s
@@ -0,0 +1,16 @@
+// REQUIRES: systemz
+// RUN: llvm-mc %s -o %t.o -triple s390x-unknown-linux -mcpu=z13 -filetype=obj
+// RUN: not ld.lld %t.o -o /dev/null -shared 2>&1 | FileCheck %s
+// RUN: ld.lld --noinhibit-exec -shared %t.o -o %t 2>&1 | FileCheck %s
+// RUN: ls %t
+
+// CHECK: {{.*}}:(.text+0x3): relocation R_390_PC24DBL out of range: 16777216 is not in [-16777216, 16777215]
+// CHECK-NOT: relocation
+
+ bprp 1,0,foo
+ bprp 1,0,foo
+
+ .hidden foo
+ .section .text.foo
+ .zero 0xfffff4
+foo:
diff --git a/lld/test/ELF/systemz-reloc-range32.s b/lld/test/ELF/systemz-reloc-range32.s
new file mode 100644
index 00000000000000..215d36f386f7bb
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-range32.s
@@ -0,0 +1,16 @@
+// REQUIRES: systemz
+// RUN: llvm-mc %s -o %t.o -triple s390x-unknown-linux -filetype=obj
+// RUN: not ld.lld %t.o -o /dev/null -shared 2>&1 | FileCheck %s
+// RUN: ld.lld --noinhibit-exec -shared %t.o -o %t 2>&1 | FileCheck %s
+// RUN: ls %t
+
+// CHECK: {{.*}}:(.text+0x2): relocation R_390_PC32DBL out of range: 4294967296 is not in [-4294967296, 4294967295]
+// CHECK-NOT: relocation
+
+ larl %r0, foo
+ larl %r0, foo
+
+ .hidden foo
+ .bss
+ .zero 0xffffdf80
+foo:
diff --git a/lld/test/ELF/systemz-shared.s b/lld/test/ELF/systemz-shared.s
new file mode 100644
index 00000000000000..cff03e0e0b1d45
--- /dev/null
+++ b/lld/test/ELF/systemz-shared.s
@@ -0,0 +1,86 @@
+// REQUIRES: systemz
+// RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+// RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %p/Inputs/shared.s -o %t2.o
+// RUN: ld.lld -shared %t2.o -soname=%t2.so -o %t2.so
+// RUN: llvm-readelf -S %t2.so | FileCheck --check-prefix=SO %s
+
+// RUN: ld.lld -dynamic-linker /lib/ld64.so.1 -rpath foo -rpath bar --export-dynamic %t.o %t2.so -o %t
+// RUN: llvm-readelf -S -s -l --dynamic %t | FileCheck %s
+
+// RUN: ld.lld %t.o %t2.so %t2.so -o %t2
+// RUN: llvm-readelf --dyn-syms %t2 | FileCheck --check-prefix=DONT_EXPORT %s
+
+// Make sure .symtab is properly aligned.
+// SO: .symtab SYMTAB 0000000000000000 {{[^ ]*}} {{[^ ]*}} {{[^ ]*}} {{[^ ]*}} {{[^ ]*}} 8
+
+// CHECK: Section Headers:
+// CHECK-NEXT: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
+// CHECK-NEXT: [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
+// CHECK-NEXT: [ 1] .interp PROGBITS 0000000001000200 000200 00000f 00 A 0 0 1
+// CHECK-NEXT: [ 2] .dynsym DYNSYM 0000000001000210 000210 000060 18 A 5 1 8
+// CHECK-NEXT: [ 3] .gnu.hash GNU_HASH 0000000001000270 000270 000020 00 A 2 0 8
+// CHECK-NEXT: [ 4] .hash HASH 0000000001000290 000290 000028 04 A 2 0 4
+// CHECK-NEXT: [ 5] .dynstr STRTAB 00000000010002b8 0002b8 {{.*}} 00 A 0 0 1
+// CHECK-NEXT: [ 6] .rela.dyn RELA {{0*}}[[RELADYN:.*]] {{.*}} 000030 18 A 2 0 8
+// CHECK-NEXT: [ 7] .text PROGBITS {{0*}}[[TEXT:.*]] {{.*}} 00000c 00 AX 0 0 4
+// CHECK-NEXT: [ 8] .dynamic DYNAMIC {{0*}}[[DYNAMIC:.*]] {{.*}} 0000d0 10 WA 5 0 8
+// CHECK-NEXT: [ 9] .got PROGBITS {{.*}} {{.*}} 000028 00 WA 0 0 8
+// CHECK-NEXT: [10] .relro_padding NOBITS {{.*}} {{.*}} {{.*}} 00 WA 0 0 1
+// CHECK-NEXT: [11] .comment PROGBITS 0000000000000000 {{.*}} 000008 01 MS 0 0 1
+// CHECK-NEXT: [12] .symtab SYMTAB 0000000000000000 {{.*}} {{.*}} 18 14 2 8
+// CHECK-NEXT: [13] .shstrtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
+// CHECK-NEXT: [14] .strtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
+
+// CHECK: Program Headers:
+// CHECK-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
+// CHECK-NEXT: PHDR 0x000040 0x0000000001000040 0x0000000001000040 0x0001c0 0x0001c0 R 0x8
+// CHECK-NEXT: INTERP 0x000200 0x0000000001000200 0x0000000001000200 0x00000f 0x00000f R 0x1
+// CHECK-NEXT: [Requesting program interpreter: /lib/ld64.so.1]
+// CHECK-NEXT: LOAD 0x000000 0x0000000001000000 0x0000000001000000 0x{{.*}} 0x{{.*}} R 0x1000
+// CHECK-NEXT: LOAD 0x{{.*}} 0x{{0*}}[[TEXT]] 0x{{0*}}[[TEXT]] 0x00000c 0x00000c R E 0x1000
+// CHECK-NEXT: LOAD 0x{{.*}} 0x{{0*}}[[DYNAMIC]] 0x{{0*}}[[DYNAMIC]] 0x{{.*}} 0x{{.*}} RW 0x1000
+// CHECK-NEXT: DYNAMIC 0x{{.*}} 0x{{0*}}[[DYNAMIC]] 0x{{0*}}[[DYNAMIC]] 0x0000d0 0x0000d0 RW 0x8
+// CHECK-NEXT: GNU_RELRO 0x{{.*}} 0x{{0*}}[[DYNAMIC]] 0x{{0*}}[[DYNAMIC]] 0x{{.*}} 0x{{.*}} R 0x1
+// CHECK-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0
+
+// CHECK: Dynamic section at offset {{.*}} contains 13 entries:
+// CHECK-NEXT: Tag Type Name/Value
+// CHECK-NEXT: 0x000000000000001d (RUNPATH) Library runpath: [foo:bar]
+// CHECK-NEXT: 0x0000000000000001 (NEEDED) Shared library: [{{.*}}2.so]
+// CHECK-NEXT: 0x0000000000000015 (DEBUG) 0x0
+// CHECK-NEXT: 0x0000000000000007 (RELA) 0x[[RELADYN]]
+// CHECK-NEXT: 0x0000000000000008 (RELASZ) 48 (bytes)
+// CHECK-NEXT: 0x0000000000000009 (RELAENT) 24 (bytes)
+// CHECK-NEXT: 0x0000000000000006 (SYMTAB) 0x1000210
+// CHECK-NEXT: 0x000000000000000b (SYMENT) 24 (bytes)
+// CHECK-NEXT: 0x0000000000000005 (STRTAB) 0x10002b8
+// CHECK-NEXT: 0x000000000000000a (STRSZ) {{.*}} (bytes)
+// CHECK-NEXT: 0x000000006ffffef5 (GNU_HASH) 0x
+// CHECK-NEXT: 0x0000000000000004 (HASH) 0x
+// CHECK-NEXT: 0x0000000000000000 (NULL) 0x0
+
+// CHECK: Symbol table '.dynsym' contains 4 entries:
+// CHECK-NEXT: Num: Value Size Type Bind Vis Ndx Name
+// CHECK-NEXT: 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
+// CHECK-NEXT: 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bar
+// CHECK-NEXT: 2: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND zed
+// CHECK-NEXT: 3: {{.*}} 0 NOTYPE GLOBAL DEFAULT 7 _start
+
+// CHECK: Symbol table '.symtab' contains 5 entries:
+// CHECK-NEXT: Num: Value Size Type Bind Vis Ndx Name
+// CHECK-NEXT: 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
+// CHECK-NEXT: 1: {{0*}}[[DYNAMIC]] 0 NOTYPE LOCAL HIDDEN 8 _DYNAMIC
+// CHECK-NEXT: 2: {{.*}} 0 NOTYPE GLOBAL DEFAULT 7 _start
+// CHECK-NEXT: 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bar
+// CHECK-NEXT: 4: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND zed
+
+// DONT_EXPORT: Symbol table '.dynsym' contains 3 entries:
+// DONT_EXPORT-NEXT: Num: Value Size Type Bind Vis Ndx Name
+// DONT_EXPORT-NEXT: 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
+// DONT_EXPORT-NEXT: 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bar
+// DONT_EXPORT-NEXT: 2: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND zed
+
+.global _start
+_start:
+ lgrl %r1,bar at GOT
+ lgrl %r2,zed at GOT
diff --git a/lld/test/ELF/systemz-tls-gd.s b/lld/test/ELF/systemz-tls-gd.s
new file mode 100644
index 00000000000000..3d9fe4049b7191
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-gd.s
@@ -0,0 +1,142 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+# RUN: echo '.tbss; .globl b, c; b: .zero 4; c:' | llvm-mc -filetype=obj -triple=s390x-unknown-linux - -o %t1.o
+# RUN: ld.lld -shared -soname=t1.so %t1.o -o %t1.so
+
+# RUN: ld.lld -shared %t.o %t1.o -o %t.so
+# RUN: llvm-readelf -r %t.so | FileCheck --check-prefix=GD-REL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t.so | FileCheck --check-prefix=GD %s
+# RUN: llvm-objdump --section .data.rel.ro --full-contents %t.so | FileCheck --check-prefix=GD-DATA %s
+
+# RUN: ld.lld %t.o %t1.o -o %t
+# RUN: llvm-readelf -r %t | FileCheck --check-prefix=NOREL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck --check-prefix=LE %s
+# RUN: llvm-objdump --section .data.rel.ro --full-contents %t | FileCheck --check-prefix=LE-DATA %s
+
+# RUN: ld.lld %t.o %t1.so -o %t
+# RUN: llvm-readelf -r %t | FileCheck --check-prefix=IE-REL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck --check-prefix=IE %s
+# RUN: llvm-objdump --section .data.rel.ro --full-contents %t | FileCheck --check-prefix=IE-DATA %s
+
+# GD-REL: Relocation section '.rela.dyn' at offset {{.*}} contains 6 entries:
+# GD-REL: 0000000000002570 0000000200000036 R_390_TLS_DTPMOD 0000000000000008 a + 0
+# GD-REL: 0000000000002578 0000000200000037 R_390_TLS_DTPOFF 0000000000000008 a + 0
+# GD-REL: 0000000000002580 0000000300000036 R_390_TLS_DTPMOD 000000000000000c b + 0
+# GD-REL: 0000000000002588 0000000300000037 R_390_TLS_DTPOFF 000000000000000c b + 0
+# GD-REL: 0000000000002590 0000000400000036 R_390_TLS_DTPMOD 0000000000000010 c + 0
+# GD-REL: 0000000000002598 0000000400000037 R_390_TLS_DTPOFF 0000000000000010 c + 0
+
+## _GLOBAL_OFFSET_TABLE is at 0x2558
+# GD: larl %r12, 0x2558
+
+## GOT offset of the TLS descriptor for a is at 0x2460
+# GD-NEXT: lgrl %r2, 0x2460
+# GD-NEXT: brasl %r14, 0x1440
+# GD-NEXT: lgf %r2, 0(%r2,%r7)
+
+## GOT offset of the TLS descriptor for b is at 0x2468
+# GD-NEXT: lgrl %r2, 0x2468
+# GD-NEXT: brasl %r14, 0x1440
+# GD-NEXT: lgf %r2, 0(%r2,%r7)
+
+## GOT offset of the TLS descriptor for c is at 0x2470
+# GD-NEXT: lgrl %r2, 0x2470
+# GD-NEXT: brasl %r14, 0x1440
+# GD-NEXT: lgf %r2, 0(%r2,%r7)
+
+## TLS descriptor addresses / GOT offsets:
+# a: 0x2570 / 0x18
+# b: 0x2580 / 0x28
+# c: 0x2590 / 0x38
+# GD-DATA: 2460 00000000 00000018 00000000 00000028
+# GD-DATA: 2470 00000000 00000038
+
+# NOREL: no relocations
+
+## _GLOBAL_OFFSET_TABLE is at 0x1002230
+# LE: larl %r12, 0x1002230
+
+## TP offset of a is at 0x1002218
+# LE-NEXT: lgrl %r2, 0x1002218
+# LE-NEXT: brcl 0,
+# LE-NEXT: lgf %r2, 0(%r2,%r7)
+
+## TP offset of b is at 0x1002220
+# LE-NEXT: lgrl %r2, 0x1002220
+# LE-NEXT: brcl 0,
+# LE-NEXT: lgf %r2, 0(%r2,%r7)
+
+## TP offset of c is at 0x1002228
+# LE-NEXT: lgrl %r2, 0x1002228
+# LE-NEXT: brcl 0,
+# LE-NEXT: lgf %r2, 0(%r2,%r7)
+
+## TP offsets
+# a: -8
+# b: -4
+# c: 0
+# LE-DATA: 1002218 ffffffff fffffff8 ffffffff fffffffc
+# LE-DATA: 1002228 00000000 00000000
+
+
+# IE-REL: Relocation section '.rela.dyn' at offset {{.*}} contains 2 entries:
+# IE-REL: 0000000001002430 0000000200000038 R_390_TLS_TPOFF 0000000000000000 b + 0
+# IE-REL: 0000000001002438 0000000300000038 R_390_TLS_TPOFF 0000000000000000 c + 0
+
+## _GLOBAL_OFFSET_TABLE is at 0x1002418
+# IE: larl %r12, 0x1002418
+
+## TP offset of a is at 0x1002340
+# IE-NEXT: lgrl %r2, 0x1002340
+# IE-NEXT: brcl 0,
+# IE-NEXT: lgf %r2, 0(%r2,%r7)
+
+## GOT offset of the TP offset for b is at 0x1002348
+# IE-NEXT: lgrl %r2, 0x1002348
+# IE-NEXT: lg %r2, 0(%r2,%r12)
+# IE-NEXT: lgf %r2, 0(%r2,%r7)
+
+## GOT offset of the TP offset for c is at 0x1002350
+# IE-NEXT: lgrl %r2, 0x1002350
+# IE-NEXT: lg %r2, 0(%r2,%r12)
+# IE-NEXT: lgf %r2, 0(%r2,%r7)
+
+## TP offsets (a) / GOT offset of TP offsets (b, c)
+# a: -4
+# b: 0x1002430 / 0x18
+# c: 0x1002438 / 0x20
+# IE-DATA: 1002340 ffffffff fffffffc 00000000 00000018
+# IE-DATA: 1002350 00000000 00000020
+
+
+ear %r7,%a0
+sllg %r7,%r1,32
+ear %r7,%a1
+larl %r12,_GLOBAL_OFFSET_TABLE_
+
+lgrl %r2,.LC0
+brasl %r14,__tls_get_offset at PLT:tls_gdcall:a
+lgf %r2,0(%r2,%r7)
+
+lgrl %r2,.LC1
+brasl %r14,__tls_get_offset at PLT:tls_gdcall:b
+lgf %r2,0(%r2,%r7)
+
+lgrl %r2,.LC2
+brasl %r14,__tls_get_offset at PLT:tls_gdcall:c
+lgf %r2,0(%r2,%r7)
+
+ .section .data.rel.ro,"aw"
+ .align 8
+.LC0:
+ .quad a at TLSGD
+.LC1:
+ .quad b at TLSGD
+.LC2:
+ .quad c at TLSGD
+
+ .section .tbss
+ .globl a
+ .zero 8
+a:
+ .zero 4
diff --git a/lld/test/ELF/systemz-tls-ie.s b/lld/test/ELF/systemz-tls-ie.s
new file mode 100644
index 00000000000000..44cbef99ab26cb
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-ie.s
@@ -0,0 +1,74 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-readelf -r %t.so | FileCheck --check-prefix=IE-REL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t.so | FileCheck --check-prefix=IE %s
+
+# RUN: ld.lld %t.o -o %t
+# RUN: llvm-readelf -r %t | FileCheck --check-prefix=NOREL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck --check-prefix=LE %s
+# RUN: llvm-objdump --section .got --full-contents %t | FileCheck --check-prefix=LE-GOT %s
+
+# IE-REL: Relocation section '.rela.dyn' at offset {{.*}} contains 3 entries:
+# IE-REL: 0000000000002400 0000000100000038 R_390_TLS_TPOFF 0000000000000008 a + 0
+# IE-REL: 0000000000002408 0000000200000038 R_390_TLS_TPOFF 000000000000000c b + 0
+# IE-REL: 0000000000002410 0000000300000038 R_390_TLS_TPOFF 0000000000000010 c + 0
+
+## TP offset for a is at 0x2400
+# IE: lgrl %r1, 0x2400
+# IE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offset for b is at 0x2408
+# IE-NEXT: lgrl %r1, 0x2408
+# IE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offset for c is at 0x2410
+# IE-NEXT: lgrl %r1, 0x2410
+# IE-NEXT: lgf %r1, 0(%r1,%r7)
+
+# NOREL: no relocations
+
+## TP offset for a is at 0x1002218
+# LE: lgrl %r1, 0x1002218
+# LE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offset for b is at 0x1002220
+# LE-NEXT: lgrl %r1, 0x1002220
+# LE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offset for c is at 0x1002228
+# LE-NEXT: lgrl %r1, 0x1002228
+# LE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offsets in GOT:
+# a: -8
+# b: -4
+# c: 0
+# LE-GOT: 1002200 00000000 00000000 00000000 00000000
+# LE-GOT: 1002210 00000000 00000000 ffffffff fffffff8
+# LE-GOT: 1002220 ffffffff fffffffc 00000000 00000000
+
+ear %r7,%a0
+sllg %r7,%r1,32
+ear %r7,%a1
+
+lgrl %r1, a at indntpoff
+lgf %r1,0(%r1,%r7)
+
+lgrl %r1, b at indntpoff
+lgf %r1,0(%r1,%r7)
+
+lgrl %r1, c at indntpoff
+lgf %r1,0(%r1,%r7)
+
+ .section .tbss
+ .globl a
+ .globl b
+ .globl c
+ .zero 8
+a:
+ .zero 4
+b:
+ .zero 4
+c:
diff --git a/lld/test/ELF/systemz-tls-ld.s b/lld/test/ELF/systemz-tls-ld.s
new file mode 100644
index 00000000000000..600ca205c3759e
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-ld.s
@@ -0,0 +1,114 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-readelf -r %t.so | FileCheck --check-prefix=LD-REL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t.so | FileCheck --check-prefix=LD %s
+# RUN: llvm-objdump --section .data.rel.ro --full-contents %t.so | FileCheck --check-prefix=LD-DATA %s
+
+# RUN: ld.lld %t.o -o %t
+# RUN: llvm-readelf -r %t | FileCheck --check-prefix=NOREL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck --check-prefix=LE %s
+# RUN: llvm-objdump --section .data.rel.ro --full-contents %t | FileCheck --check-prefix=LE-DATA %s
+
+# LD-REL: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
+# LD-REL: 00000000000024f8 0000000000000036 R_390_TLS_DTPMOD 0
+
+## _GLOBAL_OFFSET_TABLE is at 0x24e0
+# LD: larl %r12, 0x24e0
+
+## GOT offset of the LDM TLS descriptor is at 0x23e0
+# LD-NEXT: lgrl %r2, 0x23e0
+# LD-NEXT: brasl %r14, 0x13c0
+# LD-NEXT: la %r2, 0(%r2,%r7)
+
+## DTP offset for a is at 0x23e8
+# LD-NEXT: lgrl %r1, 0x23e8
+# LD-NEXT: lgf %r1, 0(%r1,%r2)
+
+## DTP offset for b is at 0x23f0
+# LD-NEXT: lgrl %r1, 0x23f0
+# LD-NEXT: lgf %r1, 0(%r1,%r2)
+
+## DTP offset for c is at 0x23f8
+# LD-NEXT: lgrl %r1, 0x23f8
+# LD-NEXT: lgf %r1, 0(%r1,%r2)
+
+## LDM GOT offset / DTP offsets:
+# LDM TLS: 0x24f8 / 0x18
+# a: 8
+# b: 12
+# c: 16
+# LD-DATA: 23e0 00000000 00000018 00000000 00000008
+# LD-DATA: 23f0 00000000 0000000c 00000000 00000010
+
+# NOREL: no relocations
+
+## _GLOBAL_OFFSET_TABLE is at 0x1002230
+# LE: larl %r12, 0x1002230
+
+## GOT offset of the LDM TLS descriptor is at 0x1002210
+# LE-NEXT: lgrl %r2, 0x1002210
+# LE-NEXT: brcl 0,
+# LE-NEXT: la %r2, 0(%r2,%r7)
+
+## TP offset for a is at 0x1002218
+# LE-NEXT: lgrl %r1, 0x1002218
+# LE-NEXT: lgf %r1, 0(%r1,%r2)
+
+## TP offset for b is at 0x1002220
+# LE-NEXT: lgrl %r1, 0x1002220
+# LE-NEXT: lgf %r1, 0(%r1,%r2)
+
+## TP offset for c is at 0x1002228
+# LE-NEXT: lgrl %r1, 0x1002228
+# LE-NEXT: lgf %r1, 0(%r1,%r2)
+
+## zeroed LDM / TP offsets:
+# LDM TLS: 0
+# a: -8
+# b: -4
+# c: 0
+# LE-DATA: 1002210 00000000 00000000 ffffffff fffffff8
+# LE-DATA: 1002220 ffffffff fffffffc 00000000 00000000
+
+
+ear %r7,%a0
+sllg %r7,%r1,32
+ear %r7,%a1
+larl %r12,_GLOBAL_OFFSET_TABLE_
+
+lgrl %r2,.LC0
+brasl %r14,__tls_get_offset at PLT:tls_ldcall:a
+la %r2,0(%r2,%r7)
+
+lgrl %r1, .LC1
+lgf %r1,0(%r1,%r2)
+
+lgrl %r1, .LC2
+lgf %r1,0(%r1,%r2)
+
+lgrl %r1, .LC3
+lgf %r1,0(%r1,%r2)
+
+ .section .data.rel.ro,"aw"
+ .align 8
+.LC0:
+ .quad a at TLSLDM
+.LC1:
+ .quad a at DTPOFF
+.LC2:
+ .quad b at DTPOFF
+.LC3:
+ .quad c at DTPOFF
+
+ .section .tbss
+ .globl a
+ .globl b
+ .globl c
+ .zero 8
+a:
+ .zero 4
+b:
+ .zero 4
+c:
diff --git a/lld/test/ELF/systemz-tls-le.s b/lld/test/ELF/systemz-tls-le.s
new file mode 100644
index 00000000000000..9e41fc768da391
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-le.s
@@ -0,0 +1,61 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
+
+# RUN: ld.lld %t.o -o %t
+# RUN: llvm-readelf -r %t | FileCheck --check-prefix=NOREL %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck --check-prefix=LE %s
+# RUN: llvm-objdump --section .data.rel.ro --full-contents %t | FileCheck --check-prefix=LE-DATA %s
+
+# NOREL: no relocations
+
+## TP offset for a is at 0x1002200
+# LE: lgrl %r1, 0x1002200
+# LE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offset for b is at 0x1002208
+# LE-NEXT: lgrl %r1, 0x1002208
+# LE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offset for c is at 0x1002210
+# LE-NEXT: lgrl %r1, 0x1002210
+# LE-NEXT: lgf %r1, 0(%r1,%r7)
+
+## TP offsets:
+# a: -8
+# b: -4
+# c: 0
+# LE-DATA: 1002200 ffffffff fffffff8 ffffffff fffffffc
+# LE-DATA: 1002210 00000000 00000000
+
+ear %r7,%a0
+sllg %r7,%r1,32
+ear %r7,%a1
+
+lgrl %r1, .LC0
+lgf %r1,0(%r1,%r7)
+
+lgrl %r1, .LC1
+lgf %r1,0(%r1,%r7)
+
+lgrl %r1, .LC2
+lgf %r1,0(%r1,%r7)
+
+ .section .data.rel.ro,"aw"
+ .align 8
+.LC0:
+ .quad a at ntpoff
+.LC1:
+ .quad b at ntpoff
+.LC2:
+ .quad c at ntpoff
+
+ .section .tbss
+ .globl a
+ .globl b
+ .globl c
+ .zero 8
+a:
+ .zero 4
+b:
+ .zero 4
+c:
diff --git a/lld/test/lit.cfg.py b/lld/test/lit.cfg.py
index b3e07f1f823cc4..d309c2ad4ee284 100644
--- a/lld/test/lit.cfg.py
+++ b/lld/test/lit.cfg.py
@@ -83,6 +83,7 @@
"PowerPC": "ppc",
"RISCV": "riscv",
"Sparc": "sparc",
+ "SystemZ": "systemz",
"WebAssembly": "wasm",
"X86": "x86",
},
More information about the llvm-commits
mailing list