Skip to content

[SystemZ][z/OS] Implement _Export keyword #140944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

perry-ca
Copy link
Contributor

@perry-ca perry-ca commented May 21, 2025

Implement the _Export keyword that is used on z/OS to indicate that a symbol with external linkage is to be exported from the shared library. In the XL C/C++ compiler this keyword is used only in C++ source code. That is being extended to include C source as well in this PR.

This code was originally in PR #111035. I have split it out into a separate PR so the code for #pragma export is in one PR and the code for _Export keyword is in another. See that original PR for earlier comments.

For reference, the XL documentation for _Export:

@perry-ca perry-ca self-assigned this May 21, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category backend:SystemZ clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 21, 2025
@llvmbot
Copy link
Member

llvmbot commented May 21, 2025

@llvm/pr-subscribers-clang

Author: Sean Perry (perry-ca)

Changes

Implement the _Export keyword that is used on z/OS to indicate that a symbol with external linkage is to be exported from the shared library. In the XL C/C++ compiler this keyword is used only in C++ source code. That is being extended to include C source as well in this PR.

This code was originally in PR #111035. I have split it out into a separate PR so the code for #pragma export is in one PR and the code for _Export keyword is in another. See that original PR for earlier comments.

For reference, the XL documentation for _Export: https://www.ibm.com/docs/en/zos/3.1.0?topic=specifiers-export-function-specifier-c-only


Patch is 20.71 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/140944.diff

18 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/Basic/Attr.td (+6)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+33)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+3)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+34-4)
  • (modified) clang/include/clang/Sema/Sema.h (+2)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+4)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+15)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+6)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+6)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+21)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+9)
  • (added) clang/test/CodeGen/attr-export.c (+23)
  • (added) clang/test/CodeGen/attr-export.cpp (+59)
  • (added) clang/test/Sema/attr-export-failing.cpp (+4)
  • (added) clang/test/Sema/zos-export.c (+25)
  • (added) clang/test/Sema/zos-export.cpp (+44)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ad8409397ff8a..8abfd60caedbc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -879,6 +879,11 @@ WebAssembly Support
 AVR Support
 ^^^^^^^^^^^
 
+SystemZ Support
+^^^^^^^^^^^^^^^
+
+- Add support for `_Export` keyword for z/OS
+
 DWARF Support in Clang
 ----------------------
 
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a6a7482a94a29..7bbaaa53e275b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4267,6 +4267,12 @@ def Thread : Attr {
   let Subjects = SubjectList<[Var]>;
 }
 
+def zOSExport : InheritableAttr {
+  let Spellings = [CustomKeyword<"_Export">];
+  let Subjects = SubjectList<[Function, Var, CXXRecord]>;
+  let Documentation = [zOSExportDocs];
+}
+
 def Win64 : IgnoredAttr {
   let Spellings = [CustomKeyword<"__w64">];
   let LangOpts = [MicrosoftExt];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 65d66dd398ad1..3beb38552df05 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -242,6 +242,39 @@ members, and static locals.
   }];
 }
 
+def zOSExportDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+Use the ``_Export`` keyword on a function or external variable to declare
+that it is to be exported (made available to other modules).  A symbol needs to be
+declared exported on or before the definition of the symbol.  The ``_Export``
+keyword must immediately precede the declaration name in the declarator.
+For example:
+
+.. code-block:: c
+
+  int _Export func(float);
+  int (*_Export funcPtr)(float);
+
+The first statement exports the function ``func``, if ``func`` is defined in the
+translation unit after this declaration.
+
+All of the static data members and member functions of a class or struct and its vague
+linkage objects (vtable, typeinfo, typeinfo name) can be exported
+by including ``_Export`` in tag of the class during the class definition or forward
+declaration of the class.
+
+.. code-block:: c++
+
+  class _Export C {
+    int func();
+  };
+
+Select members of a class can be exported by using the ``_Export`` keyword on
+declaration within the class or definition of the member.
+  }];
+}
+
 def NoEscapeDocs : Documentation {
   let Category = DocCatVariable;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 66f9480d99380..8d84d0bb698b3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9655,6 +9655,8 @@ def warn_redefine_extname_not_applied : Warning<
   "#pragma redefine_extname is applicable to external C declarations only; "
   "not applied to %select{function|variable}0 %1">,
   InGroup<Pragmas>;
+def err_cannot_be_exported : Error<
+  "needs to have external linkage to be '_Export` qualified">;
 } // End of general sema category.
 
 // inline asm.
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 94e72fea56a68..d0cb1aa2d7844 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -353,6 +353,9 @@ KEYWORD(__ptrauth                   , KEYALL)
 // C2y
 UNARY_EXPR_OR_TYPE_TRAIT(_Countof, CountOf, KEYNOCXX)
 
