[llvm] [SystemZ][z/OS] Keep read-only data in the code section only if local and without relocations (PR #226683)

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 26 04:48:48 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-systemz

Author: MMS IT GmbH (mms-it-ch)

<details>
<summary>Changes</summary>

Read-only data stayed in the code section 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, such as a `static const` table of pointers to internal functions: `getKindForGlobal` returns `ReadOnly`, but the entries point to function descriptors in the WSA and the binder rejects an address constant from the code section into the deferred WSA (IEW2353E, error code 250001). #<!-- -->223055 does not cover this case.

Add `TargetLoweringObjectFileGOFF::isReadOnlyInCodeSection()` and use it both for the section selection and in `SystemZSubtarget::isAddressedViaADA()`, so that the addressing always matches the placement (otherwise data moved to the WSA would still be addressed with `LARL`, giving "relative immediate relocation section mismatch"). Local read-only data without relocations stays in the code section.

New tests `zos-section-readonly-extern.ll` and `zos-readonly-addressing.ll`. Both reproducers from #<!-- -->226659 (including the comment) bind and run on z/OS 3.1; GMP 6.3.0 and GnuCOBOL 3.2 libcob bind and run.

Tests: `llvm-lit test/CodeGen/SystemZ test/MC/SystemZ test/MC/GOFF` passes (1305 passed, 19 unsupported).

Fixes #<!-- -->226659.

Assisted-by: Claude Code (Anthropic)

🤖 Generated with [Claude Code](https://claude.com/claude-code)


---
Full diff: https://github.com/llvm/llvm-project/pull/226683.diff


5 Files Affected:

- (modified) llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h (+7) 
- (modified) llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp (+12-1) 
- (modified) llvm/lib/Target/SystemZ/SystemZSubtarget.cpp (+2-5) 
- (added) llvm/test/CodeGen/SystemZ/zos-readonly-addressing.ll (+42) 
- (added) llvm/test/CodeGen/SystemZ/zos-section-readonly-extern.ll (+25) 


``````````diff
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
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/226683


More information about the llvm-commits mailing list