[lld] [lld] Add target support for SystemZ (s390x) (PR #75643)

Ulrich Weigand via llvm-commits llvm-commits at lists.llvm.org
Fri Dec 15 11:16:46 PST 2023


https://github.com/uweigand created https://github.com/llvm/llvm-project/pull/75643

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.

>From b02fb3abb3a808bb432790524a8c04b579fba5d7 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                    | 581 ++++++++++++++++++++
 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                | 338 ++++++++++++
 lld/test/ELF/emulation-systemz.s            |  38 ++
 lld/test/ELF/lto/systemz.ll                 |  19 +
 lld/test/ELF/systemz-gnu-ifunc.s            | 129 +++++
 lld/test/ELF/systemz-got.s                  |  33 ++
 lld/test/ELF/systemz-gotent-relax-align.s   |  49 ++
 lld/test/ELF/systemz-gotent-relax-und-dso.s |  73 +++
 lld/test/ELF/systemz-gotent-relax.s         |  93 ++++
 lld/test/ELF/systemz-init-padding.s         |  27 +
 lld/test/ELF/systemz-pie.s                  |  59 ++
 lld/test/ELF/systemz-plt.s                  |  73 +++
 lld/test/ELF/systemz-reloc-abs.s            |  29 +
 lld/test/ELF/systemz-reloc-disp12.s         |  22 +
 lld/test/ELF/systemz-reloc-disp20.s         |  22 +
 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               | 299 ++++++++++
 lld/test/ELF/systemz-tls-gd.s               | 145 +++++
 lld/test/ELF/systemz-tls-ie.s               |  76 +++
 lld/test/ELF/systemz-tls-ld.s               | 116 ++++
 lld/test/ELF/systemz-tls-le.s               |  62 +++
 lld/test/lit.cfg.py                         |   1 +
 38 files changed, 2475 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..80a65cf32b6c32
--- /dev/null
+++ b/lld/ELF/Arch/SystemZ.cpp
@@ -0,0 +1,581 @@
+//===- 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 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::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:
+    error(getErrorLocation(loc) + "unknown relocation (" + Twine(rel.type) + ")");
+    llvm_unreachable("unknown relocation");
+  }
+}
+
+static TargetInfo *getTargetInfo() {
+  static SystemZ t;
+  return &t;
+}
+
+TargetInfo *elf::getSystemZTargetInfo() { return getTargetInfo(); }
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..405189198079c0
--- /dev/null
+++ b/lld/test/ELF/basic-systemz.s
@@ -0,0 +1,338 @@
+# 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-readobj --file-headers --sections --section-data -l %t.so | FileCheck %s
+
+# Exits with return code 55 on linux.
+.text
+  lghi 2,55
+  svc 1
+
+// CHECK:      Format: elf64-s390
+// CHECK-NEXT: Arch: s390x
+// CHECK-NEXT: AddressSize: 64bit
+// CHECK-NEXT: LoadName:
+// CHECK-NEXT: ElfHeader {
+// CHECK-NEXT:  Ident {
+// CHECK-NEXT:    Magic: (7F 45 4C 46)
+// CHECK-NEXT:    Class: 64-bit (0x2)
+// CHECK-NEXT:    DataEncoding: BigEndian (0x2)
+// CHECK-NEXT:    FileVersion: 1
+// CHECK-NEXT:    OS/ABI: SystemV (0x0)
+// CHECK-NEXT:    ABIVersion: 0
+// CHECK-NEXT:    Unused: (00 00 00 00 00 00 00)
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Type: SharedObject (0x3)
+// CHECK-NEXT:  Machine: EM_S390 (0x16)
+// CHECK-NEXT:  Version: 1
+// CHECK-NEXT:  Entry: 0x0
+// CHECK-NEXT:  ProgramHeaderOffset: 0x40
+// CHECK-NEXT:  SectionHeaderOffset: 0x300
+// CHECK-NEXT:  Flags [ (0x0)
+// CHECK-NEXT:  ]
+// CHECK-NEXT:  HeaderSize: 64
+// CHECK-NEXT:  ProgramHeaderEntrySize: 56
+// CHECK-NEXT:  ProgramHeaderCount: 7
+// CHECK-NEXT:  SectionHeaderEntrySize: 64
+// CHECK-NEXT:  SectionHeaderCount: 11
+// CHECK-NEXT:  StringTableSectionIndex: 9
+// CHECK-NEXT:}
+// CHECK-NEXT:Sections [
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 0
+// CHECK-NEXT:    Name:  (0)
+// CHECK-NEXT:    Type: SHT_NULL (0x0)
+// CHECK-NEXT:    Flags [ (0x0)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x0
+// CHECK-NEXT:    Offset: 0x0
+// CHECK-NEXT:    Size: 0
+// CHECK-NEXT:    Link: 0
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 0
+// CHECK-NEXT:    EntrySize: 0
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 1
+// CHECK-NEXT:    Name: .dynsym (1)
+// CHECK-NEXT:    Type: SHT_DYNSYM (0xB)
+// CHECK-NEXT:    Flags [ (0x2)
+// CHECK-NEXT:      SHF_ALLOC (0x2)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x1C8
+// CHECK-NEXT:    Offset: 0x1C8
+// CHECK-NEXT:    Size: 24
+// CHECK-NEXT:    Link: 3
+// CHECK-NEXT:    Info: 1
+// CHECK-NEXT:    AddressAlignment: 8
+// CHECK-NEXT:    EntrySize: 24
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 00000000 00000000 00000000 00000000
+// CHECK-NEXT:      0010: 00000000 00000000
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 2
+// CHECK-NEXT:    Name: .hash (9)
+// CHECK-NEXT:    Type: SHT_HASH (0x5)
+// CHECK-NEXT:    Flags [ (0x2)
+// CHECK-NEXT:      SHF_ALLOC (0x2)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x1E0
+// CHECK-NEXT:    Offset: 0x1E0
+// CHECK-NEXT:    Size: 16
+// CHECK-NEXT:    Link: 1
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 4
+// CHECK-NEXT:    EntrySize: 4
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 00000001 00000001 00000000 00000000
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 3
+// CHECK-NEXT:    Name: .dynstr (15)
+// CHECK-NEXT:    Type: SHT_STRTAB (0x3)
+// CHECK-NEXT:    Flags [ (0x2)
+// CHECK-NEXT:      SHF_ALLOC (0x2)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x1F0
+// CHECK-NEXT:    Offset: 0x1F0
+// CHECK-NEXT:    Size: 1
+// CHECK-NEXT:    Link: 0
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 1
+// CHECK-NEXT:    EntrySize: 0
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 00
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 4
+// CHECK-NEXT:    Name: .text (23)
+// CHECK-NEXT:    Type: SHT_PROGBITS (0x1)
+// CHECK-NEXT:    Flags [ (0x6)
+// CHECK-NEXT:      SHF_ALLOC (0x2)
+// CHECK-NEXT:      SHF_EXECINSTR (0x4)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x11F4
+// CHECK-NEXT:    Offset: 0x1F4
+// CHECK-NEXT:    Size: 6
+// CHECK-NEXT:    Link: 0
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 4
+// CHECK-NEXT:    EntrySize: 0
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: A7290037 0A01  |.).7..|
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 5
+// CHECK-NEXT:    Name: .dynamic (29)
+// CHECK-NEXT:    Type: SHT_DYNAMIC (0x6)
+// CHECK-NEXT:    Flags [ (0x3)
+// CHECK-NEXT:      SHF_ALLOC (0x2)
+// CHECK-NEXT:      SHF_WRITE (0x1)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x2200
+// CHECK-NEXT:    Offset: 0x200
+// CHECK-NEXT:    Size: 96
+// CHECK-NEXT:    Link: 3
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 8
+// CHECK-NEXT:    EntrySize: 16
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 00000000 00000006 00000000 000001C8
+// CHECK-NEXT:      0010: 00000000 0000000B 00000000 00000018
+// CHECK-NEXT:      0020: 00000000 00000005 00000000 000001F0
+// CHECK-NEXT:      0030: 00000000 0000000A 00000000 00000001
+// CHECK-NEXT:      0040: 00000000 00000004 00000000 000001E0
+// CHECK-NEXT:      0050: 00000000 00000000 00000000 00000000
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 6
+// CHECK-NEXT:    Name: .relro_padding (38)
+// CHECK-NEXT:    Type: SHT_NOBITS (0x8)
+// CHECK-NEXT:    Flags [ (0x3)
+// CHECK-NEXT:      SHF_ALLOC (0x2)
+// CHECK-NEXT:      SHF_WRITE (0x1)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x2260
+// CHECK-NEXT:    Offset: 0x260
+// CHECK-NEXT:    Size: 3488
+// CHECK-NEXT:    Link: 0
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 1
+// CHECK-NEXT:    EntrySize: 0
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 7
+// CHECK-NEXT:    Name: .comment (53)
+// CHECK-NEXT:    Type: SHT_PROGBITS (0x1)
+// CHECK-NEXT:    Flags [ (0x30)
+// CHECK-NEXT:      SHF_MERGE (0x10)
+// CHECK-NEXT:      SHF_STRINGS (0x20)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x0
+// CHECK-NEXT:    Offset: 0x260
+// CHECK-NEXT:    Size: 8
+// CHECK-NEXT:    Link: 0
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 1
+// CHECK-NEXT:    EntrySize: 1
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 4C4C4420 312E3000 |LLD 1.0.|
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 8
+// CHECK-NEXT:    Name: .symtab (62)
+// CHECK-NEXT:    Type: SHT_SYMTAB (0x2)
+// CHECK-NEXT:    Flags [ (0x0)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x0
+// CHECK-NEXT:    Offset: 0x268
+// CHECK-NEXT:    Size: 48
+// CHECK-NEXT:    Link: 10
+// CHECK-NEXT:    Info: 2
+// CHECK-NEXT:    AddressAlignment: 8
+// CHECK-NEXT:    EntrySize: 24
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 00000000 00000000 00000000 00000000
+// CHECK-NEXT:      0010: 00000000 00000000 00000001 00020005
+// CHECK-NEXT:      0020: 00000000 00002200 00000000 00000000
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 9
+// CHECK-NEXT:    Name: .shstrtab (70)
+// CHECK-NEXT:    Type: SHT_STRTAB (0x3)
+// CHECK-NEXT:    Flags [ (0x0)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x0
+// CHECK-NEXT:    Offset: 0x298
+// CHECK-NEXT:    Size: 88
+// CHECK-NEXT:    Link: 0
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 1
+// CHECK-NEXT:    EntrySize: 0
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 002E6479 6E73796D 002E6861 7368002E  |..dynsym..hash..|
+// CHECK-NEXT:      0010: 64796E73 7472002E 74657874 002E6479  |dynstr..text..dy|
+// CHECK-NEXT:      0020: 6E616D69 63002E72 656C726F 5F706164  |namic..relro_pad|
+// CHECK-NEXT:      0030: 64696E67 002E636F 6D6D656E 74002E73  |ding..comment..s|
+// CHECK-NEXT:      0040: 796D7461 62002E73 68737472 74616200  |ymtab..shstrtab.|
+// CHECK-NEXT:      0050: 2E737472 74616200                    |.strtab.|
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Section {
+// CHECK-NEXT:    Index: 10
+// CHECK-NEXT:    Name: .strtab (80)
+// CHECK-NEXT:    Type: SHT_STRTAB (0x3)
+// CHECK-NEXT:    Flags [ (0x0)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: 0x0
+// CHECK-NEXT:    Offset: 0x2F0
+// CHECK-NEXT:    Size: 10
+// CHECK-NEXT:    Link: 0
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 1
+// CHECK-NEXT:    EntrySize: 0
+// CHECK-NEXT:    SectionData (
+// CHECK-NEXT:      0000: 005F4459 4E414D49 4300               |._DYNAMIC.|
+// CHECK-NEXT:    )
+// CHECK-NEXT:  }
+// CHECK-NEXT:]
+// CHECK-NEXT:ProgramHeaders [
+// CHECK-NEXT:  ProgramHeader {
+// CHECK-NEXT:    Type: PT_PHDR (0x6)
+// CHECK-NEXT:    Offset: 0x40
+// CHECK-NEXT:    VirtualAddress: 0x40
+// CHECK-NEXT:    PhysicalAddress: 0x40
+// CHECK-NEXT:    FileSize: 392
+// CHECK-NEXT:    MemSize: 392
+// CHECK-NEXT:    Flags [ (0x4)
+// CHECK-NEXT:      PF_R (0x4)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Alignment: 8
+// CHECK-NEXT:  }
+// CHECK-NEXT:  ProgramHeader {
+// CHECK-NEXT:    Type: PT_LOAD (0x1)
+// CHECK-NEXT:    Offset: 0x0
+// CHECK-NEXT:    VirtualAddress: 0x0
+// CHECK-NEXT:    PhysicalAddress: 0x0
+// CHECK-NEXT:    FileSize: 497
+// CHECK-NEXT:    MemSize: 497
+// CHECK-NEXT:    Flags [ (0x4)
+// CHECK-NEXT:      PF_R (0x4)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Alignment: 4096
+// CHECK-NEXT:  }
+// CHECK-NEXT:  ProgramHeader {
+// CHECK-NEXT:    Type: PT_LOAD (0x1)
+// CHECK-NEXT:    Offset: 0x1F4
+// CHECK-NEXT:    VirtualAddress: 0x11F4
+// CHECK-NEXT:    PhysicalAddress: 0x11F4
+// CHECK-NEXT:    FileSize: 6
+// CHECK-NEXT:    MemSize: 6
+// CHECK-NEXT:    Flags [ (0x5)
+// CHECK-NEXT:      PF_R (0x4)
+// CHECK-NEXT:      PF_X (0x1)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Alignment: 4096
+// CHECK-NEXT:  }
+// CHECK-NEXT:  ProgramHeader {
+// CHECK-NEXT:    Type: PT_LOAD (0x1)
+// CHECK-NEXT:    Offset: 0x200
+// CHECK-NEXT:    VirtualAddress: 0x2200
+// CHECK-NEXT:    PhysicalAddress: 0x2200
+// CHECK-NEXT:    FileSize: 96
+// CHECK-NEXT:    MemSize: 3584
+// CHECK-NEXT:    Flags [ (0x6)
+// CHECK-NEXT:      PF_R (0x4)
+// CHECK-NEXT:      PF_W (0x2)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Alignment: 4096
+// CHECK-NEXT:  }
+// CHECK-NEXT:  ProgramHeader {
+// CHECK-NEXT:    Type: PT_DYNAMIC (0x2)
+// CHECK-NEXT:    Offset: 0x200
+// CHECK-NEXT:    VirtualAddress: 0x2200
+// CHECK-NEXT:    PhysicalAddress: 0x2200
+// CHECK-NEXT:    FileSize: 96
+// CHECK-NEXT:    MemSize: 96
+// CHECK-NEXT:    Flags [ (0x6)
+// CHECK-NEXT:      PF_R (0x4)
+// CHECK-NEXT:      PF_W (0x2)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Alignment: 8
+// CHECK-NEXT:  }
+// CHECK-NEXT:  ProgramHeader {
+// CHECK-NEXT:    Type: PT_GNU_RELRO (0x6474E552)
+// CHECK-NEXT:    Offset: 0x200
+// CHECK-NEXT:    VirtualAddress: 0x2200
+// CHECK-NEXT:    PhysicalAddress: 0x2200
+// CHECK-NEXT:    FileSize: 96
+// CHECK-NEXT:    MemSize: 3584
+// CHECK-NEXT:    Flags [ (0x4)
+// CHECK-NEXT:      PF_R (0x4)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Alignment: 1
+// CHECK-NEXT:  }
+// CHECK-NEXT:  ProgramHeader {
+// CHECK-NEXT:    Type: PT_GNU_STACK (0x6474E551)
+// CHECK-NEXT:    Offset: 0x0
+// CHECK-NEXT:    VirtualAddress: 0x0
+// CHECK-NEXT:    PhysicalAddress: 0x0
+// CHECK-NEXT:    FileSize: 0
+// CHECK-NEXT:    MemSize: 0
+// CHECK-NEXT:    Flags [ (0x6)
+// CHECK-NEXT:      PF_R (0x4)
+// CHECK-NEXT:      PF_W (0x2)
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Alignment: 0
+// CHECK-NEXT:  }
+// CHECK-NEXT:]
diff --git a/lld/test/ELF/emulation-systemz.s b/lld/test/ELF/emulation-systemz.s
new file mode 100644
index 00000000000000..ecce602784990f
--- /dev/null
+++ b/lld/test/ELF/emulation-systemz.s
@@ -0,0 +1,38 @@
+# 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-readobj --file-headers %t2 | FileCheck %s
+# RUN: ld.lld %t -o %t3
+# RUN: llvm-readobj --file-headers %t3 | FileCheck %s
+# RUN: echo 'OUTPUT_FORMAT(elf64-s390)' > %t.script
+# RUN: ld.lld %t.script  %t -o %t4
+# RUN: llvm-readobj --file-headers %t4 | FileCheck %s
+
+# CHECK:      ElfHeader {
+# CHECK-NEXT:   Ident {
+# CHECK-NEXT:     Magic: (7F 45 4C 46)
+# CHECK-NEXT:     Class: 64-bit (0x2)
+# CHECK-NEXT:     DataEncoding: BigEndian (0x2)
+# CHECK-NEXT:     FileVersion: 1
+# CHECK-NEXT:     OS/ABI: SystemV (0x0)
+# CHECK-NEXT:     ABIVersion: 0
+# CHECK-NEXT:     Unused: (00 00 00 00 00 00 00)
+# CHECK-NEXT:   }
+# CHECK-NEXT:   Type: Executable (0x2)
+# CHECK-NEXT:   Machine: EM_S390 (0x16)
+# CHECK-NEXT:   Version: 1
+# CHECK-NEXT:   Entry:
+# CHECK-NEXT:   ProgramHeaderOffset: 0x40
+# CHECK-NEXT:   SectionHeaderOffset:
+# CHECK-NEXT:   Flags [ (0x0)
+# CHECK-NEXT:   ]
+# CHECK-NEXT:   HeaderSize: 64
+# CHECK-NEXT:   ProgramHeaderEntrySize: 56
+# CHECK-NEXT:   ProgramHeaderCount:
+# CHECK-NEXT:   SectionHeaderEntrySize: 64
+# CHECK-NEXT:   SectionHeaderCount:
+# CHECK-NEXT:   StringTableSectionIndex:
+# CHECK-NEXT: }
+
+.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..9db116eedea417
--- /dev/null
+++ b/lld/test/ELF/lto/systemz.ll
@@ -0,0 +1,19 @@
+; 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..af47aa5fbf1c97
--- /dev/null
+++ b/lld/test/ELF/systemz-gnu-ifunc.s
@@ -0,0 +1,129 @@
+// 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-readobj -r --symbols --sections %tout | FileCheck %s
+
+// CHECK:      Sections [
+// CHECK:       Section {
+// CHECK:       Index: 1
+// CHECK-NEXT:  Name: .rela.dyn
+// CHECK-NEXT:  Type: SHT_RELA
+// CHECK-NEXT:  Flags [
+// CHECK-NEXT:    SHF_ALLOC
+// CHECK-NEXT:    SHF_INFO_LINK
+// CHECK-NEXT:  ]
+// CHECK-NEXT:  Address: 0x1000158
+// CHECK-NEXT:  Offset: 0x158
+// CHECK-NEXT:  Size: 48
+// CHECK-NEXT:  Link: 0
+// CHECK-NEXT:  Info: 4
+// CHECK-NEXT:  AddressAlignment: 8
+// CHECK-NEXT:  EntrySize: 24
+// CHECK-NEXT: }
+// CHECK:      Relocations [
+// CHECK-NEXT:   Section (1) .rela.dyn {
+// CHECK-NEXT:     0x10021F0 R_390_IRELATIVE - 0x1001188
+// CHECK-NEXT:     0x10021F8 R_390_IRELATIVE - 0x100118A
+// CHECK-NEXT:   }
+// CHECK-NEXT: ]
+// CHECK:      Symbols [
+// CHECK-NEXT:  Symbol {
+// CHECK-NEXT:    Name:
+// CHECK-NEXT:    Value: 0x0
+// CHECK-NEXT:    Size: 0
+// CHECK-NEXT:    Binding: Local
+// CHECK-NEXT:    Type: None
+// CHECK-NEXT:    Other: 0
+// CHECK-NEXT:    Section: Undefined
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Symbol {
+// CHECK-NEXT:    Name: __rela_iplt_start
+// CHECK-NEXT:    Value: 0x1000158
+// CHECK-NEXT:    Size: 0
+// CHECK-NEXT:    Binding: Local
+// CHECK-NEXT:    Type: None
+// CHECK-NEXT:    Other [
+// CHECK-NEXT:      STV_HIDDEN
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Section: .rela.dyn
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Symbol {
+// CHECK-NEXT:    Name: __rela_iplt_end
+// CHECK-NEXT:    Value: 0x1000188
+// CHECK-NEXT:    Size: 0
+// CHECK-NEXT:    Binding: Local
+// CHECK-NEXT:    Type: None
+// CHECK-NEXT:    Other [
+// CHECK-NEXT:      STV_HIDDEN
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Section: .rela.dyn
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Symbol {
+// CHECK-NEXT:    Name: foo
+// CHECK-NEXT:    Value: 0x1001188
+// CHECK-NEXT:    Size: 0
+// CHECK-NEXT:    Binding: Global
+// CHECK-NEXT:    Type: GNU_IFunc
+// CHECK-NEXT:    Other: 0
+// CHECK-NEXT:    Section: .text
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Symbol {
+// CHECK-NEXT:    Name: bar
+// CHECK-NEXT:    Value: 0x100118A
+// CHECK-NEXT:    Size: 0
+// CHECK-NEXT:    Binding: Global
+// CHECK-NEXT:    Type: GNU_IFunc
+// CHECK-NEXT:    Other: 0
+// CHECK-NEXT:    Section: .text
+// CHECK-NEXT:  }
+// CHECK-NEXT:  Symbol {
+// CHECK-NEXT:    Name: _start
+// CHECK-NEXT:    Value: 0x100118C
+// CHECK-NEXT:    Size: 0
+// CHECK-NEXT:    Binding: Global
+// CHECK-NEXT:    Type: None
+// CHECK-NEXT:    Other: 0
+// CHECK-NEXT:    Section: .text
+// CHECK-NEXT:  }
+// CHECK-NEXT: ]
+
+// 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..823b7f9fc0c607
--- /dev/null
+++ b/lld/test/ELF/systemz-got.s
@@ -0,0 +1,33 @@
+// 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-readobj -S -r  %t | FileCheck %s
+
+// CHECK:        Name: .got
+// CHECK-NEXT:   Type: SHT_PROGBITS
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:     SHF_WRITE
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address:
+// CHECK-NEXT:   Offset:
+// CHECK-NEXT:   Size: 32
+// CHECK-NEXT:   Link: 0
+// CHECK-NEXT:   Info: 0
+// CHECK-NEXT:   AddressAlignment: 8
+// CHECK-NEXT:   EntrySize: 0
+// CHECK-NEXT: }
+
+// CHECK:      Relocations [
+// CHECK-NEXT:   Section (6) .rela.dyn {
+// CHECK-NEXT:     R_390_GLOB_DAT bar 0x0
+// CHECK-NEXT:   }
+// CHECK-NEXT: ]
+
+
+.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..99230c9c18b2f7
--- /dev/null
+++ b/lld/test/ELF/systemz-gotent-relax-align.s
@@ -0,0 +1,49 @@
+# 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..58d077d39ea570
--- /dev/null
+++ b/lld/test/ELF/systemz-gotent-relax-und-dso.s
@@ -0,0 +1,73 @@
+# 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-readobj -r -S %t | FileCheck --check-prefix=RELOC %s
+# RUN: llvm-objdump --no-print-imm-hex -d %t | FileCheck --check-prefix=DISASM %s
+
+# RELOC:      Relocations [
+# RELOC-NEXT:   Section ({{.*}}) .rela.dyn {
+# RELOC-NEXT:     R_390_GLOB_DAT foo 0x0
+# RELOC-NEXT:     R_390_GLOB_DAT und 0x0
+# RELOC-NEXT:     R_390_GLOB_DAT dsofoo 0x0
+# RELOC-NEXT:   }
+# RELOC-NEXT: ]
+
+# 0x101e + 7 - 36 = 0x1001
+# 0x1025 + 7 - 43 = 0x1001
+# 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..3222102c442ffe
--- /dev/null
+++ b/lld/test/ELF/systemz-gotent-relax.s
@@ -0,0 +1,93 @@
+# 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.
+# RELOC-LABEL: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entry:
+# CHECK:           Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
+# 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..7ec57a46ebb24b
--- /dev/null
+++ b/lld/test/ELF/systemz-pie.s
@@ -0,0 +1,59 @@
+# REQUIRES: systemz
+# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t1.o
+
+## Default is no PIE.
+# RUN: ld.lld %t1.o -o %t
+# RUN: llvm-readobj --file-headers --sections -l --symbols -r %t \
+# RUN:   | FileCheck %s --check-prefix=NOPIE
+
+## Check -pie.
+# RUN: ld.lld -pie %t1.o -o %t
+# RUN: llvm-readobj --file-headers --sections -l -d --symbols -r %t | FileCheck %s
+
+## Test --pic-executable alias
+# RUN: ld.lld --pic-executable %t1.o -o %t
+# RUN: llvm-readobj --file-headers --sections -l -d --symbols -r %t | FileCheck %s
+
+# CHECK:      ElfHeader {
+# CHECK-NEXT:  Ident {
+# CHECK-NEXT:    Magic: (7F 45 4C 46)
+# CHECK-NEXT:    Class: 64-bit
+# CHECK-NEXT:    DataEncoding: BigEndian
+# CHECK-NEXT:    FileVersion: 1
+# CHECK-NEXT:    OS/ABI: SystemV
+# CHECK-NEXT:    ABIVersion: 0
+# CHECK-NEXT:    Unused: (00 00 00 00 00 00 00)
+# CHECK-NEXT:  }
+# CHECK-NEXT:  Type: SharedObject
+
+# CHECK:      ProgramHeaders [
+# CHECK-NEXT:  ProgramHeader {
+# CHECK-NEXT:    Type: PT_PHDR
+# CHECK-NEXT:    Offset: 0x40
+# CHECK-NEXT:    VirtualAddress: 0x40
+# CHECK-NEXT:    PhysicalAddress: 0x40
+# CHECK-NEXT:    FileSize:
+# CHECK-NEXT:    MemSize:
+# CHECK-NEXT:    Flags [
+# CHECK-NEXT:      PF_R
+# CHECK-NEXT:    ]
+# CHECK-NEXT:    Alignment: 8
+# CHECK-NEXT:  }
+# CHECK-NEXT:  ProgramHeader {
+# CHECK-NEXT:    Type: PT_LOAD
+# CHECK-NEXT:    Offset: 0x0
+# CHECK-NEXT:    VirtualAddress: 0x0
+# CHECK-NEXT:    PhysicalAddress: 0x0
+
+# CHECK:         Type: PT_DYNAMIC
+
+# CHECK:      DynamicSection [
+# CHECK:        0x000000006FFFFFFB FLAGS_1 PIE
+
+## Check -nopie
+# RUN: ld.lld -no-pie %t1.o -o %t2
+# RUN: llvm-readobj --file-headers -r %t2 | FileCheck %s --check-prefix=NOPIE
+# NOPIE-NOT: Type: SharedObject
+
+.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..7c283b25fd051d
--- /dev/null
+++ b/lld/test/ELF/systemz-plt.s
@@ -0,0 +1,73 @@
+// 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 --export-dynamic %t.o %t2.so -o %t
+// RUN: llvm-readobj -S --dynamic-table %t | FileCheck %s
+
+// CHECK:        Name: .rela.plt
+// CHECK-NEXT:   Type: SHT_RELA
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:     SHF_INFO_LINK
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address:
+// CHECK-NEXT:   Offset:
+// CHECK-NEXT:   Size: [[PLTRELASIZE:.*]]
+// CHECK-NEXT:   Link:
+// CHECK-NEXT:   Info:
+// CHECK-NEXT:   AddressAlignment: 8
+// CHECK-NEXT:   EntrySize:
+// CHECK-NEXT: }
+
+// CHECK:        Name: .plt
+// CHECK-NEXT:   Type: SHT_PROGBITS
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:     SHF_EXECINSTR
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address: [[PLTADDR:.*]]
+// CHECK-NEXT:   Offset:
+// CHECK-NEXT:   Size:
+// CHECK-NEXT:   Link:
+// CHECK-NEXT:   Info:
+// CHECK-NEXT:   AddressAlignment: 16
+// CHECK-NEXT:   EntrySize: 0
+// CHECK-NEXT: }
+
+// CHECK:        Name: .got.plt
+// CHECK-NEXT:   Type: SHT_PROGBITS
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:     SHF_WRITE
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address:
+// CHECK-NEXT:   Offset:
+// CHECK-NEXT:   Size:
+// CHECK-NEXT:   Link:
+// CHECK-NEXT:   Info:
+// CHECK-NEXT:   AddressAlignment: 8
+// CHECK-NEXT:   EntrySize:
+// CHECK-NEXT: }
+
+// CHECK:      DynamicSection [
+// CHECK-NEXT:   Tag        Type                 Name/Value
+// CHECK-NEXT:   0x0000000000000001 NEEDED               Shared library: [{{.*}}2.so]
+// CHECK-NEXT:   0x0000000000000015 DEBUG                0x0
+// CHECK-NEXT:   0x0000000000000017 JMPREL
+// CHECK-NEXT:   0x0000000000000002 PLTRELSZ             [[PLTRELASIZE]]
+// CHECK-NEXT:   0x0000000000000003 PLTGOT               0x0
+// CHECK-NEXT:   0x0000000000000014 PLTREL               RELA
+// CHECK-NEXT:   0x0000000000000006 SYMTAB
+// CHECK-NEXT:   0x000000000000000B SYMENT
+// CHECK-NEXT:   0x0000000000000005 STRTAB
+// CHECK-NEXT:   0x000000000000000A STRSZ
+// CHECK-NEXT:   0x000000006FFFFEF5 GNU_HASH
+// CHECK-NEXT:   0x0000000000000004 HASH
+// CHECK-NEXT:   0x0000000000000000 NULL                 0x0
+// CHECK-NEXT: ]
+
+.global _start
+_start:
+	brasl  %r14,bar at PLT
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..81773e27b3aa4b
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-disp12.s
@@ -0,0 +1,22 @@
+# 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..431a9399cbdf5c
--- /dev/null
+++ b/lld/test/ELF/systemz-reloc-disp20.s
@@ -0,0 +1,22 @@
+# 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..f616dae8994cf9
--- /dev/null
+++ b/lld/test/ELF/systemz-shared.s
@@ -0,0 +1,299 @@
+// 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-readobj -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-readobj -S -l --dynamic-table --symbols --dyn-syms --section-data %t | FileCheck %s
+
+// RUN: ld.lld %t.o %t2.so %t2.so -o %t2
+// RUN: llvm-readobj --dyn-syms %t2 | FileCheck --check-prefix=DONT_EXPORT %s
+
+// Make sure .symtab is properly aligned.
+// SO:      Name: .symtab
+// SO-NEXT: Type: SHT_SYMTAB
+// SO-NEXT: Flags [
+// SO-NEXT: ]
+// SO-NEXT: Address:
+// SO-NEXT: Offset:
+// SO-NEXT: Size:
+// SO-NEXT: Link:
+// SO-NEXT: Info:
+// SO-NEXT: AddressAlignment: 8
+
+// CHECK:        Name: .interp
+// CHECK-NEXT:   Type: SHT_PROGBITS
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address: [[INTERPADDR:.*]]
+// CHECK-NEXT:   Offset: [[INTERPOFFSET:.*]]
+// CHECK-NEXT:   Size: [[INTERPSIZE:.*]]
+// CHECK-NEXT:   Link: 0
+// CHECK-NEXT:   Info: 0
+// CHECK-NEXT:   AddressAlignment: 1
+// CHECK-NEXT:   EntrySize: 0
+// CHECK-NEXT:   SectionData (
+// CHECK-NEXT:     0000: 2F6C6962 2F6C6436 342E736F 2E3100    |/lib/ld64.so.1.|
+// CHECK-NEXT:   )
+// CHECK-NEXT: }
+
+// test that .hash is linked to .dynsym
+// CHECK:        Index: 2
+// CHECK-NEXT:   Name: .dynsym
+// CHECK-NEXT:   Type: SHT_DYNSYM
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address: [[DYNSYMADDR:.*]]
+// CHECK-NEXT:   Offset: 0x210
+// CHECK-NEXT:   Size:
+// CHECK-NEXT:   Link: [[DYNSTR:.*]]
+// CHECK-NEXT:   Info: 1
+// CHECK-NEXT:   AddressAlignment: 8
+// CHECK-NEXT:   EntrySize: 24
+// CHECK-NEXT:   SectionData (
+// CHECK-NEXT:     0000:
+// CHECK-NEXT:     0010:
+// CHECK-NEXT:     0020:
+// CHECK-NEXT:     0030:
+// CHECK-NEXT:     0040:
+// CHECK-NEXT:     0050:
+// CHECK-NEXT:   )
+// CHECK-NEXT: }
+
+// CHECK:         Index: 4
+// CHECK-NEXT:    Name: .hash
+// CHECK-NEXT:    Type: SHT_HASH
+// CHECK-NEXT:    Flags [
+// CHECK-NEXT:      SHF_ALLOC
+// CHECK-NEXT:    ]
+// CHECK-NEXT:    Address: [[HASHADDR:.*]]
+// CHECK-NEXT:    Offset:
+// CHECK-NEXT:    Size:
+// CHECK-NEXT:    Link: 2
+// CHECK-NEXT:    Info: 0
+// CHECK-NEXT:    AddressAlignment: 4
+// CHECK-NEXT:    EntrySize: 4
+// CHECK:      Section {
+// CHECK-NEXT:   Index: [[DYNSTR]]
+// CHECK-NEXT:   Name: .dynstr
+// CHECK-NEXT:   Type: SHT_STRTAB
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address: [[DYNSTRADDR:.*]]
+// CHECK-NEXT:   Offset:
+// CHECK-NEXT:   Size:
+// CHECK-NEXT:   Link: 0
+// CHECK-NEXT:   Info: 0
+// CHECK-NEXT:   AddressAlignment: 1
+// CHECK-NEXT:   EntrySize: 0
+
+// CHECK:      Name: .rela.dyn
+// CHECK-NEXT: Type: SHT_RELA
+// CHECK-NEXT: Flags [
+// CHECK-NEXT:   SHF_ALLOC
+// CHECK-NEXT: ]
+// CHECK-NEXT: Address: [[RELAADDR:.*]]
+// CHECK-NEXT: Offset:
+// CHECK-NEXT: Size: [[RELASIZE:.*]]
+// CHECK-NEXT: Link:
+// CHECK-NEXT: Info:
+// CHECK-NEXT: AddressAlignment:
+// CHECK-NEXT: EntrySize: [[RELAENT:.*]]
+
+// CHECK:        Name: .dynamic
+// CHECK-NEXT:   Type: SHT_DYNAMIC
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     SHF_ALLOC
+// CHECK-NEXT:     SHF_WRITE
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Address: [[ADDR:.*]]
+// CHECK-NEXT:   Offset: [[OFFSET:.*]]
+// CHECK-NEXT:   Size: [[SIZE:.*]]
+// CHECK-NEXT:   Link: [[DYNSTR]]
+// CHECK-NEXT:   Info: 0
+// CHECK-NEXT:   AddressAlignment: [[ALIGN:.*]]
+// CHECK-NEXT:   EntrySize: 16
+// CHECK-NEXT:   SectionData (
+// CHECK:        )
+
+// CHECK:      Name: .symtab
+// CHECK-NEXT: Type: SHT_SYMTAB
+// CHECK-NEXT: Flags [
+// CHECK-NEXT: ]
+// CHECK-NEXT: Address:
+// CHECK-NEXT: Offset:
+// CHECK-NEXT: Size:
+// CHECK-NEXT: Link:
+// CHECK-NEXT: Info:
+// CHECK-NEXT: AddressAlignment:
+// CHECK-NEXT: EntrySize: [[SYMENT:.*]]
+
+// CHECK:     ProgramHeaders [
+// CHECK:        Type: PT_INTERP
+// CHECK-NEXT:   Offset: [[INTERPOFFSET]]
+// CHECK-NEXT:   VirtualAddress: [[INTERPADDR]]
+// CHECK-NEXT:   PhysicalAddress: [[INTERPADDR]]
+// CHECK-NEXT:   FileSize: [[INTERPSIZE]]
+// CHECK-NEXT:   MemSize: [[INTERPSIZE]]
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     PF_R
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Alignment: 1
+// CHECK-NEXT: }
+// CHECK:        Type: PT_DYNAMIC
+// CHECK-NEXT:   Offset: [[OFFSET]]
+// CHECK-NEXT:   VirtualAddress: [[ADDR]]
+// CHECK-NEXT:   PhysicalAddress: [[ADDR]]
+// CHECK-NEXT:   FileSize: [[SIZE]]
+// CHECK-NEXT:   MemSize: [[SIZE]]
+// CHECK-NEXT:   Flags [
+// CHECK-NEXT:     PF_R
+// CHECK-NEXT:     PF_W
+// CHECK-NEXT:   ]
+// CHECK-NEXT:   Alignment: [[ALIGN]]
+// CHECK-NEXT: }
+
+// CHECK:      DynamicSection [
+// 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                 [[RELAADDR]]
+// CHECK-NEXT:   0x0000000000000008 RELASZ               [[RELASIZE]] (bytes)
+// CHECK-NEXT:   0x0000000000000009 RELAENT              [[RELAENT]] (bytes)
+// CHECK-NEXT:   0x0000000000000006 SYMTAB               [[DYNSYMADDR]]
+// CHECK-NEXT:   0x000000000000000B SYMENT               [[SYMENT]] (bytes)
+// CHECK-NEXT:   0x0000000000000005 STRTAB               [[DYNSTRADDR]]
+// CHECK-NEXT:   0x000000000000000A STRSZ
+// CHECK-NEXT:   0x000000006FFFFEF5 GNU_HASH
+// CHECK-NEXT:   0x0000000000000004 HASH                 [[HASHADDR]]
+// CHECK-NEXT:   0x0000000000000000 NULL                 0x0
+// CHECK-NEXT: ]
+
+// CHECK:      Symbols [
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name:
+// CHECK-NEXT:     Value: 0x0
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Local
+// CHECK-NEXT:     Type: None
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: Undefined
+// CHECK-NEXT:   }
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name: _DYNAMIC
+// CHECK-NEXT:     Value:
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Local
+// CHECK-NEXT:     Type: None
+// CHECK-NEXT:     Other [ (0x2)
+// CHECK-NEXT:       STV_HIDDEN
+// CHECK-NEXT:     ]
+// CHECK-NEXT:     Section: .dynamic
+// CHECK-NEXT:   }
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name: _start
+// CHECK-NEXT:     Value:
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Global
+// CHECK-NEXT:     Type: None
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: .text
+// CHECK-NEXT:   }
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name: bar
+// CHECK-NEXT:     Value: 0x0
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Global
+// CHECK-NEXT:     Type: Function
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: Undefined
+// CHECK-NEXT:   }
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name: zed
+// CHECK-NEXT:     Value: 0x0
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Global (0x1)
+// CHECK-NEXT:     Type: None (0x0)
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: Undefined (0x0)
+// CHECK-NEXT:   }
+// CHECK-NEXT: ]
+
+// CHECK:      DynamicSymbols [
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name:
+// CHECK-NEXT:     Value: 0x0
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Local
+// CHECK-NEXT:     Type: None
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: Undefined
+// CHECK-NEXT:   }
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name: bar
+// CHECK-NEXT:     Value: 0x0
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Global
+// CHECK-NEXT:     Type: Function
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: Undefined
+// CHECK-NEXT:   }
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name: zed
+// CHECK-NEXT:     Value: 0x0
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Global
+// CHECK-NEXT:     Type: None
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: Undefined
+// CHECK-NEXT:   }
+// CHECK-NEXT:   Symbol {
+// CHECK-NEXT:     Name: _start
+// CHECK-NEXT:     Value:
+// CHECK-NEXT:     Size: 0
+// CHECK-NEXT:     Binding: Global
+// CHECK-NEXT:     Type: Non
+// CHECK-NEXT:     Other: 0
+// CHECK-NEXT:     Section: .text
+// CHECK-NEXT:   }
+// CHECK-NEXT: ]
+
+// DONT_EXPORT:      DynamicSymbols [
+// DONT_EXPORT-NEXT:   Symbol {
+// DONT_EXPORT-NEXT:     Name:
+// DONT_EXPORT-NEXT:     Value: 0x0
+// DONT_EXPORT-NEXT:     Size: 0
+// DONT_EXPORT-NEXT:     Binding: Local (0x0)
+// DONT_EXPORT-NEXT:     Type: None (0x0)
+// DONT_EXPORT-NEXT:     Other: 0
+// DONT_EXPORT-NEXT:     Section: Undefined (0x0)
+// DONT_EXPORT-NEXT:   }
+// DONT_EXPORT-NEXT:   Symbol {
+// DONT_EXPORT-NEXT:     Name: bar
+// DONT_EXPORT-NEXT:     Value: 0x0
+// DONT_EXPORT-NEXT:     Size: 0
+// DONT_EXPORT-NEXT:     Binding: Global
+// DONT_EXPORT-NEXT:     Type: Function
+// DONT_EXPORT-NEXT:     Other: 0
+// DONT_EXPORT-NEXT:     Section: Undefined
+// DONT_EXPORT-NEXT:   }
+// DONT_EXPORT-NEXT:   Symbol {
+// DONT_EXPORT-NEXT:     Name: zed
+// DONT_EXPORT-NEXT:     Value: 0x0
+// DONT_EXPORT-NEXT:     Size: 0
+// DONT_EXPORT-NEXT:     Binding: Global
+// DONT_EXPORT-NEXT:     Type: None
+// DONT_EXPORT-NEXT:     Other: 0
+// DONT_EXPORT-NEXT:     Section: Undefined
+// DONT_EXPORT-NEXT:   }
+// DONT_EXPORT-NEXT: ]
+
+.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..9288180db637ef
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-gd.s
@@ -0,0 +1,145 @@
+# 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-readobj -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-readobj -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:      .rela.dyn {
+# GD-REL-NEXT:    0x2570 R_390_TLS_DTPMOD a 0x0
+# GD-REL-NEXT:    0x2578 R_390_TLS_DTPOFF a 0x0
+# GD-REL-NEXT:    0x2580 R_390_TLS_DTPMOD b 0x0
+# GD-REL-NEXT:    0x2588 R_390_TLS_DTPOFF b 0x0
+# GD-REL-NEXT:    0x2590 R_390_TLS_DTPMOD c 0x0
+# GD-REL-NEXT:    0x2598 R_390_TLS_DTPOFF c 0x0
+# GD-REL-NEXT: }
+
+## _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:      .rela.dyn {
+# IE-REL-NEXT:    0x1002430 R_390_TLS_TPOFF b 0x0
+# IE-REL-NEXT:    0x1002438 R_390_TLS_TPOFF c 0x0
+# IE-REL-NEXT: }
+
+## _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..310d8c2e7ba82e
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-ie.s
@@ -0,0 +1,76 @@
+# 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-readobj -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:      .rela.dyn {
+# IE-REL-NEXT:    0x2400 R_390_TLS_TPOFF a 0x0
+# IE-REL-NEXT:    0x2408 R_390_TLS_TPOFF b 0x0
+# IE-REL-NEXT:    0x2410 R_390_TLS_TPOFF c 0x0
+# IE-REL-NEXT: }
+
+## 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..adeead73bfb009
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-ld.s
@@ -0,0 +1,116 @@
+# 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-readobj -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:      .rela.dyn {
+# LD-REL-NEXT:    0x24F8 R_390_TLS_DTPMOD - 0x0
+# LD-REL-NEXT: }
+
+## _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..a5e8a5d0231cf0
--- /dev/null
+++ b/lld/test/ELF/systemz-tls-le.s
@@ -0,0 +1,62 @@
+# 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