[llvm] [SystemZ][z/OS] Keep read-only data in the code section only if local and without relocations (PR #226683)
MMS IT GmbH via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 29 03:05:53 PDT 2026
https://github.com/mms-it-ch updated https://github.com/llvm/llvm-project/pull/226683
>From f2ab17ff8ada20dd19acb4949b02016c40e6e445 Mon Sep 17 00:00:00 2001
From: mms-it-ch <info at mms-it.ch>
Date: Sat, 26 Sep 2026 13:38:38 +0200
Subject: [PATCH 1/2] [SystemZ][z/OS] Keep read-only data in the code section
only if local and without relocations
Read-only data was placed into the code section and addressed PC-relative
unless it was classified as ReadOnlyWithRel. This is wrong in two cases:
- Externally visible read-only data: references from other translation units
are always parts in the WSA, so the binder cannot resolve them against a
label in the code section (IEW2456E, name space 3).
- Local read-only data with relocations to local symbols, e.g. a static const
table of pointers to internal functions: it is classified as ReadOnly, but
function pointers point to function descriptors in the WSA, and the binder
rejects an address constant from the code section into the WSA (IEW2353E).
Add TargetLoweringObjectFileGOFF::isReadOnlyInCodeSection() and use it both for
the section selection and in SystemZSubtarget::isAddressedViaADA(), so that the
addressing always matches the placement.
Fixes #226659
Assisted-by: Claude Code (Anthropic)
---
.../CodeGen/TargetLoweringObjectFileImpl.h | 7 ++++
.../CodeGen/TargetLoweringObjectFileImpl.cpp | 13 +++++-
llvm/lib/Target/SystemZ/SystemZSubtarget.cpp | 7 +---
.../SystemZ/zos-readonly-addressing.ll | 42 +++++++++++++++++++
.../SystemZ/zos-section-readonly-extern.ll | 25 +++++++++++
5 files changed, 88 insertions(+), 6 deletions(-)
create mode 100644 llvm/test/CodeGen/SystemZ/zos-readonly-addressing.ll
create mode 100644 llvm/test/CodeGen/SystemZ/zos-section-readonly-extern.ll
diff --git a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
index e631d820eb637..390a8441be1e0 100644
--- a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
+++ b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
@@ -331,6 +331,13 @@ class LLVM_ABI TargetLoweringObjectFileGOFF : public TargetLoweringObjectFile {
void getModuleMetadata(Module &M) override;
+ /// Returns true if the read-only global \p GO of kind \p Kind is placed into
+ /// the code section and can therefore be addressed PC-relative. This is only
+ /// the case for local data whose initializer needs no relocations: references
+ /// from other translation units are always parts in the WSA, and function
+ /// pointers point to function descriptors in the WSA.
+ static bool isReadOnlyInCodeSection(const GlobalObject *GO, SectionKind Kind);
+
bool shouldPutJumpTableInFunctionSection(bool UsesLabelDifference,
const Function &F) const override;
MCSection *getSectionForConstant(const DataLayout &DL, SectionKind Kind,
diff --git a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
index 8b81aaf9d12d4..11fd38d6f7759 100644
--- a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
+++ b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
@@ -2921,11 +2921,22 @@ MCSection *TargetLoweringObjectFileGOFF::getSectionForLSDA(
WSA);
}
+bool TargetLoweringObjectFileGOFF::isReadOnlyInCodeSection(
+ const GlobalObject *GO, SectionKind Kind) {
+ if (!Kind.isReadOnly() || !GO->hasLocalLinkage())
+ return false;
+ if (const auto *GVar = dyn_cast<GlobalVariable>(GO))
+ if (GVar->hasInitializer() && GVar->getInitializer()->needsRelocation())
+ return false;
+ return true;
+}
+
MCSection *TargetLoweringObjectFileGOFF::SelectSectionForGlobal(
const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const {
auto *Symbol = TM.getSymbol(GO);
- if (Kind.isBSS() || Kind.isData() || Kind.isReadOnlyWithRel()) {
+ if (Kind.isBSS() || Kind.isData() || Kind.isReadOnlyWithRel() ||
+ (Kind.isReadOnly() && !isReadOnlyInCodeSection(GO, Kind))) {
GOFF::ESDBindingScope PRBindingScope =
GO->hasExternalLinkage()
? (GO->hasDefaultVisibility() ? GOFF::ESD_BSC_ImportExport
diff --git a/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp b/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp
index 942ef88385620..d4855038492c7 100644
--- a/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp
@@ -99,11 +99,8 @@ bool SystemZSubtarget::isAddressedViaADA(const GlobalValue *GV) const {
SectionKind GOKind = TargetLoweringObjectFile::getKindForGlobal(
GO, TLInfo.getTargetMachine());
- if (!GOKind.isReadOnly()) {
- return true;
- }
-
- return false; // R/O variable with multiple of 2 byte alignment
+ // Must match the section selection in TargetLoweringObjectFileGOFF.
+ return !TargetLoweringObjectFileGOFF::isReadOnlyInCodeSection(GO, GOKind);
}
return true;
}
diff --git a/llvm/test/CodeGen/SystemZ/zos-readonly-addressing.ll b/llvm/test/CodeGen/SystemZ/zos-readonly-addressing.ll
new file mode 100644
index 0000000000000..0d7932217e39f
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-readonly-addressing.ll
@@ -0,0 +1,42 @@
+; Read-only data stays in the code section (and is addressed PC-relative) only
+; if it is local and its initializer needs no relocations. Externally visible
+; read-only data and tables of function pointers (which point to function
+; descriptors in the WSA) are parts in the WSA and are addressed via the ADA.
+;
+; RUN: llc < %s -mtriple=s390x-ibm-zos | FileCheck %s
+
+; CHECK-LABEL: useext DS 0H
+; CHECK: lg 2,0(5)
+; CHECK-NOT: larl
+; CHECK: b 2(7)
+; CHECK-LABEL: useloc DS 0H
+; CHECK: larl 2,loc
+; CHECK: b 2(7)
+; CHECK-LABEL: usetbl DS 0H
+; CHECK: lg 1,8(5)
+; CHECK-NOT: larl
+; CHECK: b 2(7)
+; CHECK: ext CSECT
+; CHECK-NEXT: C_WSA64 CATTR {{.*}}PART(ext)
+; CHECK: loc XATTR LINKAGE(XPLINK),REFERENCE(DATA),SCOPE(SECTION)
+; CHECK-NEXT: loc DS 0H
+; CHECK: tbl CSECT
+; CHECK-NEXT: C_WSA64 CATTR {{.*}}PART(tbl)
+ at ext = constant [2 x i32] [i32 3, i32 4], align 4
+ at loc = internal constant [2 x i32] [i32 5, i32 6], align 4
+ at tbl = internal constant [1 x ptr] [ptr @f], align 8
+define internal void @f() { ret void }
+define signext i32 @useext(i64 %i) {
+ %p = getelementptr inbounds [2 x i32], ptr @ext, i64 0, i64 %i
+ %v = load i32, ptr %p, align 4
+ ret i32 %v
+}
+define signext i32 @useloc(i64 %i) {
+ %p = getelementptr inbounds [2 x i32], ptr @loc, i64 0, i64 %i
+ %v = load i32, ptr %p, align 4
+ ret i32 %v
+}
+define ptr @usetbl() {
+ %v = load ptr, ptr @tbl, align 8
+ ret ptr %v
+}
diff --git a/llvm/test/CodeGen/SystemZ/zos-section-readonly-extern.ll b/llvm/test/CodeGen/SystemZ/zos-section-readonly-extern.ll
new file mode 100644
index 0000000000000..c75ff2a576cf5
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-section-readonly-extern.ll
@@ -0,0 +1,25 @@
+; Externally visible read-only data is placed into the WSA (C_WSA64), because
+; a reference from another translation unit is always a part in the WSA.
+; Local read-only data stays in the code section.
+;
+; RUN: llc < %s -mtriple=s390x-ibm-zos | FileCheck %s
+
+; CHECK: tab CSECT
+; CHECK-NEXT: C_WSA64 CATTR {{.*}}PART(t
+; CHECK: tab XATTR LINKAGE(XPLINK),REFERENCE(DATA),SCOPE(LIBRARY)
+; CHECK: exp CSECT
+; CHECK-NEXT: C_WSA64 CATTR {{.*}}PART(e
+; CHECK: exp XATTR LINKAGE(XPLINK),REFERENCE(DATA),SCOPE(EXPORT)
+; CHECK: C_CODE64 CATTR
+; CHECK: loc XATTR LINKAGE(XPLINK),REFERENCE(DATA),SCOPE(SECTION)
+; CHECK-NEXT: loc DS 0H
+
+ at tab = hidden constant [2 x i32] [i32 1, i32 2], align 4
+ at exp = constant [2 x i32] [i32 3, i32 4], align 4
+ at loc = internal constant [2 x i32] [i32 5, i32 6], align 4
+
+define signext i32 @get(i64 %i) {
+ %p = getelementptr inbounds [2 x i32], ptr @loc, i64 0, i64 %i
+ %v = load i32, ptr %p, align 4
+ ret i32 %v
+}
>From 924dad683d1ae49247309d6d39ad12962e782b91 Mon Sep 17 00:00:00 2001
From: mms-it-ch <info at mms-it.ch>
Date: Tue, 29 Sep 2026 11:57:34 +0200
Subject: [PATCH 2/2] Drop the SectionKind argument of isReadOnlyInCodeSection
The read-only check is done by the callers.
Assisted-by: Claude Code (Anthropic)
---
.../llvm/CodeGen/TargetLoweringObjectFileImpl.h | 12 ++++++------
llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp | 6 +++---
llvm/lib/Target/SystemZ/SystemZSubtarget.cpp | 3 ++-
3 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
index 390a8441be1e0..6d44206024340 100644
--- a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
+++ b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
@@ -331,12 +331,12 @@ class LLVM_ABI TargetLoweringObjectFileGOFF : public TargetLoweringObjectFile {
void getModuleMetadata(Module &M) override;
- /// Returns true if the read-only global \p GO of kind \p Kind is placed into
- /// the code section and can therefore be addressed PC-relative. This is only
- /// the case for local data whose initializer needs no relocations: references
- /// from other translation units are always parts in the WSA, and function
- /// pointers point to function descriptors in the WSA.
- static bool isReadOnlyInCodeSection(const GlobalObject *GO, SectionKind Kind);
+ /// Returns true if the global \p GO, whose section kind is read-only, is
+ /// placed into the code section and can therefore be addressed PC-relative.
+ /// This is only the case for local data whose initializer needs no
+ /// relocations: references from other translation units are always parts in
+ /// the WSA, and function pointers point to function descriptors in the WSA.
+ static bool isReadOnlyInCodeSection(const GlobalObject *GO);
bool shouldPutJumpTableInFunctionSection(bool UsesLabelDifference,
const Function &F) const override;
diff --git a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
index 11fd38d6f7759..32ae6d0de130c 100644
--- a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
+++ b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
@@ -2922,8 +2922,8 @@ MCSection *TargetLoweringObjectFileGOFF::getSectionForLSDA(
}
bool TargetLoweringObjectFileGOFF::isReadOnlyInCodeSection(
- const GlobalObject *GO, SectionKind Kind) {
- if (!Kind.isReadOnly() || !GO->hasLocalLinkage())
+ const GlobalObject *GO) {
+ if (!GO->hasLocalLinkage())
return false;
if (const auto *GVar = dyn_cast<GlobalVariable>(GO))
if (GVar->hasInitializer() && GVar->getInitializer()->needsRelocation())
@@ -2936,7 +2936,7 @@ MCSection *TargetLoweringObjectFileGOFF::SelectSectionForGlobal(
auto *Symbol = TM.getSymbol(GO);
if (Kind.isBSS() || Kind.isData() || Kind.isReadOnlyWithRel() ||
- (Kind.isReadOnly() && !isReadOnlyInCodeSection(GO, Kind))) {
+ (Kind.isReadOnly() && !isReadOnlyInCodeSection(GO))) {
GOFF::ESDBindingScope PRBindingScope =
GO->hasExternalLinkage()
? (GO->hasDefaultVisibility() ? GOFF::ESD_BSC_ImportExport
diff --git a/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp b/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp
index d4855038492c7..a3dcb76053a9c 100644
--- a/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZSubtarget.cpp
@@ -100,7 +100,8 @@ bool SystemZSubtarget::isAddressedViaADA(const GlobalValue *GV) const {
SectionKind GOKind = TargetLoweringObjectFile::getKindForGlobal(
GO, TLInfo.getTargetMachine());
// Must match the section selection in TargetLoweringObjectFileGOFF.
- return !TargetLoweringObjectFileGOFF::isReadOnlyInCodeSection(GO, GOKind);
+ return !GOKind.isReadOnly() ||
+ !TargetLoweringObjectFileGOFF::isReadOnlyInCodeSection(GO);
}
return true;
}
More information about the llvm-commits
mailing list