+// z/OS specific keywords
+KEYWORD(_Export                     , KEYZOS)
+
 // C++ 2.11p1: Keywords.
 KEYWORD(asm                         , KEYCXX|KEYGNU)
 KEYWORD(bool                        , BOOLSUPPORT|KEYC23)
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 6c4a32c4ac2f0..58a69d274a02e 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -397,6 +397,8 @@ class DeclSpec {
   unsigned FS_virtual_specified : 1;
   LLVM_PREFERRED_TYPE(bool)
   unsigned FS_noreturn_specified : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1; // z/OS extension
 
   // friend-specifier
   LLVM_PREFERRED_TYPE(bool)
@@ -443,6 +445,7 @@ class DeclSpec {
   SourceLocation FS_forceinlineLoc;
   SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
   SourceLocation TQ_pipeLoc;
+  SourceLocation ExportLoc;
 
   WrittenBuiltinSpecs writtenBS;
   void SaveWrittenBuiltinSpecs();
@@ -491,9 +494,9 @@ class DeclSpec {
         TypeSpecPipe(false), TypeSpecSat(false), ConstrainedAuto(false),
         TypeQualifiers(TQ_unspecified), FS_inline_specified(false),
         FS_forceinline_specified(false), FS_virtual_specified(false),
-        FS_noreturn_specified(false), FriendSpecifiedFirst(false),
-        ConstexprSpecifier(
-            static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
+        FS_noreturn_specified(false), ExportSpecified(false),
+        FriendSpecifiedFirst(false), ConstexprSpecifier(static_cast<unsigned>(
+                                         ConstexprSpecKind::Unspecified)),
         Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
 
   // storage-class-specifier
@@ -660,6 +663,9 @@ class DeclSpec {
   bool isNoreturnSpecified() const { return FS_noreturn_specified; }
   SourceLocation getNoreturnSpecLoc() const { return FS_noreturnLoc; }
 
+  bool isExportSpecified() const { return ExportSpecified; }
+  SourceLocation getExportSpecLoc() const { return ExportLoc; }
+
   void ClearFunctionSpecs() {
     FS_inline_specified = false;
     FS_inlineLoc = SourceLocation();
@@ -810,6 +816,8 @@ class DeclSpec {
   bool setFunctionSpecNoreturn(SourceLocation Loc, const char *&PrevSpec,
                                unsigned &DiagID);
 
+  bool setExportSpec(SourceLocation Loc);
+
   bool SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
                      unsigned &DiagID);
   bool setModulePrivateSpec(SourceLocation Loc, const char *&PrevSpec,
@@ -1955,6 +1963,10 @@ class Declarator {
   LLVM_PREFERRED_TYPE(bool)
   unsigned InlineStorageUsed : 1;
 
+  /// Indicates whether this is set as _Export.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1; // z/OS extension
+
   /// Indicates whether this declarator has an initializer.
   LLVM_PREFERRED_TYPE(bool)
   unsigned HasInitializer : 1;
@@ -2001,6 +2013,9 @@ class Declarator {
   /// this declarator as a parameter pack.
   SourceLocation EllipsisLoc;
 
+  /// The source location of the _Export keyword on this declarator.
+  SourceLocation ExportLoc;
+
   Expr *PackIndexingExpr;
 
   friend struct DeclaratorChunk;
@@ -2030,7 +2045,8 @@ class Declarator {
                                    FunctionDefinitionKind::Declaration)),
         Redeclaration(false), Extension(false), ObjCIvar(false),
         ObjCWeakProperty(false), InlineStorageUsed(false),
-        HasInitializer(false), Attrs(DS.getAttributePool().getFactory()),
+        ExportSpecified(false), HasInitializer(false),
+        Attrs(DS.getAttributePool().getFactory()),
         DeclarationAttrs(DeclarationAttrs), AsmLabel(nullptr),
         TrailingRequiresClause(nullptr),
         InventedTemplateParameterList(nullptr) {
@@ -2109,6 +2125,18 @@ class Declarator {
       Range.setEnd(SR.getEnd());
   }
 
+  /// Set this declarator as _Export.
+  void SetExport(SourceLocation Loc) {
+    ExportSpecified = true;
+    ExportLoc = Loc;
+  }
+
+  /// Whether this declarator is marked as _Export.
+  bool IsExport() const { return ExportSpecified; }
+
+  /// Get the location of the _Export keyword.
+  SourceLocation getExportLoc() const { return ExportLoc; }
+
   /// Reset the contents of this Declarator.
   void clear() {
     SS.clear();
@@ -2125,8 +2153,10 @@ class Declarator {
     HasInitializer = false;
     ObjCIvar = false;
     ObjCWeakProperty = false;
+    ExportSpecified = false;
     CommaLoc = SourceLocation();
     EllipsisLoc = SourceLocation();
+    ExportLoc = SourceLocation();
     PackIndexingExpr = nullptr;
   }
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a994b845e11fc..a973f116440ff 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4871,6 +4871,8 @@ class Sema final : public SemaBase {
                           TypeVisibilityAttr::VisibilityType Vis);
   VisibilityAttr *mergeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI,
                                       VisibilityAttr::VisibilityType Vis);
+  void mergeVisibilityType(Decl *D, SourceLocation Loc,
+                           VisibilityAttr::VisibilityType Type);
   SectionAttr *mergeSectionAttr(Decl *D, const AttributeCommonInfo &CI,
                                 StringRef Name);
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index c5ad3ef1b00f1..371623b83abd3 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -37,6 +37,10 @@ void ZOS::addClangTargetOptions(const ArgList &DriverArgs,
                                 options::OPT_fno_aligned_allocation))
     CC1Args.push_back("-faligned-alloc-unavailable");
 
+  if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,
+                         options::OPT_fvisibility_ms_compat))
+    CC1Args.push_back("-fvisibility=hidden");
+
   if (DriverArgs.hasFlag(options::OPT_fxl_pragma_pack,
                          options::OPT_fno_xl_pragma_pack, true))
     CC1Args.push_back("-fxl-pragma-pack");
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 7a87cd2e340cc..3928062079cfc 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4217,6 +4217,11 @@ void Parser::ParseDeclarationSpecifiers(
       isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
       break;
 
+    case tok::kw__Export:
+      // _Export keyword is part of the declarator id
+      goto DoneWithDeclSpec;
+      break;
+
     // friend
     case tok::kw_friend:
       if (DSContext == DeclSpecContext::DSC_class) {
@@ -6418,6 +6423,16 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
 
   tok::TokenKind Kind = Tok.getKind();
 
+  if (Kind == tok::kw__Export) {
+    SourceLocation loc = ConsumeToken();
+    D.SetExport(loc);
+    D.SetRangeEnd(loc);
+
+    if (DirectDeclParser)
+      (this->*DirectDeclParser)(D);
+    return;
+  }
+
   if (D.getDeclSpec().isTypeSpecPipe() && !isPipeDeclarator(D)) {
     DeclSpec DS(AttrFactory);
     ParseTypeQualifierListOpt(DS);
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 316bc30edf1f0..8d991ddc85e93 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1588,6 +1588,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
   // If attributes exist after tag, parse them.
   for (;;) {
     MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
+    if (Tok.is(tok::kw__Export)) {
+      SourceLocation loc = ConsumeToken();
+      DS.setExportSpec(loc);
+      continue;
+    }
+
     // Parse inheritance specifiers.
     if (Tok.isOneOf(tok::kw___single_inheritance,
                     tok::kw___multiple_inheritance,
diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp
index ee5a862c32509..e1cafc3d0f977 100644
--- a/clang/lib/Sema/DeclSpec.cpp
+++ b/clang/lib/Sema/DeclSpec.cpp
@@ -1104,6 +1104,12 @@ bool DeclSpec::setFunctionSpecNoreturn(SourceLocation Loc,
   return false;
 }
 
+bool DeclSpec::setExportSpec(SourceLocation Loc) {
+  ExportSpecified = true;
+  ExportLoc = Loc;
+  return false;
+}
+
 bool DeclSpec::SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
                              unsigned &DiagID) {
   if (isFriendSpecified()) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index cb81ac889e480..2cbb7507795a0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -5175,6 +5175,9 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
   assert(EllipsisLoc.isInvalid() &&
          "Friend ellipsis but not friend-specified?");
 
+  if (DS.isExportSpecified())
+    mergeVisibilityType(Tag, DS.getExportSpecLoc(), VisibilityAttr::Default);
+
   // Track whether this decl-specifier declares anything.
   bool DeclaresAnything = true;
 
@@ -6515,6 +6518,9 @@ NamedDecl *Sema::HandleDeclarator(Scope *S, Declarator &D,
   if (!New)
     return nullptr;
 
+  if (D.IsExport())
+    mergeVisibilityType(New, D.getExportLoc(), VisibilityAttr::Default);
+
   warnOnCTypeHiddenInCPlusPlus(New);
 
   // If this has an identifier and is not a function template specialization,
@@ -6754,6 +6760,9 @@ Sema::ActOnTypedefDeclarator(Scope* S, Declarator& D, DeclContext* DC,
     return nullptr;
   }
 
+  if (D.IsExport())
+    Diag(D.getName().StartLocation, diag::err_cannot_be_exported);
+
   TypedefDecl *NewTD = ParseTypedefDecl(S, D, TInfo->getType(), TInfo);
   if (!NewTD) return nullptr;
 
@@ -8213,6 +8222,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
 
   ProcessPragmaWeak(S, NewVD);
 
+  if (D.IsExport() && !NewVD->hasExternalFormalLinkage())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   // If this is the first declaration of an extern C variable, update
   // the map of such variables.
   if (NewVD->isFirstDecl() && !NewVD->isInvalidDecl() &&
@@ -10864,6 +10876,9 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   ProcessPragmaWeak(S, NewFD);
   checkAttributesAfterMerging(*this, *NewFD);
 
+  if (D.IsExport() && !NewFD->hasExternalFormalLinkage())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   AddKnownFunctionAttributes(NewFD);
 
   if (NewFD->hasAttr<OverloadableAttr>() &&
@@ -15423,6 +15438,9 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
   if (getLangOpts().OpenCL)
     deduceOpenCLAddressSpace(New);
 
+  if (D.IsExport())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   return New;
 }
 
@@ -19005,6 +19023,9 @@ FieldDecl *Sema::CheckFieldDecl(DeclarationName Name, QualType T,
       PPC().CheckPPCMMAType(T, NewFD->getLocation()))
     NewFD->setInvalidDecl();
 
+  if (D && D->IsExport())
+    Diag(D->getIdentifierLoc(), diag::err_cannot_be_exported);
+
   NewFD->setAccess(AS);
   return NewFD;
 }
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 4d7f0455444f1..c1bbd2127ea03 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -2632,6 +2632,15 @@ static void handleExternalSourceSymbolAttr(Sema &S, Decl *D,
       S.Context, AL, Language, DefinedIn, IsGeneratedDeclaration, USR));
 }
 
+void Sema::mergeVisibilityType(Decl *D, SourceLocation Loc,
+                               VisibilityAttr::VisibilityType Value) {
+  if (VisibilityAttr *Attr = D->getAttr<VisibilityAttr>()) {
+    if (Attr->getVisibility() != Value)
+      Diag(Loc, diag::err_mismatched_visibility);
+  } else
+    D->addAttr(VisibilityAttr::CreateImplicit(Context, Value));
+}
+
 template <class T>
 static T *mergeVisibilityAttr(Sema &S, Decl *D, const AttributeCommonInfo &CI,
                               typename T::VisibilityType value) {
diff --git a/clang/test/CodeGen/attr-export.c b/clang/test/CodeGen/attr-export.c
new file mode 100644
index 0000000000000..aae0562c1890c
--- /dev/null
+++ b/clang/test/CodeGen/attr-export.c
@@ -0,0 +1,23 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang --target=s390x-none-zos -S -emit-llvm %s -o - | FileCheck %s
+
+// Check the variables
+// CHECK: @func_ptr = global ptr null, align 8
+// CHECK: @var1 = global i32 0, align 4
+// CHECK: @var2 = hidden global i32 0, align 4
+// CHECK: @var3 = global i32 0, align 4
+// CHECK: @var4 = hidden global i32 0, align 4
+// CHECK: @var5 = global i32 0, align 4
+
+// Check the functions
+// CHECK: define void @foo1
+// CHECK: define hidden void @foo2
+
+int _Export var1;
+int var2;
+int _Export var3, var4, _Export var5;
+
+void _Export foo1(){};
+void foo2(){};
+
+int (*_Export func_ptr)(void) = 0;
diff --git a/clang/test/CodeGen/attr-export.cpp b/clang/test/CodeGen/attr-export.cpp
new file mode 100644
index 0000000000000..b5da80356ffda
--- /dev/null
+++ b/clang/test/CodeGen/attr-export.cpp
@@ -0,0 +1,59 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clangxx --target=s390x-none-zos -S -emit-llvm %s -o - | FileCheck %s
+
+// Check the variables
+// CHECK: @var1 = global i32 0, align 4
+// CHECK: @var2 = hidden global i32 0, align 4
+// CHECK: @var3 = global i32 0, align 4
+// CHECK: @var4 = hidden global i32 0, align 4
+// CHECK: @var5 = global i32 0, align 4
+// CHECK: @obj1 = global %class.class1 zeroinitializer, align 2
+// CHECK: @obj2 = hidden global %class.class1 zeroinitializer, align 2
+// CHECK: @func_ptr = global ptr null, align 8
+// CHECK: @p2m = global i64 -1, align 8
+
+// Check the functions
+// CHECK: define void @_Z4foo1v
+// CHECK: define hidden void @_Z4foo2v
+// CHECK: define void @_ZN6class13fooEv
+// CHECK: define hidden void @_ZN6class23fooEv
+// CHECK: define hidden void @_ZN6class33fooEv
+// CHECK: define void @_ZN6class33barEv
+
+int _Export var1;
+int var2;
+int _Export var3, var4, _Export var5;
+
+void _Export foo1(){};
+void foo2(){};
+
+class _Export class1 {
+public:
+  void foo();
+};
+
+class class2 {
+public:
+  void foo();
+};
+
+void class1::foo(){};
+
+void class2::foo(){};
+
+class1 _Export obj1;
+class1 obj2;
+
+class class3 {
+public:
+  int mbr;
+  void foo();
+  void _Export bar();
+};
+
+void class3::foo() {};
+void class3::bar() {};
+
+int (*_Export func_ptr)(void) = 0;
+
+int class3::* _Export p2m = 0;
diff --git a/clang/test/Sema/attr-export-failing.cpp b/clang/test/Sema/attr-export-failing.cpp
new file mode 100644
index 0000000000000..14c22c96f2f1f
--- /dev/null
+++ b/clang/test/Sema/attr-export-failing.cpp
@@ -0,0 +1,4 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -triple s390x-none-zos -fzos-extensions %s -fsyntax-only -verify
+__attribute__((visibility("hidden"))) int _Export i; // expected-error {{visibility does not match previous declaration}}
+class __attribute__((visibility("hidden"))) _Export C; // expected-error {{visibility does not match previous declaration}}
diff --git a/clang/test/Sema/zos-export.c b/clang/test/Sema/zos-export.c
new file mode 100644
index 0000000000000..64aeefd063411
--- /dev/null
+++ b/clang/test/Sema/zos-export.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple s390x-ibm-zos %s -fsyntax-only -verify
+
+typedef int _Export ty; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+ty x;
+int f(int _Export argument); //expected-error {{needs to have external linkage to be '_Export` qualified}}
+static int _Export file_scope_static; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+struct S {
+  int _Export nonstaticdatamember; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+};
+void g() {
+  int _Export automatic; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+static void _Export static_func() { //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+void _Export h() {
+  static_func();
+}
+
+void j() {
+  static int _Export sl = 0; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+int _Export file_scope;
diff --git a/clang/test/Sema/zos-export.cpp b/clang/test/Sema/zos-export.cpp
new file mode 100644
index 0000000000000..42be5327e28a5
--- /dev/null
+++ b/clang/test/Sema/zos-export.cpp
@@ -0,0 +1,44 @@
+// RUN: %clang_cc1 -triple s390x-ibm-zos %s -fsyntax-only -verify
+
+typedef int _Export ty; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+ty typedef_var;
+int f(int _Export argument); //expected-error {{needs to have external linkage to be '_Export` qualified}}
+static int _Export file_scope_static; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+struct S {
+  int _Export nonstaticdatamember; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+};
+void g() {
+  int _Export automatic; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+static void _Export static_func() { //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+void _Export h() {
+  static_func();
+}
+
+void j...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented May 21, 2025

@llvm/pr-subscribers-clang-driver

Author: Sean Perry (perry-ca)

Changes

Implement the _Export keyword that is used on z/OS to indicate that a symbol with external linkage is to be exported from the shared library. In the XL C/C++ compiler this keyword is used only in C++ source code. That is being extended to include C source as well in this PR.

This code was originally in PR #111035. I have split it out into a separate PR so the code for #pragma export is in one PR and the code for _Export keyword is in another. See that original PR for earlier comments.

For reference, the XL documentation for _Export: https://www.ibm.com/docs/en/zos/3.1.0?topic=specifiers-export-function-specifier-c-only


Patch is 20.71 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/140944.diff

18 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/Basic/Attr.td (+6)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+33)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+3)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+34-4)
  • (modified) clang/include/clang/Sema/Sema.h (+2)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+4)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+15)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+6)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+6)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+21)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+9)
  • (added) clang/test/CodeGen/attr-export.c (+23)
  • (added) clang/test/CodeGen/attr-export.cpp (+59)
  • (added) clang/test/Sema/attr-export-failing.cpp (+4)
  • (added) clang/test/Sema/zos-export.c (+25)
  • (added) clang/test/Sema/zos-export.cpp (+44)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ad8409397ff8a..8abfd60caedbc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -879,6 +879,11 @@ WebAssembly Support
 AVR Support
 ^^^^^^^^^^^
 
+SystemZ Support
+^^^^^^^^^^^^^^^
+
+- Add support for `_Export` keyword for z/OS
+
 DWARF Support in Clang
 ----------------------
 
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a6a7482a94a29..7bbaaa53e275b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4267,6 +4267,12 @@ def Thread : Attr {
   let Subjects = SubjectList<[Var]>;
 }
 
+def zOSExport : InheritableAttr {
+  let Spellings = [CustomKeyword<"_Export">];
+  let Subjects = SubjectList<[Function, Var, CXXRecord]>;
+  let Documentation = [zOSExportDocs];
+}
+
 def Win64 : IgnoredAttr {
   let Spellings = [CustomKeyword<"__w64">];
   let LangOpts = [MicrosoftExt];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 65d66dd398ad1..3beb38552df05 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -242,6 +242,39 @@ members, and static locals.
   }];
 }
 
+def zOSExportDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+Use the ``_Export`` keyword on a function or external variable to declare
+that it is to be exported (made available to other modules).  A symbol needs to be
+declared exported on or before the definition of the symbol.  The ``_Export``
+keyword must immediately precede the declaration name in the declarator.
+For example:
+
+.. code-block:: c
+
+  int _Export func(float);
+  int (*_Export funcPtr)(float);
+
+The first statement exports the function ``func``, if ``func`` is defined in the
+translation unit after this declaration.
+
+All of the static data members and member functions of a class or struct and its vague
+linkage objects (vtable, typeinfo, typeinfo name) can be exported
+by including ``_Export`` in tag of the class during the class definition or forward
+declaration of the class.
+
+.. code-block:: c++
+
+  class _Export C {
+    int func();
+  };
+
+Select members of a class can be exported by using the ``_Export`` keyword on
+declaration within the class or definition of the member.
+  }];
+}
+
 def NoEscapeDocs : Documentation {
   let Category = DocCatVariable;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 66f9480d99380..8d84d0bb698b3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9655,6 +9655,8 @@ def warn_redefine_extname_not_applied : Warning<
   "#pragma redefine_extname is applicable to external C declarations only; "
   "not applied to %select{function|variable}0 %1">,
   InGroup<Pragmas>;
+def err_cannot_be_exported : Error<
+  "needs to have external linkage to be '_Export` qualified">;
 } // End of general sema category.
 
 // inline asm.
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 94e72fea56a68..d0cb1aa2d7844 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -353,6 +353,9 @@ KEYWORD(__ptrauth                   , KEYALL)
 // C2y
 UNARY_EXPR_OR_TYPE_TRAIT(_Countof, CountOf, KEYNOCXX)
 
+// z/OS specific keywords
+KEYWORD(_Export                     , KEYZOS)
+
 // C++ 2.11p1: Keywords.
 KEYWORD(asm                         , KEYCXX|KEYGNU)
 KEYWORD(bool                        , BOOLSUPPORT|KEYC23)
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 6c4a32c4ac2f0..58a69d274a02e 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -397,6 +397,8 @@ class DeclSpec {
   unsigned FS_virtual_specified : 1;
   LLVM_PREFERRED_TYPE(bool)
   unsigned FS_noreturn_specified : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1; // z/OS extension
 
   // friend-specifier
   LLVM_PREFERRED_TYPE(bool)
@@ -443,6 +445,7 @@ class DeclSpec {
   SourceLocation FS_forceinlineLoc;
   SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
   SourceLocation TQ_pipeLoc;
+  SourceLocation ExportLoc;
 
   WrittenBuiltinSpecs writtenBS;
   void SaveWrittenBuiltinSpecs();
@@ -491,9 +494,9 @@ class DeclSpec {
         TypeSpecPipe(false), TypeSpecSat(false), ConstrainedAuto(false),
         TypeQualifiers(TQ_unspecified), FS_inline_specified(false),
         FS_forceinline_specified(false), FS_virtual_specified(false),
-        FS_noreturn_specified(false), FriendSpecifiedFirst(false),
-        ConstexprSpecifier(
-            static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
+        FS_noreturn_specified(false), ExportSpecified(false),
+        FriendSpecifiedFirst(false), ConstexprSpecifier(static_cast<unsigned>(
+                                         ConstexprSpecKind::Unspecified)),
         Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
 
   // storage-class-specifier
@@ -660,6 +663,9 @@ class DeclSpec {
   bool isNoreturnSpecified() const { return FS_noreturn_specified; }
   SourceLocation getNoreturnSpecLoc() const { return FS_noreturnLoc; }
 
+  bool isExportSpecified() const { return ExportSpecified; }
+  SourceLocation getExportSpecLoc() const { return ExportLoc; }
+
   void ClearFunctionSpecs() {
     FS_inline_specified = false;
     FS_inlineLoc = SourceLocation();
@@ -810,6 +816,8 @@ class DeclSpec {
   bool setFunctionSpecNoreturn(SourceLocation Loc, const char *&PrevSpec,
                                unsigned &DiagID);
 
+  bool setExportSpec(SourceLocation Loc);
+
   bool SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
                      unsigned &DiagID);
   bool setModulePrivateSpec(SourceLocation Loc, const char *&PrevSpec,
@@ -1955,6 +1963,10 @@ class Declarator {
   LLVM_PREFERRED_TYPE(bool)
   unsigned InlineStorageUsed : 1;
 
+  /// Indicates whether this is set as _Export.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1; // z/OS extension
+
   /// Indicates whether this declarator has an initializer.
   LLVM_PREFERRED_TYPE(bool)
   unsigned HasInitializer : 1;
@@ -2001,6 +2013,9 @@ class Declarator {
   /// this declarator as a parameter pack.
   SourceLocation EllipsisLoc;
 
+  /// The source location of the _Export keyword on this declarator.
+  SourceLocation ExportLoc;
+
   Expr *PackIndexingExpr;
 
   friend struct DeclaratorChunk;
@@ -2030,7 +2045,8 @@ class Declarator {
                                    FunctionDefinitionKind::Declaration)),
         Redeclaration(false), Extension(false), ObjCIvar(false),
         ObjCWeakProperty(false), InlineStorageUsed(false),
-        HasInitializer(false), Attrs(DS.getAttributePool().getFactory()),
+        ExportSpecified(false), HasInitializer(false),
+        Attrs(DS.getAttributePool().getFactory()),
         DeclarationAttrs(DeclarationAttrs), AsmLabel(nullptr),
         TrailingRequiresClause(nullptr),
         InventedTemplateParameterList(nullptr) {
@@ -2109,6 +2125,18 @@ class Declarator {
       Range.setEnd(SR.getEnd());
   }
 
+  /// Set this declarator as _Export.
+  void SetExport(SourceLocation Loc) {
+    ExportSpecified = true;
+    ExportLoc = Loc;
+  }
+
+  /// Whether this declarator is marked as _Export.
+  bool IsExport() const { return ExportSpecified; }
+
+  /// Get the location of the _Export keyword.
+  SourceLocation getExportLoc() const { return ExportLoc; }
+
   /// Reset the contents of this Declarator.
   void clear() {
     SS.clear();
@@ -2125,8 +2153,10 @@ class Declarator {
     HasInitializer = false;
     ObjCIvar = false;
     ObjCWeakProperty = false;
+    ExportSpecified = false;
     CommaLoc = SourceLocation();
     EllipsisLoc = SourceLocation();
+    ExportLoc = SourceLocation();
     PackIndexingExpr = nullptr;
   }
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a994b845e11fc..a973f116440ff 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4871,6 +4871,8 @@ class Sema final : public SemaBase {
                           TypeVisibilityAttr::VisibilityType Vis);
   VisibilityAttr *mergeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI,
                                       VisibilityAttr::VisibilityType Vis);
+  void mergeVisibilityType(Decl *D, SourceLocation Loc,
+                           VisibilityAttr::VisibilityType Type);
   SectionAttr *mergeSectionAttr(Decl *D, const AttributeCommonInfo &CI,
                                 StringRef Name);
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index c5ad3ef1b00f1..371623b83abd3 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -37,6 +37,10 @@ void ZOS::addClangTargetOptions(const ArgList &DriverArgs,
                                 options::OPT_fno_aligned_allocation))
     CC1Args.push_back("-faligned-alloc-unavailable");
 
+  if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,
+                         options::OPT_fvisibility_ms_compat))
+    CC1Args.push_back("-fvisibility=hidden");
+
   if (DriverArgs.hasFlag(options::OPT_fxl_pragma_pack,
                          options::OPT_fno_xl_pragma_pack, true))
     CC1Args.push_back("-fxl-pragma-pack");
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 7a87cd2e340cc..3928062079cfc 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4217,6 +4217,11 @@ void Parser::ParseDeclarationSpecifiers(
       isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
       break;
 
+    case tok::kw__Export:
+      // _Export keyword is part of the declarator id
+      goto DoneWithDeclSpec;
+      break;
+
     // friend
     case tok::kw_friend:
       if (DSContext == DeclSpecContext::DSC_class) {
@@ -6418,6 +6423,16 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
 
   tok::TokenKind Kind = Tok.getKind();
 
+  if (Kind == tok::kw__Export) {
+    SourceLocation loc = ConsumeToken();
+    D.SetExport(loc);
+    D.SetRangeEnd(loc);
+
+    if (DirectDeclParser)
+      (this->*DirectDeclParser)(D);
+    return;
+  }
+
   if (D.getDeclSpec().isTypeSpecPipe() && !isPipeDeclarator(D)) {
     DeclSpec DS(AttrFactory);
     ParseTypeQualifierListOpt(DS);
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 316bc30edf1f0..8d991ddc85e93 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1588,6 +1588,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
   // If attributes exist after tag, parse them.
   for (;;) {
     MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
+    if (Tok.is(tok::kw__Export)) {
+      SourceLocation loc = ConsumeToken();
+      DS.setExportSpec(loc);
+      continue;
+    }
+
     // Parse inheritance specifiers.
     if (Tok.isOneOf(tok::kw___single_inheritance,
                     tok::kw___multiple_inheritance,
diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp
index ee5a862c32509..e1cafc3d0f977 100644
--- a/clang/lib/Sema/DeclSpec.cpp
+++ b/clang/lib/Sema/DeclSpec.cpp
@@ -1104,6 +1104,12 @@ bool DeclSpec::setFunctionSpecNoreturn(SourceLocation Loc,
   return false;
 }
 
+bool DeclSpec::setExportSpec(SourceLocation Loc) {
+  ExportSpecified = true;
+  ExportLoc = Loc;
+  return false;
+}
+
 bool DeclSpec::SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
                              unsigned &DiagID) {
   if (isFriendSpecified()) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index cb81ac889e480..2cbb7507795a0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -5175,6 +5175,9 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
   assert(EllipsisLoc.isInvalid() &&
          "Friend ellipsis but not friend-specified?");
 
+  if (DS.isExportSpecified())
+    mergeVisibilityType(Tag, DS.getExportSpecLoc(), VisibilityAttr::Default);
+
   // Track whether this decl-specifier declares anything.
   bool DeclaresAnything = true;
 
@@ -6515,6 +6518,9 @@ NamedDecl *Sema::HandleDeclarator(Scope *S, Declarator &D,
   if (!New)
     return nullptr;
 
+  if (D.IsExport())
+    mergeVisibilityType(New, D.getExportLoc(), VisibilityAttr::Default);
+
   warnOnCTypeHiddenInCPlusPlus(New);
 
   // If this has an identifier and is not a function template specialization,
@@ -6754,6 +6760,9 @@ Sema::ActOnTypedefDeclarator(Scope* S, Declarator& D, DeclContext* DC,
     return nullptr;
   }
 
+  if (D.IsExport())
+    Diag(D.getName().StartLocation, diag::err_cannot_be_exported);
+
   TypedefDecl *NewTD = ParseTypedefDecl(S, D, TInfo->getType(), TInfo);
   if (!NewTD) return nullptr;
 
@@ -8213,6 +8222,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
 
   ProcessPragmaWeak(S, NewVD);
 
+  if (D.IsExport() && !NewVD->hasExternalFormalLinkage())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   // If this is the first declaration of an extern C variable, update
   // the map of such variables.
   if (NewVD->isFirstDecl() && !NewVD->isInvalidDecl() &&
@@ -10864,6 +10876,9 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   ProcessPragmaWeak(S, NewFD);
   checkAttributesAfterMerging(*this, *NewFD);
 
+  if (D.IsExport() && !NewFD->hasExternalFormalLinkage())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   AddKnownFunctionAttributes(NewFD);
 
   if (NewFD->hasAttr<OverloadableAttr>() &&
@@ -15423,6 +15438,9 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
   if (getLangOpts().OpenCL)
     deduceOpenCLAddressSpace(New);
 
+  if (D.IsExport())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   return New;
 }
 
@@ -19005,6 +19023,9 @@ FieldDecl *Sema::CheckFieldDecl(DeclarationName Name, QualType T,
       PPC().CheckPPCMMAType(T, NewFD->getLocation()))
     NewFD->setInvalidDecl();
 
+  if (D && D->IsExport())
+    Diag(D->getIdentifierLoc(), diag::err_cannot_be_exported);
+
   NewFD->setAccess(AS);
   return NewFD;
 }
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 4d7f0455444f1..c1bbd2127ea03 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -2632,6 +2632,15 @@ static void handleExternalSourceSymbolAttr(Sema &S, Decl *D,
       S.Context, AL, Language, DefinedIn, IsGeneratedDeclaration, USR));
 }
 
+void Sema::mergeVisibilityType(Decl *D, SourceLocation Loc,
+                               VisibilityAttr::VisibilityType Value) {
+  if (VisibilityAttr *Attr = D->getAttr<VisibilityAttr>()) {
+    if (Attr->getVisibility() != Value)
+      Diag(Loc, diag::err_mismatched_visibility);
+  } else
+    D->addAttr(VisibilityAttr::CreateImplicit(Context, Value));
+}
+
 template <class T>
 static T *mergeVisibilityAttr(Sema &S, Decl *D, const AttributeCommonInfo &CI,
                               typename T::VisibilityType value) {
diff --git a/clang/test/CodeGen/attr-export.c b/clang/test/CodeGen/attr-export.c
new file mode 100644
index 0000000000000..aae0562c1890c
--- /dev/null
+++ b/clang/test/CodeGen/attr-export.c
@@ -0,0 +1,23 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang --target=s390x-none-zos -S -emit-llvm %s -o - | FileCheck %s
+
+// Check the variables
+// CHECK: @func_ptr = global ptr null, align 8
+// CHECK: @var1 = global i32 0, align 4
+// CHECK: @var2 = hidden global i32 0, align 4
+// CHECK: @var3 = global i32 0, align 4
+// CHECK: @var4 = hidden global i32 0, align 4
+// CHECK: @var5 = global i32 0, align 4
+
+// Check the functions
+// CHECK: define void @foo1
+// CHECK: define hidden void @foo2
+
+int _Export var1;
+int var2;
+int _Export var3, var4, _Export var5;
+
+void _Export foo1(){};
+void foo2(){};
+
+int (*_Export func_ptr)(void) = 0;
diff --git a/clang/test/CodeGen/attr-export.cpp b/clang/test/CodeGen/attr-export.cpp
new file mode 100644
index 0000000000000..b5da80356ffda
--- /dev/null
+++ b/clang/test/CodeGen/attr-export.cpp
@@ -0,0 +1,59 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clangxx --target=s390x-none-zos -S -emit-llvm %s -o - | FileCheck %s
+
+// Check the variables
+// CHECK: @var1 = global i32 0, align 4
+// CHECK: @var2 = hidden global i32 0, align 4
+// CHECK: @var3 = global i32 0, align 4
+// CHECK: @var4 = hidden global i32 0, align 4
+// CHECK: @var5 = global i32 0, align 4
+// CHECK: @obj1 = global %class.class1 zeroinitializer, align 2
+// CHECK: @obj2 = hidden global %class.class1 zeroinitializer, align 2
+// CHECK: @func_ptr = global ptr null, align 8
+// CHECK: @p2m = global i64 -1, align 8
+
+// Check the functions
+// CHECK: define void @_Z4foo1v
+// CHECK: define hidden void @_Z4foo2v
+// CHECK: define void @_ZN6class13fooEv
+// CHECK: define hidden void @_ZN6class23fooEv
+// CHECK: define hidden void @_ZN6class33fooEv
+// CHECK: define void @_ZN6class33barEv
+
+int _Export var1;
+int var2;
+int _Export var3, var4, _Export var5;
+
+void _Export foo1(){};
+void foo2(){};
+
+class _Export class1 {
+public:
+  void foo();
+};
+
+class class2 {
+public:
+  void foo();
+};
+
+void class1::foo(){};
+
+void class2::foo(){};
+
+class1 _Export obj1;
+class1 obj2;
+
+class class3 {
+public:
+  int mbr;
+  void foo();
+  void _Export bar();
+};
+
+void class3::foo() {};
+void class3::bar() {};
+
+int (*_Export func_ptr)(void) = 0;
+
+int class3::* _Export p2m = 0;
diff --git a/clang/test/Sema/attr-export-failing.cpp b/clang/test/Sema/attr-export-failing.cpp
new file mode 100644
index 0000000000000..14c22c96f2f1f
--- /dev/null
+++ b/clang/test/Sema/attr-export-failing.cpp
@@ -0,0 +1,4 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -triple s390x-none-zos -fzos-extensions %s -fsyntax-only -verify
+__attribute__((visibility("hidden"))) int _Export i; // expected-error {{visibility does not match previous declaration}}
+class __attribute__((visibility("hidden"))) _Export C; // expected-error {{visibility does not match previous declaration}}
diff --git a/clang/test/Sema/zos-export.c b/clang/test/Sema/zos-export.c
new file mode 100644
index 0000000000000..64aeefd063411
--- /dev/null
+++ b/clang/test/Sema/zos-export.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple s390x-ibm-zos %s -fsyntax-only -verify
+
+typedef int _Export ty; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+ty x;
+int f(int _Export argument); //expected-error {{needs to have external linkage to be '_Export` qualified}}
+static int _Export file_scope_static; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+struct S {
+  int _Export nonstaticdatamember; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+};
+void g() {
+  int _Export automatic; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+static void _Export static_func() { //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+void _Export h() {
+  static_func();
+}
+
+void j() {
+  static int _Export sl = 0; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+int _Export file_scope;
diff --git a/clang/test/Sema/zos-export.cpp b/clang/test/Sema/zos-export.cpp
new file mode 100644
index 0000000000000..42be5327e28a5
--- /dev/null
+++ b/clang/test/Sema/zos-export.cpp
@@ -0,0 +1,44 @@
+// RUN: %clang_cc1 -triple s390x-ibm-zos %s -fsyntax-only -verify
+
+typedef int _Export ty; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+ty typedef_var;
+int f(int _Export argument); //expected-error {{needs to have external linkage to be '_Export` qualified}}
+static int _Export file_scope_static; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+struct S {
+  int _Export nonstaticdatamember; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+};
+void g() {
+  int _Export automatic; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+static void _Export static_func() { //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+void _Export h() {
+  static_func();
+}
+
+void j...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented May 21, 2025

@llvm/pr-subscribers-backend-systemz

Author: Sean Perry (perry-ca)

Changes

Implement the _Export keyword that is used on z/OS to indicate that a symbol with external linkage is to be exported from the shared library. In the XL C/C++ compiler this keyword is used only in C++ source code. That is being extended to include C source as well in this PR.

This code was originally in PR #111035. I have split it out into a separate PR so the code for #pragma export is in one PR and the code for _Export keyword is in another. See that original PR for earlier comments.

For reference, the XL documentation for _Export: https://www.ibm.com/docs/en/zos/3.1.0?topic=specifiers-export-function-specifier-c-only


Patch is 20.71 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/140944.diff

18 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/Basic/Attr.td (+6)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+33)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+3)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+34-4)
  • (modified) clang/include/clang/Sema/Sema.h (+2)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+4)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+15)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+6)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+6)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+21)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+9)
  • (added) clang/test/CodeGen/attr-export.c (+23)
  • (added) clang/test/CodeGen/attr-export.cpp (+59)
  • (added) clang/test/Sema/attr-export-failing.cpp (+4)
  • (added) clang/test/Sema/zos-export.c (+25)
  • (added) clang/test/Sema/zos-export.cpp (+44)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ad8409397ff8a..8abfd60caedbc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -879,6 +879,11 @@ WebAssembly Support
 AVR Support
 ^^^^^^^^^^^
 
+SystemZ Support
+^^^^^^^^^^^^^^^
+
+- Add support for `_Export` keyword for z/OS
+
 DWARF Support in Clang
 ----------------------
 
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a6a7482a94a29..7bbaaa53e275b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4267,6 +4267,12 @@ def Thread : Attr {
   let Subjects = SubjectList<[Var]>;
 }
 
+def zOSExport : InheritableAttr {
+  let Spellings = [CustomKeyword<"_Export">];
+  let Subjects = SubjectList<[Function, Var, CXXRecord]>;
+  let Documentation = [zOSExportDocs];
+}
+
 def Win64 : IgnoredAttr {
   let Spellings = [CustomKeyword<"__w64">];
   let LangOpts = [MicrosoftExt];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 65d66dd398ad1..3beb38552df05 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -242,6 +242,39 @@ members, and static locals.
   }];
 }
 
+def zOSExportDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+Use the ``_Export`` keyword on a function or external variable to declare
+that it is to be exported (made available to other modules).  A symbol needs to be
+declared exported on or before the definition of the symbol.  The ``_Export``
+keyword must immediately precede the declaration name in the declarator.
+For example:
+
+.. code-block:: c
+
+  int _Export func(float);
+  int (*_Export funcPtr)(float);
+
+The first statement exports the function ``func``, if ``func`` is defined in the
+translation unit after this declaration.
+
+All of the static data members and member functions of a class or struct and its vague
+linkage objects (vtable, typeinfo, typeinfo name) can be exported
+by including ``_Export`` in tag of the class during the class definition or forward
+declaration of the class.
+
+.. code-block:: c++
+
+  class _Export C {
+    int func();
+  };
+
+Select members of a class can be exported by using the ``_Export`` keyword on
+declaration within the class or definition of the member.
+  }];
+}
+
 def NoEscapeDocs : Documentation {
   let Category = DocCatVariable;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 66f9480d99380..8d84d0bb698b3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9655,6 +9655,8 @@ def warn_redefine_extname_not_applied : Warning<
   "#pragma redefine_extname is applicable to external C declarations only; "
   "not applied to %select{function|variable}0 %1">,
   InGroup<Pragmas>;
+def err_cannot_be_exported : Error<
+  "needs to have external linkage to be '_Export` qualified">;
 } // End of general sema category.
 
 // inline asm.
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 94e72fea56a68..d0cb1aa2d7844 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -353,6 +353,9 @@ KEYWORD(__ptrauth                   , KEYALL)
 // C2y
 UNARY_EXPR_OR_TYPE_TRAIT(_Countof, CountOf, KEYNOCXX)
 
+// z/OS specific keywords
+KEYWORD(_Export                     , KEYZOS)
+
 // C++ 2.11p1: Keywords.
 KEYWORD(asm                         , KEYCXX|KEYGNU)
 KEYWORD(bool                        , BOOLSUPPORT|KEYC23)
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 6c4a32c4ac2f0..58a69d274a02e 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -397,6 +397,8 @@ class DeclSpec {
   unsigned FS_virtual_specified : 1;
   LLVM_PREFERRED_TYPE(bool)
   unsigned FS_noreturn_specified : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1; // z/OS extension
 
   // friend-specifier
   LLVM_PREFERRED_TYPE(bool)
@@ -443,6 +445,7 @@ class DeclSpec {
   SourceLocation FS_forceinlineLoc;
   SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
   SourceLocation TQ_pipeLoc;
+  SourceLocation ExportLoc;
 
   WrittenBuiltinSpecs writtenBS;
   void SaveWrittenBuiltinSpecs();
@@ -491,9 +494,9 @@ class DeclSpec {
         TypeSpecPipe(false), TypeSpecSat(false), ConstrainedAuto(false),
         TypeQualifiers(TQ_unspecified), FS_inline_specified(false),
         FS_forceinline_specified(false), FS_virtual_specified(false),
-        FS_noreturn_specified(false), FriendSpecifiedFirst(false),
-        ConstexprSpecifier(
-            static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
+        FS_noreturn_specified(false), ExportSpecified(false),
+        FriendSpecifiedFirst(false), ConstexprSpecifier(static_cast<unsigned>(
+                                         ConstexprSpecKind::Unspecified)),
         Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
 
   // storage-class-specifier
@@ -660,6 +663,9 @@ class DeclSpec {
   bool isNoreturnSpecified() const { return FS_noreturn_specified; }
   SourceLocation getNoreturnSpecLoc() const { return FS_noreturnLoc; }
 
+  bool isExportSpecified() const { return ExportSpecified; }
+  SourceLocation getExportSpecLoc() const { return ExportLoc; }
+
   void ClearFunctionSpecs() {
     FS_inline_specified = false;
     FS_inlineLoc = SourceLocation();
@@ -810,6 +816,8 @@ class DeclSpec {
   bool setFunctionSpecNoreturn(SourceLocation Loc, const char *&PrevSpec,
                                unsigned &DiagID);
 
+  bool setExportSpec(SourceLocation Loc);
+
   bool SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
                      unsigned &DiagID);
   bool setModulePrivateSpec(SourceLocation Loc, const char *&PrevSpec,
@@ -1955,6 +1963,10 @@ class Declarator {
   LLVM_PREFERRED_TYPE(bool)
   unsigned InlineStorageUsed : 1;
 
+  /// Indicates whether this is set as _Export.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1; // z/OS extension
+
   /// Indicates whether this declarator has an initializer.
   LLVM_PREFERRED_TYPE(bool)
   unsigned HasInitializer : 1;
@@ -2001,6 +2013,9 @@ class Declarator {
   /// this declarator as a parameter pack.
   SourceLocation EllipsisLoc;
 
+  /// The source location of the _Export keyword on this declarator.
+  SourceLocation ExportLoc;
+
   Expr *PackIndexingExpr;
 
   friend struct DeclaratorChunk;
@@ -2030,7 +2045,8 @@ class Declarator {
                                    FunctionDefinitionKind::Declaration)),
         Redeclaration(false), Extension(false), ObjCIvar(false),
         ObjCWeakProperty(false), InlineStorageUsed(false),
-        HasInitializer(false), Attrs(DS.getAttributePool().getFactory()),
+        ExportSpecified(false), HasInitializer(false),
+        Attrs(DS.getAttributePool().getFactory()),
         DeclarationAttrs(DeclarationAttrs), AsmLabel(nullptr),
         TrailingRequiresClause(nullptr),
         InventedTemplateParameterList(nullptr) {
@@ -2109,6 +2125,18 @@ class Declarator {
       Range.setEnd(SR.getEnd());
   }
 
+  /// Set this declarator as _Export.
+  void SetExport(SourceLocation Loc) {
+    ExportSpecified = true;
+    ExportLoc = Loc;
+  }
+
+  /// Whether this declarator is marked as _Export.
+  bool IsExport() const { return ExportSpecified; }
+
+  /// Get the location of the _Export keyword.
+  SourceLocation getExportLoc() const { return ExportLoc; }
+
   /// Reset the contents of this Declarator.
   void clear() {
     SS.clear();
@@ -2125,8 +2153,10 @@ class Declarator {
     HasInitializer = false;
     ObjCIvar = false;
     ObjCWeakProperty = false;
+    ExportSpecified = false;
     CommaLoc = SourceLocation();
     EllipsisLoc = SourceLocation();
+    ExportLoc = SourceLocation();
     PackIndexingExpr = nullptr;
   }
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a994b845e11fc..a973f116440ff 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4871,6 +4871,8 @@ class Sema final : public SemaBase {
                           TypeVisibilityAttr::VisibilityType Vis);
   VisibilityAttr *mergeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI,
                                       VisibilityAttr::VisibilityType Vis);
+  void mergeVisibilityType(Decl *D, SourceLocation Loc,
+                           VisibilityAttr::VisibilityType Type);
   SectionAttr *mergeSectionAttr(Decl *D, const AttributeCommonInfo &CI,
                                 StringRef Name);
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index c5ad3ef1b00f1..371623b83abd3 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -37,6 +37,10 @@ void ZOS::addClangTargetOptions(const ArgList &DriverArgs,
                                 options::OPT_fno_aligned_allocation))
     CC1Args.push_back("-faligned-alloc-unavailable");
 
+  if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,
+                         options::OPT_fvisibility_ms_compat))
+    CC1Args.push_back("-fvisibility=hidden");
+
   if (DriverArgs.hasFlag(options::OPT_fxl_pragma_pack,
                          options::OPT_fno_xl_pragma_pack, true))
     CC1Args.push_back("-fxl-pragma-pack");
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 7a87cd2e340cc..3928062079cfc 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4217,6 +4217,11 @@ void Parser::ParseDeclarationSpecifiers(
       isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
       break;
 
+    case tok::kw__Export:
+      // _Export keyword is part of the declarator id
+      goto DoneWithDeclSpec;
+      break;
+
     // friend
     case tok::kw_friend:
       if (DSContext == DeclSpecContext::DSC_class) {
@@ -6418,6 +6423,16 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
 
   tok::TokenKind Kind = Tok.getKind();
 
+  if (Kind == tok::kw__Export) {
+    SourceLocation loc = ConsumeToken();
+    D.SetExport(loc);
+    D.SetRangeEnd(loc);
+
+    if (DirectDeclParser)
+      (this->*DirectDeclParser)(D);
+    return;
+  }
+
   if (D.getDeclSpec().isTypeSpecPipe() && !isPipeDeclarator(D)) {
     DeclSpec DS(AttrFactory);
     ParseTypeQualifierListOpt(DS);
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 316bc30edf1f0..8d991ddc85e93 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1588,6 +1588,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
   // If attributes exist after tag, parse them.
   for (;;) {
     MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
+    if (Tok.is(tok::kw__Export)) {
+      SourceLocation loc = ConsumeToken();
+      DS.setExportSpec(loc);
+      continue;
+    }
+
     // Parse inheritance specifiers.
     if (Tok.isOneOf(tok::kw___single_inheritance,
                     tok::kw___multiple_inheritance,
diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp
index ee5a862c32509..e1cafc3d0f977 100644
--- a/clang/lib/Sema/DeclSpec.cpp
+++ b/clang/lib/Sema/DeclSpec.cpp
@@ -1104,6 +1104,12 @@ bool DeclSpec::setFunctionSpecNoreturn(SourceLocation Loc,
   return false;
 }
 
+bool DeclSpec::setExportSpec(SourceLocation Loc) {
+  ExportSpecified = true;
+  ExportLoc = Loc;
+  return false;
+}
+
 bool DeclSpec::SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
                              unsigned &DiagID) {
   if (isFriendSpecified()) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index cb81ac889e480..2cbb7507795a0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -5175,6 +5175,9 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS,
   assert(EllipsisLoc.isInvalid() &&
          "Friend ellipsis but not friend-specified?");
 
+  if (DS.isExportSpecified())
+    mergeVisibilityType(Tag, DS.getExportSpecLoc(), VisibilityAttr::Default);
+
   // Track whether this decl-specifier declares anything.
   bool DeclaresAnything = true;
 
@@ -6515,6 +6518,9 @@ NamedDecl *Sema::HandleDeclarator(Scope *S, Declarator &D,
   if (!New)
     return nullptr;
 
+  if (D.IsExport())
+    mergeVisibilityType(New, D.getExportLoc(), VisibilityAttr::Default);
+
   warnOnCTypeHiddenInCPlusPlus(New);
 
   // If this has an identifier and is not a function template specialization,
@@ -6754,6 +6760,9 @@ Sema::ActOnTypedefDeclarator(Scope* S, Declarator& D, DeclContext* DC,
     return nullptr;
   }
 
+  if (D.IsExport())
+    Diag(D.getName().StartLocation, diag::err_cannot_be_exported);
+
   TypedefDecl *NewTD = ParseTypedefDecl(S, D, TInfo->getType(), TInfo);
   if (!NewTD) return nullptr;
 
@@ -8213,6 +8222,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
 
   ProcessPragmaWeak(S, NewVD);
 
+  if (D.IsExport() && !NewVD->hasExternalFormalLinkage())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   // If this is the first declaration of an extern C variable, update
   // the map of such variables.
   if (NewVD->isFirstDecl() && !NewVD->isInvalidDecl() &&
@@ -10864,6 +10876,9 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   ProcessPragmaWeak(S, NewFD);
   checkAttributesAfterMerging(*this, *NewFD);
 
+  if (D.IsExport() && !NewFD->hasExternalFormalLinkage())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   AddKnownFunctionAttributes(NewFD);
 
   if (NewFD->hasAttr<OverloadableAttr>() &&
@@ -15423,6 +15438,9 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
   if (getLangOpts().OpenCL)
     deduceOpenCLAddressSpace(New);
 
+  if (D.IsExport())
+    Diag(D.getIdentifierLoc(), diag::err_cannot_be_exported);
+
   return New;
 }
 
@@ -19005,6 +19023,9 @@ FieldDecl *Sema::CheckFieldDecl(DeclarationName Name, QualType T,
       PPC().CheckPPCMMAType(T, NewFD->getLocation()))
     NewFD->setInvalidDecl();
 
+  if (D && D->IsExport())
+    Diag(D->getIdentifierLoc(), diag::err_cannot_be_exported);
+
   NewFD->setAccess(AS);
   return NewFD;
 }
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 4d7f0455444f1..c1bbd2127ea03 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -2632,6 +2632,15 @@ static void handleExternalSourceSymbolAttr(Sema &S, Decl *D,
       S.Context, AL, Language, DefinedIn, IsGeneratedDeclaration, USR));
 }
 
+void Sema::mergeVisibilityType(Decl *D, SourceLocation Loc,
+                               VisibilityAttr::VisibilityType Value) {
+  if (VisibilityAttr *Attr = D->getAttr<VisibilityAttr>()) {
+    if (Attr->getVisibility() != Value)
+      Diag(Loc, diag::err_mismatched_visibility);
+  } else
+    D->addAttr(VisibilityAttr::CreateImplicit(Context, Value));
+}
+
 template <class T>
 static T *mergeVisibilityAttr(Sema &S, Decl *D, const AttributeCommonInfo &CI,
                               typename T::VisibilityType value) {
diff --git a/clang/test/CodeGen/attr-export.c b/clang/test/CodeGen/attr-export.c
new file mode 100644
index 0000000000000..aae0562c1890c
--- /dev/null
+++ b/clang/test/CodeGen/attr-export.c
@@ -0,0 +1,23 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang --target=s390x-none-zos -S -emit-llvm %s -o - | FileCheck %s
+
+// Check the variables
+// CHECK: @func_ptr = global ptr null, align 8
+// CHECK: @var1 = global i32 0, align 4
+// CHECK: @var2 = hidden global i32 0, align 4
+// CHECK: @var3 = global i32 0, align 4
+// CHECK: @var4 = hidden global i32 0, align 4
+// CHECK: @var5 = global i32 0, align 4
+
+// Check the functions
+// CHECK: define void @foo1
+// CHECK: define hidden void @foo2
+
+int _Export var1;
+int var2;
+int _Export var3, var4, _Export var5;
+
+void _Export foo1(){};
+void foo2(){};
+
+int (*_Export func_ptr)(void) = 0;
diff --git a/clang/test/CodeGen/attr-export.cpp b/clang/test/CodeGen/attr-export.cpp
new file mode 100644
index 0000000000000..b5da80356ffda
--- /dev/null
+++ b/clang/test/CodeGen/attr-export.cpp
@@ -0,0 +1,59 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clangxx --target=s390x-none-zos -S -emit-llvm %s -o - | FileCheck %s
+
+// Check the variables
+// CHECK: @var1 = global i32 0, align 4
+// CHECK: @var2 = hidden global i32 0, align 4
+// CHECK: @var3 = global i32 0, align 4
+// CHECK: @var4 = hidden global i32 0, align 4
+// CHECK: @var5 = global i32 0, align 4
+// CHECK: @obj1 = global %class.class1 zeroinitializer, align 2
+// CHECK: @obj2 = hidden global %class.class1 zeroinitializer, align 2
+// CHECK: @func_ptr = global ptr null, align 8
+// CHECK: @p2m = global i64 -1, align 8
+
+// Check the functions
+// CHECK: define void @_Z4foo1v
+// CHECK: define hidden void @_Z4foo2v
+// CHECK: define void @_ZN6class13fooEv
+// CHECK: define hidden void @_ZN6class23fooEv
+// CHECK: define hidden void @_ZN6class33fooEv
+// CHECK: define void @_ZN6class33barEv
+
+int _Export var1;
+int var2;
+int _Export var3, var4, _Export var5;
+
+void _Export foo1(){};
+void foo2(){};
+
+class _Export class1 {
+public:
+  void foo();
+};
+
+class class2 {
+public:
+  void foo();
+};
+
+void class1::foo(){};
+
+void class2::foo(){};
+
+class1 _Export obj1;
+class1 obj2;
+
+class class3 {
+public:
+  int mbr;
+  void foo();
+  void _Export bar();
+};
+
+void class3::foo() {};
+void class3::bar() {};
+
+int (*_Export func_ptr)(void) = 0;
+
+int class3::* _Export p2m = 0;
diff --git a/clang/test/Sema/attr-export-failing.cpp b/clang/test/Sema/attr-export-failing.cpp
new file mode 100644
index 0000000000000..14c22c96f2f1f
--- /dev/null
+++ b/clang/test/Sema/attr-export-failing.cpp
@@ -0,0 +1,4 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -triple s390x-none-zos -fzos-extensions %s -fsyntax-only -verify
+__attribute__((visibility("hidden"))) int _Export i; // expected-error {{visibility does not match previous declaration}}
+class __attribute__((visibility("hidden"))) _Export C; // expected-error {{visibility does not match previous declaration}}
diff --git a/clang/test/Sema/zos-export.c b/clang/test/Sema/zos-export.c
new file mode 100644
index 0000000000000..64aeefd063411
--- /dev/null
+++ b/clang/test/Sema/zos-export.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple s390x-ibm-zos %s -fsyntax-only -verify
+
+typedef int _Export ty; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+ty x;
+int f(int _Export argument); //expected-error {{needs to have external linkage to be '_Export` qualified}}
+static int _Export file_scope_static; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+struct S {
+  int _Export nonstaticdatamember; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+};
+void g() {
+  int _Export automatic; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+static void _Export static_func() { //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+void _Export h() {
+  static_func();
+}
+
+void j() {
+  static int _Export sl = 0; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+int _Export file_scope;
diff --git a/clang/test/Sema/zos-export.cpp b/clang/test/Sema/zos-export.cpp
new file mode 100644
index 0000000000000..42be5327e28a5
--- /dev/null
+++ b/clang/test/Sema/zos-export.cpp
@@ -0,0 +1,44 @@
+// RUN: %clang_cc1 -triple s390x-ibm-zos %s -fsyntax-only -verify
+
+typedef int _Export ty; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+ty typedef_var;
+int f(int _Export argument); //expected-error {{needs to have external linkage to be '_Export` qualified}}
+static int _Export file_scope_static; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+struct S {
+  int _Export nonstaticdatamember; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+};
+void g() {
+  int _Export automatic; //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+static void _Export static_func() { //expected-error {{needs to have external linkage to be '_Export` qualified}}
+}
+
+void _Export h() {
+  static_func();
+}
+
+void j...
[truncated]

@hubert-reinterpretcast
Copy link
Collaborator

For reference, the XL documentation for _Export: https://www.ibm.com/docs/en/zos/3.1.0?topic=specifiers-export-function-specifier-c-only

@perry-ca, additional context from https://www.ibm.com/docs/en/zos/3.1.0?topic=qualifiers-export-qualifier-c-only would help to cover the case for non-functions. I suggest to update the PR description to also add that.

Copy link
Collaborator

@hubert-reinterpretcast hubert-reinterpretcast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not seeing any syntax tests for _Export in disallowed positions.
For example:

int _Export *x = 0;

It's not the end of the world if it happens to be accepted, but it is potentially worth noting.

@perry-ca
Copy link
Contributor Author

I am not seeing any syntax tests for _Export in disallowed positions. For example:

int _Export *x = 0;

It's not the end of the world if it happens to be accepted, but it is potentially worth noting.

I'll add a few tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:SystemZ clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants