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_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@redhat.com>
  • Loading branch information
uweigand and tuliom committed Jan 22, 2024
1 parent d7fb9eb commit e20e013
Show file tree
Hide file tree
Showing 38 changed files with 1,881 additions and 3 deletions.
587 changes: 587 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 @@ -1136,7 +1137,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 @@ -654,6 +654,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 @@ -708,6 +709,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);
Expand All @@ -716,6 +719,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, type);
Expand Down Expand Up @@ -807,6 +812,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
24 changes: 22 additions & 2 deletions lld/ELF/Relocations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1364,8 +1364,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);
Expand Down Expand Up @@ -1411,6 +1411,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 &&
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_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,
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 @@ -186,6 +186,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
# RUN: ld.lld -m elf64_s390 %t -o %t2
# RUN: llvm-readelf --file-header %t2 | FileCheck %s
# RUN: ld.lld %t -o %t3
# RUN: llvm-readelf --file-header %t3 | FileCheck %s
# RUN: echo 'OUTPUT_FORMAT(elf64-s390)' > %t.script
# RUN: ld.lld %t.script %t -o %t4
# RUN: llvm-readelf --file-header %t4 | FileCheck %s

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

.globl _start
_start:
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
}
72 changes: 72 additions & 0 deletions lld/test/ELF/systemz-gnu-ifunc.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// REQUIRES: systemz
// RUN: llvm-mc -filetype=obj -triple=s390x-none-linux-gnu %s -o %t.o
// RUN: ld.lld -static %t.o -o %tout
// RUN: llvm-objdump --no-print-imm-hex -d --no-show-raw-insn %tout | FileCheck %s --check-prefix=DISASM
// RUN: llvm-readelf --section-headers --relocations --symbols %tout | FileCheck %s

// CHECK: There are 9 section headers
// CHECK: Section Headers:
// CHECK-NEXT: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
// CHECK-NEXT: [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
// CHECK-NEXT: [ 1] .rela.dyn RELA 0000000001000158 000158 000030 18 AI 0 4 8
// CHECK-NEXT: [ 2] .text PROGBITS 0000000001001188 000188 00001c 00 AX 0 0 4
// CHECK-NEXT: [ 3] .iplt PROGBITS 00000000010011b0 0001b0 000040 00 AX 0 0 16
// CHECK-NEXT: [ 4] .got.plt PROGBITS 00000000010021f0 0001f0 000010 00 WA 0 0 8
// CHECK-NEXT: [ 5] .comment PROGBITS 0000000000000000 000200 000008 01 MS 0 0 1
// CHECK-NEXT: [ 6] .symtab SYMTAB 0000000000000000 000208 000090 18 8 3 8
// CHECK-NEXT: [ 7] .shstrtab STRTAB 0000000000000000 000298 000043 00 0 0 1
// CHECK-NEXT: [ 8] .strtab STRTAB 0000000000000000 0002db 000032 00 0 0 1

// CHECK: Relocation section '.rela.dyn' at offset 0x158 contains 2 entries:
// CHECK-NEXT: Offset Info Type Symbol's Value Symbol's Name + Addend
// CHECK-NEXT: 00000000010021f0 000000000000003d R_390_IRELATIVE 1001188
// CHECK-NEXT: 00000000010021f8 000000000000003d R_390_IRELATIVE 100118a

// CHECK: Symbol table '.symtab' contains 6 entries:
// CHECK-NEXT: Num: Value Size Type Bind Vis Ndx Name
// CHECK-NEXT: 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
// CHECK-NEXT: 1: 0000000001000158 0 NOTYPE LOCAL HIDDEN 1 __rela_iplt_start
// CHECK-NEXT: 2: 0000000001000188 0 NOTYPE LOCAL HIDDEN 1 __rela_iplt_end
// CHECK-NEXT: 3: 0000000001001188 0 IFUNC GLOBAL DEFAULT 2 foo
// CHECK-NEXT: 4: 000000000100118a 0 IFUNC GLOBAL DEFAULT 2 bar
// CHECK-NEXT: 5: 000000000100118c 0 NOTYPE GLOBAL DEFAULT 2 _start

// DISASM: Disassembly of section .text:
// DISASM-EMPTY:
// DISASM-NEXT: <foo>:
// DISASM-NEXT: 1001188: br %r14
// DISASM: <bar>:
// DISASM-NEXT: 100118a: br %r14
// DISASM: <_start>:
// DISASM-NEXT: 100118c: brasl %r14, 0x10011b0
// DISASM-NEXT: 1001192: brasl %r14, 0x10011d0
// DISASM-NEXT: 1001198: larl %r2, 0x1000158
// DISASM-NEXT: 100119e: larl %r2, 0x1000188
// DISASM-EMPTY:
// DISASM-NEXT: Disassembly of section .iplt:
// DISASM-EMPTY:
// DISASM-NEXT: <.iplt>:
// DISASM: 10011b0: larl %r1, 0x10021f0
// DISASM-NEXT: 10011b6: lg %r1, 0(%r1)
// DISASM-NEXT: 10011bc: br %r1
// DISASM: 10011d0: larl %r1, 0x10021f8
// DISASM-NEXT: 10011d6: lg %r1, 0(%r1)
// DISASM-NEXT: 10011dc: br %r1

.text
.type foo STT_GNU_IFUNC
.globl foo
foo:
br %r14

.type bar STT_GNU_IFUNC
.globl bar
bar:
br %r14

.globl _start
_start:
brasl %r14, foo@plt
brasl %r14, bar@plt
larl %r2, __rela_iplt_start
larl %r2, __rela_iplt_end
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
Loading

0 comments on commit e20e013

Please sign in to comment.