[llvm-branch-commits] [llvm] [llvm-ar][GOFF] Implement symbol attributes for GOFF archives (PR #214528)
Amy Kwan via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Aug 12 22:06:33 PDT 2026
https://github.com/amy-kwan updated https://github.com/llvm/llvm-project/pull/214528
>From 1250c3e52cdd3134dcc2e3ab1473b7675d8184f8 Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Thu, 6 Aug 2026 12:34:57 -0400
Subject: [PATCH 1/3] [llvm-ar][GOFF] Implement symbol attributes for GOFF
archives
z/OS archive symbol table entries contain a 32-bit attribute word
alongside each member offset. The low three bits encode:
bit 2 (0x4): 64-bit addressing (AMODE 64)
bit 1 (0x2): XPLink calling convention
bit 0 (0x1): Writable Static Area (WSA)
Previously in e2c8fa0, llvm-ar wrote zero for
these attributes. This patch reads them from GOFF ESD records and stores them
in a SymbolAttrs vector parallel to the existing Symbols vector in
MemberData to emit the correct word per symbol.
These attributes are tested using `llvm-nm --print-armap` implemented in
#212830 within the LIT test.
---
llvm/include/llvm/Object/GOFFObjectFile.h | 6 ++
llvm/lib/Object/ArchiveWriter.cpp | 51 +++++++++++-----
llvm/lib/Object/GOFFObjectFile.cpp | 35 +++++++++++
llvm/test/tools/llvm-ar/zos-symattrs.test | 73 +++++++++++++++++++++++
4 files changed, 151 insertions(+), 14 deletions(-)
create mode 100644 llvm/test/tools/llvm-ar/zos-symattrs.test
diff --git a/llvm/include/llvm/Object/GOFFObjectFile.h b/llvm/include/llvm/Object/GOFFObjectFile.h
index 80da64ed3730d..4ffffc828e103 100644
--- a/llvm/include/llvm/Object/GOFFObjectFile.h
+++ b/llvm/include/llvm/Object/GOFFObjectFile.h
@@ -47,6 +47,12 @@ class LLVM_ABI GOFFObjectFile : public ObjectFile {
public:
Expected<StringRef> getSymbolName(SymbolRef Symbol) const;
+ // Returns the z/OS archive symbol attribute bits for a symbol:
+ // bit 2 (0x4): symbol is 64-bit
+ // bit 1 (0x2): symbol uses the XPLink calling convention
+ // bit 0 (0x1): symbol resides in a writable static area (WSA)
+ uint32_t getZOSSymbolArchiveAttributes(DataRefImpl Symb) const;
+
GOFFObjectFile(MemoryBufferRef Object, Error &Err);
static inline bool classof(const Binary *V) { return V->isGOFF(); }
section_iterator section_begin() const override;
diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index 9af8e0efe1872..4540ec5f1c75e 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -368,6 +368,9 @@ printMemberHeader(raw_ostream &Out, uint64_t Pos, raw_ostream &StringTable,
namespace {
struct MemberData {
std::vector<unsigned> Symbols;
+ // z/OS archive attribute bits per symbol. Entry i of SymbolAttrs corresponds
+ // to Symbols[i]. These attributes are empty for non-z/OS archives.
+ std::vector<uint32_t> SymbolAttrs;
std::string Header;
StringRef Data;
StringRef Padding;
@@ -386,7 +389,7 @@ static MemberData computeStringTable(StringRef Names) {
printWithSpacePadding(Out, "//", 48);
printWithSpacePadding(Out, Size + Pad, 10);
Out << "`\n";
- return {{}, std::move(Header), Names, Pad ? "\n" : ""};
+ return {{}, {}, std::move(Header), Names, Pad ? "\n" : ""};
}
static sys::TimePoint<std::chrono::seconds> now(bool Deterministic) {
@@ -677,13 +680,12 @@ static void writeSymbolTable(raw_ostream &Out, object::Archive::Kind Kind,
}
}
- for (unsigned StringOffset : M.Symbols) {
+ for (size_t I = 0, E = M.Symbols.size(); I != E; ++I) {
if (isBSDLike(Kind))
- printNBits(Out, Kind, StringOffset);
+ printNBits(Out, Kind, M.Symbols[I]);
printNBits(Out, Kind, Pos); // member offset
- // FIXME: Properly handle symbol attributes for z/OS archives.
if (isZOSArchive(Kind))
- printNBits(Out, Kind, 0); // symbol flags
+ printNBits(Out, Kind, M.SymbolAttrs[I]); // symbol attribute flags
}
Pos += M.Header.size() + M.Data.size() + M.Padding.size();
}
@@ -1078,21 +1080,42 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames,
if (!SymbolsOrErr)
return createFileError(MemberName, SymbolsOrErr.takeError());
D.Symbols = std::move(*SymbolsOrErr);
+ // For z/OS, populate SymbolAttrs in lockstep with Symbols so that
+ // writeSymbolTable() can emit the per-symbol attribute word.
+ if (isZOSArchive(Kind)) {
+ auto *GOFFObj = dyn_cast_or_null<GOFFObjectFile>(D.SymFile.get());
+ if (GOFFObj) {
+ for (const object::BasicSymbolRef &S : GOFFObj->symbols()) {
+ if (!isArchiveSymbol(S))
+ continue;
+ D.SymbolAttrs.push_back(
+ GOFFObj->getZOSSymbolArchiveAttributes(S.getRawDataRefImpl()));
+ }
+ } else {
+ // For non-GOFF symbolic files (e.g. bitcode/IR), there is no z/OS
+ // archive attribute data available. Pad SymbolAttrs to stay in sync
+ // with Symbols.
+ D.SymbolAttrs.resize(D.Symbols.size(), 0);
+ }
+ }
if (D.SymFile)
HasObject = true;
- }
- // On z/OS, when there are no symbols, add a dummy blank symbol
- // into the symbol table. This is done since the z/OS binder:
- // - emits an error if there is no symbol table in the archive
- // - emits an error if the symbol table has 0 symbols
- // - should not find any references to a blank symbol
- if ((LastZosObjIndex == Index) && (SymNames.tell() == 0)) {
- D.Symbols.push_back(0);
- SymNames << ' ' << '\0';
+ // On z/OS, when there are no symbols, add a dummy blank symbol
+ // into the symbol table. This is done since the z/OS binder:
+ // - emits an error if there is no symbol table in the archive
+ // - emits an error if the symbol table has 0 symbols
+ // - should not find any references to a blank symbol
+ if (isZOSArchive(Kind) && (LastZosObjIndex == Index) &&
+ (SymNames.tell() == 0)) {
+ D.Symbols.push_back(0);
+ D.SymbolAttrs.push_back(0);
+ SymNames << ' ' << '\0';
+ }
}
Pos += D.Header.size() + D.Data.size() + D.Padding.size();
}
+
// If there are no symbols, emit an empty symbol table, to satisfy Solaris
// tools, older versions of which expect a symbol table in a non-empty
// archive, regardless of whether there are any symbols in it.
diff --git a/llvm/lib/Object/GOFFObjectFile.cpp b/llvm/lib/Object/GOFFObjectFile.cpp
index 057d3fc208c9b..47d690e7f092e 100644
--- a/llvm/lib/Object/GOFFObjectFile.cpp
+++ b/llvm/lib/Object/GOFFObjectFile.cpp
@@ -375,6 +375,41 @@ GOFFObjectFile::getSymbolSection(DataRefImpl Symb) const {
std::to_string(SymEdId));
}
+uint32_t GOFFObjectFile::getZOSSymbolArchiveAttributes(DataRefImpl Symb) const {
+ const uint8_t *SymRecord = getSymbolEsdRecord(Symb);
+ uint32_t Attrs = 0;
+
+ // Bit 2 (0x4): 64-bit — AMODE is only defined for LD/ER records.
+ GOFF::ESDSymbolType SymType;
+ ESDRecord::getSymbolType(SymRecord, SymType);
+ if (SymType != GOFF::ESD_ST_PartReference) {
+ GOFF::ESDAmode Amode;
+ ESDRecord::getAmode(SymRecord, Amode);
+ if (Amode == GOFF::ESD_AMODE_64)
+ Attrs |= 0x4;
+ }
+
+ // Bit 1 (0x2): XPLink — LinkageType is ESD_LT_XPLink.
+ GOFF::ESDLinkageType LinkageType;
+ ESDRecord::getLinkageType(SymRecord, LinkageType);
+ if (LinkageType == GOFF::ESD_LT_XPLink)
+ Attrs |= 0x2;
+
+ // Bit 0 (0x1): Writable Static Area — the symbol's parent ED has NameSpace
+ // ESD_NS_Parts.
+ uint32_t ParentEsdId;
+ ESDRecord::getParentEsdId(SymRecord, ParentEsdId);
+ if (ParentEsdId) {
+ const uint8_t *EdRecord = EsdPtrs[ParentEsdId];
+ GOFF::ESDNameSpaceId NameSpace;
+ ESDRecord::getNameSpaceId(EdRecord, NameSpace);
+ if (NameSpace == GOFF::ESD_NS_Parts)
+ Attrs |= 0x1;
+ }
+
+ return Attrs;
+}
+
uint64_t GOFFObjectFile::getSymbolSize(DataRefImpl Symb) const {
const uint8_t *Record = getSymbolEsdRecord(Symb);
uint32_t Length;
diff --git a/llvm/test/tools/llvm-ar/zos-symattrs.test b/llvm/test/tools/llvm-ar/zos-symattrs.test
new file mode 100644
index 0000000000000..3fab075c995e1
--- /dev/null
+++ b/llvm/test/tools/llvm-ar/zos-symattrs.test
@@ -0,0 +1,73 @@
+## Test that llvm-ar correctly writes z/OS archive symbol attribute bits,
+## and that llvm-nm --print-armap reads them back correctly.
+##
+## The three attribute bits come from GOFF ESD record fields:
+## bit 2 (0x4): 64-bit - AMODE == ESD_AMODE_64
+## bit 1 (0x2): XPLink - LinkageType == ESD_LT_XPLink
+## bit 0 (0x1): WSA - parent ED NameSpaceId == ESD_NS_Parts
+##
+## GOFF objects are produced by compiling embedded .ll files with llc.
+
+# REQUIRES: systemz-registered-target
+
+# RUN: rm -rf %t.dir && mkdir -p %t.dir
+# RUN: split-file %s %t.dir
+
+## Compile each .ll file into a GOFF object.
+# RUN: llc -mtriple=s390x-ibm-zos -filetype=obj %t.dir/func.ll -o %t.dir/func.o
+# RUN: llc -mtriple=s390x-ibm-zos -filetype=obj %t.dir/data.ll -o %t.dir/data.o
+
+## Build a z/OS archive containing both objects.
+# RUN: llvm-ar rcD --format=zos %t.dir/test.a %t.dir/func.o %t.dir/data.o
+
+## 1. Check the archive map shows each symbol with the correct inline flags.
+# RUN: llvm-nm --print-armap %t.dir/test.a | FileCheck %s --check-prefix=ARMAP
+
+# ARMAP: Archive map
+# ARMAP-NEXT: s_func in func.o (flags: 0x00000006 [64-bit + XPLink])
+# ARMAP-NEXT: s_data in data.o (flags: 0x00000003 [XPLink + WSA])
+
+## 2. Test that basic archive operations still work on a GOFF archive.
+## llvm-ar t should list all member names.
+# RUN: llvm-ar t %t.dir/test.a | FileCheck %s --check-prefix=LIST
+
+# LIST: func.o
+# LIST-NEXT: data.o
+
+## 3. Test mixed archive: one GOFF object and one bitcode object together.
+## The bitcode member should produce attribute of 0 (no GOFF ESD data available).
+# RUN: llc -mtriple=s390x-ibm-zos -filetype=obj %t.dir/func2.ll -o %t.dir/func2.o
+# RUN: llvm-as %t.dir/mixed.ll -o %t.dir/mixed.bc
+# RUN: llvm-ar rcD --format=zos %t.dir/mixed.a %t.dir/func2.o %t.dir/mixed.bc
+# RUN: llvm-nm --print-armap %t.dir/mixed.a | FileCheck %s --check-prefix=MIXED
+
+# MIXED: Archive map
+# MIXED-NEXT: s_func2 in func2.o (flags: 0x00000006 [64-bit + XPLink])
+# MIXED-NEXT: mixed_sym in mixed.bc (flags: 0x00000000 [none])
+
+#--- func.ll
+target triple = "s390x-ibm-zos"
+
+define void @s_func() {
+entry:
+ ret void
+}
+
+#--- data.ll
+target triple = "s390x-ibm-zos"
+
+ at s_data = global i32 0, align 4
+
+#--- func2.ll
+target triple = "s390x-ibm-zos"
+
+define void @s_func2() {
+entry:
+ ret void
+}
+
+#--- mixed.ll
+define i32 @mixed_sym() {
+entry:
+ ret i32 0
+}
>From 671e17444520bedb1947d522d467aac0ef12eadb Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Thu, 6 Aug 2026 15:48:34 -0400
Subject: [PATCH 2/3] Remove unnecessary new line
---
llvm/lib/Object/ArchiveWriter.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index 4540ec5f1c75e..f3126357946d1 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -1115,7 +1115,6 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames,
Pos += D.Header.size() + D.Data.size() + D.Padding.size();
}
-
// If there are no symbols, emit an empty symbol table, to satisfy Solaris
// tools, older versions of which expect a symbol table in a non-empty
// archive, regardless of whether there are any symbols in it.
>From 47cc2f4fcc4931e2cb0ca7652d08fe8aa874de7f Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Thu, 13 Aug 2026 01:06:07 -0400
Subject: [PATCH 3/3] Address comments in GOFFObjectFile.cpp
---
llvm/lib/Object/GOFFObjectFile.cpp | 37 +++++++++++++++---------------
1 file changed, 18 insertions(+), 19 deletions(-)
diff --git a/llvm/lib/Object/GOFFObjectFile.cpp b/llvm/lib/Object/GOFFObjectFile.cpp
index 47d690e7f092e..03181415e5473 100644
--- a/llvm/lib/Object/GOFFObjectFile.cpp
+++ b/llvm/lib/Object/GOFFObjectFile.cpp
@@ -379,15 +379,20 @@ uint32_t GOFFObjectFile::getZOSSymbolArchiveAttributes(DataRefImpl Symb) const {
const uint8_t *SymRecord = getSymbolEsdRecord(Symb);
uint32_t Attrs = 0;
- // Bit 2 (0x4): 64-bit — AMODE is only defined for LD/ER records.
- GOFF::ESDSymbolType SymType;
- ESDRecord::getSymbolType(SymRecord, SymType);
- if (SymType != GOFF::ESD_ST_PartReference) {
- GOFF::ESDAmode Amode;
- ESDRecord::getAmode(SymRecord, Amode);
- if (Amode == GOFF::ESD_AMODE_64)
- Attrs |= 0x4;
+ // Bit 2 (0x4): 64-bit AMODE. If the child AMODE is unspecified,
+ // query the parent ED.
+ GOFF::ESDAmode Amode;
+ ESDRecord::getAmode(SymRecord, Amode);
+ if (Amode == GOFF::ESD_AMODE_None) {
+ uint32_t ParentEsdId;
+ ESDRecord::getParentEsdId(SymRecord, ParentEsdId);
+ if (ParentEsdId) {
+ const uint8_t *EdRecord = EsdPtrs[ParentEsdId];
+ ESDRecord::getAmode(EdRecord, Amode);
+ }
}
+ if (Amode == GOFF::ESD_AMODE_64)
+ Attrs |= 0x4;
// Bit 1 (0x2): XPLink — LinkageType is ESD_LT_XPLink.
GOFF::ESDLinkageType LinkageType;
@@ -395,17 +400,11 @@ uint32_t GOFFObjectFile::getZOSSymbolArchiveAttributes(DataRefImpl Symb) const {
if (LinkageType == GOFF::ESD_LT_XPLink)
Attrs |= 0x2;
- // Bit 0 (0x1): Writable Static Area — the symbol's parent ED has NameSpace
- // ESD_NS_Parts.
- uint32_t ParentEsdId;
- ESDRecord::getParentEsdId(SymRecord, ParentEsdId);
- if (ParentEsdId) {
- const uint8_t *EdRecord = EsdPtrs[ParentEsdId];
- GOFF::ESDNameSpaceId NameSpace;
- ESDRecord::getNameSpaceId(EdRecord, NameSpace);
- if (NameSpace == GOFF::ESD_NS_Parts)
- Attrs |= 0x1;
- }
+ // Bit 0 (0x1): Writable Static Area.
+ GOFF::ESDNameSpaceId NameSpace;
+ ESDRecord::getNameSpaceId(SymRecord, NameSpace);
+ if (NameSpace == GOFF::ESD_NS_Parts)
+ Attrs |= 0x1;
return Attrs;
}
More information about the llvm-branch-commits
mailing list