[llvm] [llvm-ar][GOFF] Implement symbol attributes for GOFF archives (PR #214528)

Amy Kwan via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 06:07:34 PDT 2026


https://github.com/amy-kwan updated https://github.com/llvm/llvm-project/pull/214528

>From 5a020ba6461fa29aa2734c5501a59a82de517927 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/7] [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 a07f3edf17d07..7e590a30e645e 100644
--- a/llvm/include/llvm/Object/GOFFObjectFile.h
+++ b/llvm/include/llvm/Object/GOFFObjectFile.h
@@ -67,6 +67,12 @@ class LLVM_ABI GOFFObjectFile : public ObjectFile {
 
   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 e18fcfa0f880c..7881d610e8a99 100644
--- a/llvm/lib/Object/GOFFObjectFile.cpp
+++ b/llvm/lib/Object/GOFFObjectFile.cpp
@@ -535,6 +535,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 ab3c2022f3c7bf4ca510a2f7ebd525e8a3c12d93 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/7] 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 3d0ce83761b124dd9fa8c1ed3538303679fcb2b1 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/7] 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 7881d610e8a99..32cb2318f044a 100644
--- a/llvm/lib/Object/GOFFObjectFile.cpp
+++ b/llvm/lib/Object/GOFFObjectFile.cpp
@@ -539,15 +539,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;
@@ -555,17 +560,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;
 }

>From 66188661f9774a0ce518ca25fbdacd95f997e559 Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Wed, 19 Aug 2026 11:33:28 -0400
Subject: [PATCH 4/7] Add assert to ensure SymbolAttrs and Symbols are equal
 sized.

---
 llvm/lib/Object/ArchiveWriter.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index f3126357946d1..ca3864f136d5e 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -680,6 +680,8 @@ static void writeSymbolTable(raw_ostream &Out, object::Archive::Kind Kind,
       }
     }
 
+    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, M.Symbols[I]);

>From 7002dda1b87d05c2e86226dd43ed17f9f5650a66 Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Sat, 5 Sep 2026 01:42:37 +0000
Subject: [PATCH 5/7] Address archive writing and test case comments.

---
 llvm/lib/Object/ArchiveWriter.cpp         |  6 +++---
 llvm/test/tools/llvm-ar/zos-symattrs.test | 11 ++++++-----
 llvm/test/tools/llvm-ar/zos-write.test    |  9 ++++++++-
 3 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index ca3864f136d5e..6ef79bb5ba44a 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -687,7 +687,7 @@ static void writeSymbolTable(raw_ostream &Out, object::Archive::Kind Kind,
         printNBits(Out, Kind, M.Symbols[I]);
       printNBits(Out, Kind, Pos); // member offset
       if (isZOSArchive(Kind))
-        printNBits(Out, Kind, M.SymbolAttrs[I]); // symbol attribute flags
+        printNBits(Out, Kind, M.SymbolAttrs[I]); // symbol attributes
     }
     Pos += M.Header.size() + M.Data.size() + M.Padding.size();
   }
@@ -1087,7 +1087,7 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames,
       if (isZOSArchive(Kind)) {
         auto *GOFFObj = dyn_cast_or_null<GOFFObjectFile>(D.SymFile.get());
         if (GOFFObj) {
-          for (const object::BasicSymbolRef &S : GOFFObj->symbols()) {
+          for (object::BasicSymbolRef S : GOFFObj->symbols()) {
             if (!isArchiveSymbol(S))
               continue;
             D.SymbolAttrs.push_back(
@@ -1097,7 +1097,7 @@ computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames,
           // 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);
+          D.SymbolAttrs.resize(D.Symbols.size());
         }
       }
       if (D.SymFile)
diff --git a/llvm/test/tools/llvm-ar/zos-symattrs.test b/llvm/test/tools/llvm-ar/zos-symattrs.test
index 3fab075c995e1..4463098815fc8 100644
--- a/llvm/test/tools/llvm-ar/zos-symattrs.test
+++ b/llvm/test/tools/llvm-ar/zos-symattrs.test
@@ -4,7 +4,7 @@
 ## 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
+##   bit 0 (0x1): WSA     - NameSpaceId == ESD_NS_Parts
 ##
 ## GOFF objects are produced by compiling embedded .ll files with llc.
 
@@ -28,14 +28,15 @@
 # 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
+##    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: 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).
+##    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
diff --git a/llvm/test/tools/llvm-ar/zos-write.test b/llvm/test/tools/llvm-ar/zos-write.test
index 0e390183f8d00..dbf62713918f0 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,13 @@
 # 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-nm --print-armap %t.gnu-blank.a | FileCheck %s --check-prefix=GNU-NO-BLANK
+
+# GNU-NO-BLANK-NOT: in blank.bc
+
 #--- mytest.ll
 define i32 @mytest() {
 entry:

>From 66d654ee7e22cfe408be5e788929786ce3dc2556 Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Wed, 9 Sep 2026 00:49:21 -0400
Subject: [PATCH 6/7] Add TODOs and fix check.

---
 llvm/lib/Object/GOFFObjectFile.cpp        | 4 ++++
 llvm/test/tools/llvm-ar/zos-symattrs.test | 4 +++-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Object/GOFFObjectFile.cpp b/llvm/lib/Object/GOFFObjectFile.cpp
index 32cb2318f044a..e7bf4e8878b46 100644
--- a/llvm/lib/Object/GOFFObjectFile.cpp
+++ b/llvm/lib/Object/GOFFObjectFile.cpp
@@ -541,6 +541,10 @@ uint32_t GOFFObjectFile::getZOSSymbolArchiveAttributes(DataRefImpl Symb) const {
 
   // 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) {
diff --git a/llvm/test/tools/llvm-ar/zos-symattrs.test b/llvm/test/tools/llvm-ar/zos-symattrs.test
index 4463098815fc8..239cdce6dce75 100644
--- a/llvm/test/tools/llvm-ar/zos-symattrs.test
+++ b/llvm/test/tools/llvm-ar/zos-symattrs.test
@@ -21,6 +21,8 @@
 # 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.
+## TODO: Add a GOFF symbol with no attributes (flags: 0x00000000 [none]) once
+## yaml2obj gains full GOFF ESD record support.
 # RUN: llvm-nm --print-armap %t.dir/test.a | FileCheck %s --check-prefix=ARMAP
 
 # ARMAP:      Archive map
@@ -30,7 +32,7 @@
 ## 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={{.+}}
+# RUN:   --implicit-check-not={{.}}
 
 # LIST: func.o
 # LIST-NEXT: data.o

>From 54e937025aef05ddef5d4e6ca4b54f115ecef42d Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Wed, 9 Sep 2026 09:07:14 -0400
Subject: [PATCH 7/7] Fix clang-format

---
 llvm/lib/Object/GOFFObjectFile.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Object/GOFFObjectFile.cpp b/llvm/lib/Object/GOFFObjectFile.cpp
index e7bf4e8878b46..572d8a1565c49 100644
--- a/llvm/lib/Object/GOFFObjectFile.cpp
+++ b/llvm/lib/Object/GOFFObjectFile.cpp
@@ -543,8 +543,8 @@ uint32_t GOFFObjectFile::getZOSSymbolArchiveAttributes(DataRefImpl Symb) const {
   // 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.
+  // 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) {



More information about the llvm-commits mailing list