Skip to content

Commit

Permalink
[lld] Add target support for SystemZ (s390x)
Browse files Browse the repository at this point in the history
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_GOTREL, 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 <[email protected]>
  • Loading branch information
uweigand and tuliom committed Feb 12, 2024
1 parent fb48fd1 commit 9f0f1a8
Show file tree
Hide file tree
Showing 36 changed files with 1,959 additions and 10 deletions.
607 changes: 607 additions & 0 deletions lld/ELF/Arch/SystemZ.cpp

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lld/ELF/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lld/ELF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,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)
Expand Down Expand Up @@ -1137,7 +1138,7 @@ static SmallVector<StringRef, 0> getSymbolOrderingFile(MemoryBufferRef mb) {
static bool getIsRela(opt::InputArgList &args) {
// The psABI specifies the default relocation entry format.
bool rela = is_contained({EM_AARCH64, EM_AMDGPU, EM_HEXAGON, EM_LOONGARCH,
EM_PPC, EM_PPC64, EM_RISCV, EM_X86_64},
EM_PPC, EM_PPC64, EM_RISCV, EM_S390, EM_X86_64},
config->emachine);
// If -z rel or -z rela is specified, use the last option.
for (auto *arg : args.filtered(OPT_z)) {
Expand Down
2 changes: 2 additions & 0 deletions lld/ELF/InputFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,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:
Expand Down
7 changes: 7 additions & 0 deletions lld/ELF/InputSection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,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:
Expand Down Expand Up @@ -717,6 +718,10 @@ 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_GOTREL:
return sym.getGotPltVA() + a - in.got->getVA();
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, type);
Expand Down Expand Up @@ -808,6 +813,8 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
return getLoongArchPageDelta(sym.getPltVA() + a, p, type);
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
Expand Down
25 changes: 16 additions & 9 deletions lld/ELF/Relocations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,9 @@ static bool isAbsoluteValue(const Symbol &sym) {

// Returns true if Expr refers a PLT entry.
static bool needsPlt(RelExpr expr) {
return oneof<R_PLT, R_PLT_PC, R_PLT_GOTPLT, R_LOONGARCH_PLT_PAGE_PC,
R_PPC32_PLTREL, R_PPC64_CALL_PLT>(expr);
return oneof<R_PLT, R_PLT_PC, R_PLT_GOTREL, R_PLT_GOTPLT, R_GOTPLT_GOTREL,
R_GOTPLT_PC, R_LOONGARCH_PLT_PAGE_PC, R_PPC32_PLTREL,
R_PPC64_CALL_PLT>(expr);
}

bool lld::elf::needsGot(RelExpr expr) {
Expand Down Expand Up @@ -233,6 +234,8 @@ static RelExpr toPlt(RelExpr expr) {
return R_PLT_PC;
case R_ABS:
return R_PLT;
case R_GOTREL:
return R_PLT_GOTREL;
default:
return expr;
}
Expand All @@ -253,6 +256,8 @@ static RelExpr fromPlt(RelExpr expr) {
return R_ABS;
case R_PLT_GOTPLT:
return R_GOTPLTREL;
case R_PLT_GOTREL:
return R_GOTREL;
default:
return expr;
}
Expand Down Expand Up @@ -979,10 +984,10 @@ bool RelocationScanner::isStaticLinkTimeConstant(RelExpr e, RelType type,
if (oneof<R_GOTPLT, R_GOT_OFF, R_RELAX_HINT, R_MIPS_GOT_LOCAL_PAGE,
R_MIPS_GOTREL, R_MIPS_GOT_OFF, R_MIPS_GOT_OFF32, R_MIPS_GOT_GP_PC,
R_AARCH64_GOT_PAGE_PC, R_GOT_PC, R_GOTONLY_PC, R_GOTPLTONLY_PC,
R_PLT_PC, R_PLT_GOTPLT, R_PPC32_PLTREL, R_PPC64_CALL_PLT,
R_PPC64_RELAX_TOC, R_RISCV_ADD, R_AARCH64_GOT_PAGE,
R_LOONGARCH_PLT_PAGE_PC, R_LOONGARCH_GOT, R_LOONGARCH_GOT_PAGE_PC>(
e))
R_PLT_PC, R_PLT_GOTREL, R_PLT_GOTPLT, R_GOTPLT_GOTREL, R_GOTPLT_PC,
R_PPC32_PLTREL, R_PPC64_CALL_PLT, R_PPC64_RELAX_TOC, R_RISCV_ADD,
R_AARCH64_GOT_PAGE, R_LOONGARCH_PLT_PAGE_PC, R_LOONGARCH_GOT,
R_LOONGARCH_GOT_PAGE_PC>(e))
return true;

// These never do, except if the entire file is position dependent or if
Expand Down Expand Up @@ -1374,8 +1379,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 optimized to Local-Exec if the symbol is
// locally defined.
if (execOptimize && isLocalInExecutable) {
// locally defined. This is not supported on SystemZ.
if (execOptimize && 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);
Expand Down Expand Up @@ -1534,8 +1539,10 @@ void RelocationScanner::scan(ArrayRef<RelTy> rels) {
// For EhInputSection, OffsetGetter expects the relocations to be sorted by
// r_offset. In rare cases (.eh_frame pieces are reordered by a linker
// script), the relocations may be unordered.
// On SystemZ, all sections need to be sorted by r_offset, to allow TLS
// relaxation to be handled correctly - see SystemZ::getTlsGdRelaxSkip.
SmallVector<RelTy, 0> storage;
if (isa<EhInputSection>(sec))
if (isa<EhInputSection>(sec) || config->emachine == EM_S390)
rels = sortRels(rels, storage);

end = static_cast<const void *>(rels.end());
Expand Down
3 changes: 3 additions & 0 deletions lld/ELF/Relocations.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ enum RelExpr {
R_GOTPLT,
R_GOTPLTREL,
R_GOTREL,
R_GOTPLT_GOTREL,
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,
Expand Down
1 change: 1 addition & 0 deletions lld/ELF/ScriptParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,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});
}

Expand Down
3 changes: 3 additions & 0 deletions lld/ELF/SyntheticSections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions lld/ELF/Target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions lld/ELF/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ TargetInfo *getPPC64TargetInfo();
TargetInfo *getPPCTargetInfo();
TargetInfo *getRISCVTargetInfo();
TargetInfo *getSPARCV9TargetInfo();
TargetInfo *getSystemZTargetInfo();
TargetInfo *getX86TargetInfo();
TargetInfo *getX86_64TargetInfo();
template <class ELFT> TargetInfo *getMipsTargetInfo();
Expand Down
5 changes: 5 additions & 0 deletions lld/test/ELF/Inputs/systemz-init.s
Original file line number Diff line number Diff line change
@@ -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",@progbits
.align 4
lg %r4, 272(%r15)
63 changes: 63 additions & 0 deletions lld/test/ELF/basic-systemz.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# REQUIRES: systemz
# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
# RUN: ld.lld --hash-style=sysv -discard-all -shared %t.o -o %t.so
# RUN: llvm-readelf --file-header --program-headers --section-headers --dynamic-table %t.so | FileCheck %s

# Exits with return code 55 on linux.
.text
lghi 2,55
svc 1

# CHECK: ELF Header:
# CHECK-NEXT: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00
# CHECK-NEXT: Class: ELF64
# CHECK-NEXT: Data: 2's complement, big endian
# CHECK-NEXT: Version: 1 (current)
# CHECK-NEXT: OS/ABI: UNIX - System V
# CHECK-NEXT: ABI Version: 0
# CHECK-NEXT: Type: DYN (Shared object file)
# CHECK-NEXT: Machine: IBM S/390
# CHECK-NEXT: Version: 0x1
# CHECK-NEXT: Entry point address: 0x0
# CHECK-NEXT: Start of program headers: 64 (bytes into file)
# CHECK-NEXT: Start of section headers: 768 (bytes into file)
# CHECK-NEXT: Flags: 0x0
# CHECK-NEXT: Size of this header: 64 (bytes)
# CHECK-NEXT: Size of program headers: 56 (bytes)
# CHECK-NEXT: Number of program headers: 7
# CHECK-NEXT: Size of section headers: 64 (bytes)
# CHECK-NEXT: Number of section headers: 11
# CHECK-NEXT: Section header string table index: 9

# CHECK: Section Headers:
# CHECK-NEXT: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
# CHECK-NEXT: [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
# CHECK-NEXT: [ 1] .dynsym DYNSYM 00000000000001c8 0001c8 000018 18 A 3 1 8
# CHECK-NEXT: [ 2] .hash HASH 00000000000001e0 0001e0 000010 04 A 1 0 4
# CHECK-NEXT: [ 3] .dynstr STRTAB 00000000000001f0 0001f0 000001 00 A 0 0 1
# CHECK-NEXT: [ 4] .text PROGBITS 00000000000011f4 0001f4 000006 00 AX 0 0 4
# CHECK-NEXT: [ 5] .dynamic DYNAMIC 0000000000002200 000200 000060 10 WA 3 0 8
# CHECK-NEXT: [ 6] .relro_padding NOBITS 0000000000002260 000260 000da0 00 WA 0 0 1
# CHECK-NEXT: [ 7] .comment PROGBITS 0000000000000000 000260 000008 01 MS 0 0 1
# CHECK-NEXT: [ 8] .symtab SYMTAB 0000000000000000 000268 000030 18 10 2 8
# CHECK-NEXT: [ 9] .shstrtab STRTAB 0000000000000000 000298 000058 00 0 0 1
# CHECK-NEXT: [10] .strtab STRTAB 0000000000000000 0002f0 00000a 00 0 0 1

# CHECK: Program Headers:
# CHECK-NEXT: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
# CHECK-NEXT: PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000188 0x000188 R 0x8
# CHECK-NEXT: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0001f1 0x0001f1 R 0x1000
# CHECK-NEXT: LOAD 0x0001f4 0x00000000000011f4 0x00000000000011f4 0x000006 0x000006 R E 0x1000
# CHECK-NEXT: LOAD 0x000200 0x0000000000002200 0x0000000000002200 0x000060 0x000e00 RW 0x1000
# CHECK-NEXT: DYNAMIC 0x000200 0x0000000000002200 0x0000000000002200 0x000060 0x000060 RW 0x8
# CHECK-NEXT: GNU_RELRO 0x000200 0x0000000000002200 0x0000000000002200 0x000060 0x000e00 R 0x1
# CHECK-NEXT: GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0

# CHECK: Dynamic section at offset 0x200 contains 6 entries:
# CHECK-NEXT: Tag Type Name/Value
# CHECK-NEXT: 0x0000000000000006 (SYMTAB) 0x1c8
# CHECK-NEXT: 0x000000000000000b (SYMENT) 24 (bytes)
# CHECK-NEXT: 0x0000000000000005 (STRTAB) 0x1f0
# CHECK-NEXT: 0x000000000000000a (STRSZ) 1 (bytes)
# CHECK-NEXT: 0x0000000000000004 (HASH) 0x1e0
# CHECK-NEXT: 0x0000000000000000 (NULL) 0x0
29 changes: 29 additions & 0 deletions lld/test/ELF/emulation-systemz.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# REQUIRES: systemz
# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
# RUN: ld.lld -m elf64_s390 %t.o -o %t1
# RUN: llvm-readelf --file-header %t1 | FileCheck %s
# RUN: ld.lld %t.o -o %t2
# RUN: llvm-readelf --file-header %t2 | FileCheck %s
# RUN: echo 'OUTPUT_FORMAT(elf64-s390)' > %t.script
# RUN: ld.lld %t.script %t.o -o %t3
# RUN: llvm-readelf --file-header %t3 | FileCheck %s

# CHECK: ELF Header:
# CHECK-NEXT: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00
# CHECK-NEXT: Class: ELF64
# CHECK-NEXT: Data: 2's complement, big endian
# CHECK-NEXT: Version: 1 (current)
# CHECK-NEXT: OS/ABI: UNIX - System V
# CHECK-NEXT: ABI Version: 0
# CHECK-NEXT: Type: EXEC (Executable file)
# CHECK-NEXT: Machine: IBM S/390
# CHECK-NEXT: Version: 0x1
# CHECK-NEXT: Entry point address:
# CHECK-NEXT: Start of program headers: 64 (bytes into file)
# CHECK-NEXT: Start of section headers:
# CHECK-NEXT: Flags: 0x0
# CHECK-NEXT: Size of this header: 64 (bytes)
# CHECK-NEXT: Size of program headers: 56 (bytes)

.globl _start
_start:
18 changes: 18 additions & 0 deletions lld/test/ELF/lto/systemz.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
; REQUIRES: systemz
;; Test we can infer the e_machine value EM_S390 from a bitcode file.

; RUN: llvm-as %s -o %t.o
; RUN: ld.lld %t.o -o %t
; RUN: llvm-readobj -h %t | FileCheck %s

; CHECK: Class: 64-bit
; CHECK: DataEncoding: BigEndian
; CHECK: Machine: EM_S390

target datalayout = "E-m:e-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64"
target triple = "s390x-unknown-linux-gnu"

define void @_start() {
entry:
ret void
}
16 changes: 16 additions & 0 deletions lld/test/ELF/systemz-got.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# REQUIRES: systemz
# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %s -o %t.o
# RUN: llvm-mc -filetype=obj -triple=s390x-unknown-linux %p/Inputs/shared.s -o %t2.o
# RUN: ld.lld -shared %t2.o -soname=%t2.so -o %t2.so

# RUN: ld.lld -dynamic-linker /lib/ld64.so.1 %t.o %t2.so -o %t
# RUN: llvm-readelf -S -r %t | FileCheck %s

# CHECK: .got PROGBITS {{.*}} {{.*}} 000020 00 WA 0 0 8

# CHECK: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
# CHECK: {{.*}} 000000010000000a R_390_GLOB_DAT 0000000000000000 bar + 0

.global _start
_start:
lgrl %r1,bar@GOT
48 changes: 48 additions & 0 deletions lld/test/ELF/systemz-gotent-relax-align.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# REQUIRES: systemz
## Verify that R_390_GOTENT optimization is not performed on misaligned symbols.

# RUN: llvm-mc -filetype=obj -relax-relocations -triple=s390x-unknown-linux %s -o %t.o
# RUN: ld.lld %t.o -o %t1
# RUN: llvm-readelf -S -r -x .got -x .got.plt %t1 | FileCheck --check-prefixes=CHECK %s
# RUN: llvm-objdump --no-print-imm-hex -d %t1 | FileCheck --check-prefix=DISASM %s

## We retain one .got entry for the unaligned symbol.
# CHECK: Name Type Address Off Size ES Flg Lk Inf Al
# CHECK: .got PROGBITS 00000000010021e0 0001e0 000020 00 WA 0 0 8
# CHECK-NEXT: .relro_padding NOBITS 0000000001002200 000200 000e00 00 WA 0 0 1
# CHECK-NEXT: .data PROGBITS 0000000001003200 000200 000006 00 WA 0 0 2

# CHECK-LABEL: Hex dump of section '.got':
# CHECK-NEXT: 0x010021e0 00000000 00000000 00000000 00000000
# CHECK-NEXT: 0x010021f0 00000000 00000000 00000000 01003205

# DISASM: Disassembly of section .text:
# DISASM: <_start>:
# DISASM-NEXT: larl %r1, 0x1003200
# DISASM-NEXT: larl %r1, 0x1003200
# DISASM-NEXT: lgrl %r1, 0x10021f8
# DISASM-NEXT: lgrl %r1, 0x10021f8

.data
.globl var_align
.hidden var_align
.align 2
var_align:
.long 0

.data
.globl var_unalign
.hidden var_unalign
.align 2
.byte 0
var_unalign:
.byte 0

.text
.globl _start
.type _start, @function
_start:
lgrl %r1, var_align@GOT
lgrl %r1, var_align@GOT
lgrl %r1, var_unalign@GOT
lgrl %r1, var_unalign@GOT
Loading

0 comments on commit 9f0f1a8

Please sign in to comment.