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 <[email protected]> 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" + +@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 <[email protected]> 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 <[email protected]> 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; } _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
