[llvm-branch-commits] [llvm] release/23.x: [llvm-ar][GOFF] Implement symbol attributes for GOFF archives (#214528) (PR #226705)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Sep 26 07:53:45 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-binary-utilities
Author: Amy Kwan (amy-kwan)
<details>
<summary>Changes</summary>
Backport 847fb5cfde68fdea490171bc24087ebb0cd90738
This backport has been modified slightly by updating the LIT tests to remove
the dependency on llvm-nm. llvm-nm added GOFF support after the LLVM 23.x
release cut, and its new functionality is not available in LLVM 23.
---
Full diff: https://github.com/llvm/llvm-project/pull/226705.diff
5 Files Affected:
- (modified) llvm/include/llvm/Object/GOFFObjectFile.h (+6)
- (modified) llvm/lib/Object/ArchiveWriter.cpp (+38-14)
- (modified) llvm/lib/Object/GOFFObjectFile.cpp (+38)
- (added) llvm/test/tools/llvm-ar/zos-symattrs.test (+81)
- (modified) llvm/test/tools/llvm-ar/zos-write.test (+9-1)
``````````diff
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..6ef79bb5ba44a 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,14 @@ static void writeSymbolTable(raw_ostream &Out, object::Archive::Kind Kind,
}
}
- for (unsigned StringOffset : M.Symbols) {
+ assert((!isZOSArchive(Kind) || M.Symbols.size() == M.SymbolAttrs.size()) &&
+ "Incorrect number of symbol attributes!");
+ 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 attributes
}
Pos += M.Header.size() + M.Data.size() + M.Padding.size();
}
@@ -1078,17 +1082,37 @@ 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 (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());
+ }
+ }
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();
diff --git a/llvm/lib/Object/GOFFObjectFile.cpp b/llvm/lib/Object/GOFFObjectFile.cpp
index d00ad71105fd5..2a12a80e3b108 100644
--- a/llvm/lib/Object/GOFFObjectFile.cpp
+++ b/llvm/lib/Object/GOFFObjectFile.cpp
@@ -381,6 +381,44 @@ 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. If the child AMODE is unspecified,
+ // query the parent ED.
+ // TODO: The parent-walk path (child ESD_AMODE_None with a parent that has
+ // ESD_AMODE_64) cannot currently be tested as GOFFObjectWriter always emits
+ // ESD_AMODE_64 directly on LD/ER records and does not set AMODE on ED
+ // records. Full coverage requires yaml2obj GOFF ESD record support.
+ 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;
+ ESDRecord::getLinkageType(SymRecord, LinkageType);
+ if (LinkageType == GOFF::ESD_LT_XPLink)
+ Attrs |= 0x2;
+
+ // Bit 0 (0x1): Writable Static Area.
+ GOFF::ESDNameSpaceId NameSpace;
+ ESDRecord::getNameSpaceId(SymRecord, 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..d776922586d16
--- /dev/null
+++ b/llvm/test/tools/llvm-ar/zos-symattrs.test
@@ -0,0 +1,81 @@
+## Test that llvm-ar correctly writes z/OS archive symbol attribute bits.
+##
+## 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 - 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 attribute words written into the archive symbol table.
+## s_func: 0x6 (64-bit=0x4 + XPLink=0x2)
+## s_data: 0x3 (XPLink=0x2 + WSA=0x1)
+# RUN: %python -c " \
+# RUN: d = open(r'%t.dir/test.a', 'rb').read(); \
+# RUN: print(hex(int.from_bytes(d[76:80], 'big'))); \
+# RUN: print(hex(int.from_bytes(d[84:88], 'big'))) \
+# RUN: " | FileCheck %s --check-prefix=ATTRS
+
+# ATTRS: 0x6
+# ATTRS-NEXT: 0x3
+
+## 2. Test that basic archive operations still work on a GOFF archive.
+## llvm-ar t should list exactly the expected member names and nothing else.
+# RUN: llvm-ar t %t.dir/test.a | FileCheck %s --check-prefix=LIST \
+# RUN: --implicit-check-not={{.}}
+
+# LIST: func.o
+# LIST-NEXT: data.o
+
+## 3. Test mixed archive: one GOFF object and one bitcode object together.
+## The bitcode member should have an 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: %python -c " \
+# RUN: d = open(r'%t.dir/mixed.a', 'rb').read(); \
+# RUN: print(hex(int.from_bytes(d[76:80], 'big'))); \
+# RUN: print(hex(int.from_bytes(d[84:88], 'big'))) \
+# RUN: " | FileCheck %s --check-prefix=MIXED-ATTRS
+
+# MIXED-ATTRS: 0x6
+# MIXED-ATTRS-NEXT: 0x0
+
+#--- 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
+}
diff --git a/llvm/test/tools/llvm-ar/zos-write.test b/llvm/test/tools/llvm-ar/zos-write.test
index 0e390183f8d00..410d1ed020d60 100644
--- a/llvm/test/tools/llvm-ar/zos-write.test
+++ b/llvm/test/tools/llvm-ar/zos-write.test
@@ -1,6 +1,6 @@
## Test writing a z/OS archive.
-# RUN: rm -rf %t.dir %t.a %t.odd.a %t.long.a %t.sym.a %t.blank-sym.a && mkdir -p %t.dir
+# RUN: rm -rf %t.dir %t.a %t.odd.a %t.long.a %t.sym.a %t.blank-sym.a %t.gnu-blank.a && mkdir -p %t.dir
# RUN: split-file %s %t.dir
## Create a z/OS archive from a plain text file with symbol table writing disabled.
@@ -62,6 +62,14 @@
# EMPTY-SYMTAB-NEXT: in blank.bc
# EMPTY-SYMTAB-NOT: in blank.bc
+## The blank-symbol injection is z/OS specific. A GNU archive with no real
+## symbols should not have a blank symbol injected into its symbol table.
+# RUN: llvm-ar rcD %t.gnu-blank.a %t.dir/blank.bc
+# RUN: llvm-ar t %t.gnu-blank.a | FileCheck %s --check-prefix=GNU-NO-BLANK \
+# RUN: --implicit-check-not={{.}}
+
+# GNU-NO-BLANK: blank.bc
+
#--- mytest.ll
define i32 @mytest() {
entry:
``````````
</details>
https://github.com/llvm/llvm-project/pull/226705
More information about the llvm-branch-commits
mailing list