Skip to content

[SystemZ][z/OS] Add visibility features for z/OS (eg. _Export, pragma export) #111035

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 32 commits into
base: main
Choose a base branch
from

Conversation

perry-ca
Copy link
Contributor

@perry-ca perry-ca commented Oct 3, 2024

The z/OS operating system uses the linker to control what symbols are exported from shared libraries. The compilation step sets a bit on each symbol that should be exported from a shared library and then the linker marks these as exported from the shared library. The linker also produces what is called a side deck that is a list of all the exported symbols. This is then used as an import file during the link process for programs (or other shared libraries) that link against a shared library. The default for compilation is to only export symbols that the user wants to export.

Control over what is exported is done by:

  • making the default for visibility to be hidden
  • using the _Export keyword or #pragma export to indicate what symbols should be exported. These are features the existing compilers on z/OS use.
  • using attribute((visibility,"default")) to mark the symbol as exported.

Note: Parser::HandlePragmaExport() has an extra layer of call to parse the pragma. We have some more pragmas that have very similar syntax. I'll be upstreaming those next. It is easier to leave that layer in than to remove it for this PR and then add it back.

@perry-ca perry-ca requested a review from Endilll as a code owner October 3, 2024 18:01
@perry-ca perry-ca self-assigned this Oct 3, 2024
@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" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Oct 3, 2024
@perry-ca perry-ca requested a review from abhina-sree October 3, 2024 18:01
@llvmbot
Copy link
Member

llvmbot commented Oct 3, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-driver

Author: Sean Perry (perry-ca)

Changes

The z/OS operating system uses the linker to control what symbols are exported from shared libraries. The compilation step sets a bit on each symbol that should be exported from a shared library and then the linker marks these as exported from the shared library. The linker also produces what is called a side deck that is a list of all the exported symbols. This is then used as an import file during the link process for programs (or other shared libraries) that link against a shared library. The default for compilation is to only export symbols that the user wants to export.

Control over what is exported is done by:

  • making the default for visibility to be hidden
  • using the _Export keyword or #pragma export to indicate what symbols should be exported. These are features the existing compilers on z/OS use.
  • using attribute((visibility,"default")) to mark the symbol as exported.

Note: Parser::HandlePragmaExport() has an extra layer of call to parse the pragma. We have some more pragmas that have very similar syntax. I'll be upstreaming those next. It is easier to leave that layer in than to remove it for this PR and then add it back.


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

28 Files Affected:

  • (modified) clang/include/clang/AST/ASTConsumer.h (+3)
  • (modified) clang/include/clang/Basic/Attr.td (+6)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+21)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+5)
  • (modified) clang/include/clang/Parse/Parser.h (+13)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+33-3)
  • (modified) clang/include/clang/Sema/Sema.h (+30)
  • (modified) clang/lib/CodeGen/BackendConsumer.h (+1)
  • (modified) clang/lib/CodeGen/CodeGenAction.cpp (+4)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+15)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+2)
  • (modified) clang/lib/CodeGen/ModuleBuilder.cpp (+5-1)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+8-4)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+18)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+6)
  • (modified) clang/lib/Parse/ParsePragma.cpp (+213)
  • (modified) clang/lib/Parse/Parser.cpp (+3)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+6)
  • (modified) clang/lib/Sema/Sema.cpp (+21)
  • (modified) clang/lib/Sema/SemaAttr.cpp (+162)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+25)
  • (added) clang/test/CodeGen/attr-export-failing.cpp (+4)
  • (added) clang/test/CodeGen/attr-export.cpp (+51)
  • (added) clang/test/CodeGen/pragma-export.c (+39)
  • (added) clang/test/CodeGen/pragma-export.cpp (+82)
  • (added) clang/test/CodeGen/zos-pragmas.c (+11)
  • (added) clang/test/CodeGen/zos-pragmas.cpp (+11)
diff --git a/clang/include/clang/AST/ASTConsumer.h b/clang/include/clang/AST/ASTConsumer.h
index 447f2592d23595..b15d53e700c679 100644
--- a/clang/include/clang/AST/ASTConsumer.h
+++ b/clang/include/clang/AST/ASTConsumer.h
@@ -108,6 +108,9 @@ class ASTConsumer {
   /// completed.
   virtual void CompleteExternalDeclaration(DeclaratorDecl *D) {}
 
+  /// CompletePragmaExport - complete #pragma export statements.
+  virtual void CompletePragmaExport(Decl *D) {}
+
   /// Callback invoked when an MSInheritanceAttr has been attached to a
   /// CXXRecordDecl.
   virtual void AssignInheritanceModel(CXXRecordDecl *RD) {}
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index fbcbf0ed416416..884c4147cf1285 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4520,6 +4520,12 @@ def ReleaseHandle : InheritableParamAttr {
   let Documentation = [ReleaseHandleDocs];
 }
 
+def zOSExport : InheritableAttr {
+  let Spellings = [CustomKeyword<"_Export">];
+  let Subjects = SubjectList<[Function, Var, CXXRecord]>;
+  let Documentation = [zOSExportDocs];
+}
+
 def UnsafeBufferUsage : InheritableAttr {
   let Spellings = [Clang<"unsafe_buffer_usage">];
   let Subjects = SubjectList<[Function, Field]>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index b1512e22ee2dd4..88bd8e605adf6c 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6863,6 +6863,27 @@ attribute requires a string literal argument to identify the handle being releas
   }];
 }
 
+def zOSExportDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+Use the _Export keyword with a function name or external variable to declare
+that it is to be exported (made available to other modules). You must define
+the object name in the same translation unit in which you use the _Export
+keyword. For example:
+
+.. code-block:: c
+
+  int _Export anthony(float);
+
+This statement exports the function anthony, if you define the function in the
+translation unit. The _Export keyword must immediately precede the object name.
+If you apply the _Export keyword to a class, the compiler automatically exports
+all static data members and member functions of the class. However, if you want
+it to apply to individual class members, then you must apply it to each member
+that can be referenced.
+  }];
+}
+
 def UnsafeBufferUsageDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dc84110ef78211..aaf547de1a48bd 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1129,6 +1129,13 @@ def err_pragma_pop_visibility_mismatch : Error<
   "#pragma visibility pop with no matching #pragma visibility push">;
 def note_surrounding_namespace_starts_here : Note<
   "surrounding namespace with visibility attribute starts here">;
+def warn_failed_to_resolve_pragma : Warning<
+  "failed to resolve '#pragma %0' to a declaration">,
+  InGroup<IgnoredPragmas>;
+def warn_pragma_not_applied : Warning<
+  "#pragma %0 is applicable to symbols with external linkage only; "
+  "not applied to %1">,
+  InGroup<IgnoredPragmas>;
 def err_pragma_loop_invalid_argument_type : Error<
   "invalid argument of type %0; expected an integer type">;
 def err_pragma_loop_compatibility : Error<
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index c5c3838407cf48..afef3b84a6985f 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -345,6 +345,8 @@ KEYWORD(__func__                    , KEYALL)
 KEYWORD(__objc_yes                  , KEYALL)
 KEYWORD(__objc_no                   , KEYALL)
 
+// z/OS specific keywords
+KEYWORD(_Export                     , KEYZOS)
 
 // C++ 2.11p1: Keywords.
 KEYWORD(asm                         , KEYCXX|KEYGNU)
@@ -1003,6 +1005,9 @@ PRAGMA_ANNOTATION(pragma_fp)
 // Annotation for the attribute pragma directives - #pragma clang attribute ...
 PRAGMA_ANNOTATION(pragma_attribute)
 
+// Annotation for C/C++ #pragma export(ident)
+PRAGMA_ANNOTATION(pragma_export)
+
 // Annotation for the riscv pragma directives - #pragma clang riscv intrinsic ...
 PRAGMA_ANNOTATION(pragma_riscv)
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index eb8a851da7e04e..eee7009b8615a7 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -220,6 +220,7 @@ class Parser : public CodeCompletionHandler {
   std::unique_ptr<PragmaHandler> AttributePragmaHandler;
   std::unique_ptr<PragmaHandler> MaxTokensHerePragmaHandler;
   std::unique_ptr<PragmaHandler> MaxTokensTotalPragmaHandler;
+  std::unique_ptr<PragmaHandler> ExportHandler;
   std::unique_ptr<PragmaHandler> RISCVPragmaHandler;
 
   std::unique_ptr<CommentHandler> CommentSemaHandler;
@@ -854,6 +855,18 @@ class Parser : public CodeCompletionHandler {
 
   void HandlePragmaAttribute();
 
+  /// Helper functions for handling zOS pragmas.
+  NestedNameSpecifier *zOSParseIdentifier(StringRef PragmaName,
+                                          IdentifierInfo *IdentName);
+  bool zOSParseParameterList(StringRef PragmaName,
+                             std::optional<SmallVector<QualType, 4>> &TypeList,
+                             Qualifiers &CVQual);
+  bool zOSHandlePragmaHelper(tok::TokenKind);
+
+  /// Handle the annotation token produced for
+  /// #pragma export ...
+  void HandlePragmaExport();
+
   /// GetLookAheadToken - This peeks ahead N tokens and returns that token
   /// without consuming any tokens.  LookAhead(0) returns 'Tok', LookAhead(1)
   /// returns the token after Tok, etc.
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 06243f2624876f..d03a6b11d26ab8 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -398,6 +398,8 @@ class DeclSpec {
   unsigned FS_virtual_specified : 1;
   LLVM_PREFERRED_TYPE(bool)
   unsigned FS_noreturn_specified : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned export_specified : 1;
 
   // friend-specifier
   LLVM_PREFERRED_TYPE(bool)
@@ -444,6 +446,7 @@ class DeclSpec {
   SourceLocation FS_forceinlineLoc;
   SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
   SourceLocation TQ_pipeLoc;
+  SourceLocation exportLoc;
 
   WrittenBuiltinSpecs writtenBS;
   void SaveWrittenBuiltinSpecs();
@@ -492,7 +495,8 @@ 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),
+        FS_noreturn_specified(false), export_specified(false),
+        FriendSpecifiedFirst(false),
         ConstexprSpecifier(
             static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
         Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
@@ -661,7 +665,10 @@ class DeclSpec {
   bool isNoreturnSpecified() const { return FS_noreturn_specified; }
   SourceLocation getNoreturnSpecLoc() const { return FS_noreturnLoc; }
 
-  void ClearFunctionSpecs() {
+  bool isExportSpecified() const { return export_specified; }
+  SourceLocation getExportSpecLoc() const { return exportLoc; }
+
+    void ClearFunctionSpecs() {
     FS_inline_specified = false;
     FS_inlineLoc = SourceLocation();
     FS_forceinline_specified = false;
@@ -811,6 +818,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 +1964,10 @@ class Declarator {
   LLVM_PREFERRED_TYPE(bool)
   unsigned InlineStorageUsed : 1;
 
+  /// Indicates whether this is set as _Export
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1;
+
   /// Indicates whether this declarator has an initializer.
   LLVM_PREFERRED_TYPE(bool)
   unsigned HasInitializer : 1;
@@ -2001,6 +2014,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 +2046,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 +2126,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,6 +2154,7 @@ class Declarator {
     HasInitializer = false;
     ObjCIvar = false;
     ObjCWeakProperty = false;
+    ExportSpecified = false;
     CommaLoc = SourceLocation();
     EllipsisLoc = SourceLocation();
     PackIndexingExpr = nullptr;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index bede971ce0191b..44bc9bcc22d4b0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1906,6 +1906,36 @@ class Sema final : public SemaBase {
   ActOnPragmaMSFunction(SourceLocation Loc,
                         const llvm::SmallVectorImpl<StringRef> &NoBuiltins);
 
+  /// A label from a C++ #pragma export, for a symbol that we
+  /// haven't seen the declaration for yet. The TypeList is the argument list
+  /// the function must match if HasTypeList is true.
+  struct SymbolLabel {
+    std::optional<SmallVector<QualType, 4>> TypeList;
+    StringRef MappedName;
+    SourceLocation NameLoc;
+    bool HasTypeList;
+    Qualifiers CVQual;
+  };
+
+  typedef SmallVector<SymbolLabel, 1> PendingSymbolOverloads;
+  typedef llvm::DenseMap<NestedNameSpecifier *, PendingSymbolOverloads>
+      SymbolNames;
+  SymbolNames PendingExportNames;
+
+  FunctionDecl *tryFunctionLookUp(NestedNameSpecifier *NestedName,
+                                  SourceLocation NameLoc);
+
+  /// trySymbolLookUp try to look up a decl matching the nested specifier
+  /// with optional type list.
+  NamedDecl *trySymbolLookUp(NestedNameSpecifier *NestedName,
+                             const clang::Sema::SymbolLabel &Label);
+
+  /// ActonPragmaExport - called on well-formed '\#pragma export'.
+  void ActOnPragmaExport(NestedNameSpecifier *NestedId,
+                         SourceLocation ExportNameLoc,
+                         std::optional<SmallVector<QualType, 4>> &&TypeList,
+                         Qualifiers CVQual);
+
   /// Only called on function definitions; if there is a pragma in scope
   /// with the effect of a range-based optnone, consider marking the function
   /// with attribute optnone.
diff --git a/clang/lib/CodeGen/BackendConsumer.h b/clang/lib/CodeGen/BackendConsumer.h
index a023d29cbd1d73..48ae73b4d25dfe 100644
--- a/clang/lib/CodeGen/BackendConsumer.h
+++ b/clang/lib/CodeGen/BackendConsumer.h
@@ -108,6 +108,7 @@ class BackendConsumer : public ASTConsumer {
   void HandleTagDeclRequiredDefinition(const TagDecl *D) override;
   void CompleteTentativeDefinition(VarDecl *D) override;
   void CompleteExternalDeclaration(DeclaratorDecl *D) override;
+  void CompletePragmaExport(Decl *D) override;
   void AssignInheritanceModel(CXXRecordDecl *RD) override;
   void HandleVTable(CXXRecordDecl *RD) override;
 
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp
index c9f9b688d0d8a2..830f605fb6ad78 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -380,6 +380,10 @@ void BackendConsumer::CompleteExternalDeclaration(DeclaratorDecl *D) {
   Gen->CompleteExternalDeclaration(D);
 }
 
+void BackendConsumer::CompletePragmaExport(Decl *D) {
+  Gen->CompletePragmaExport(D);
+}
+
 void BackendConsumer::AssignInheritanceModel(CXXRecordDecl *RD) {
   Gen->AssignInheritanceModel(RD);
 }
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 25c1c496a4f27f..599ec5bab83bce 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -5275,6 +5275,21 @@ void CodeGenModule::EmitExternalDeclaration(const DeclaratorDecl *D) {
     EmitExternalFunctionDeclaration(FD);
 }
 
+void CodeGenModule::EmitPragmaExport(const Decl *D) {
+  StringRef MangledName;
+  if (auto FD = dyn_cast<FunctionDecl>(D))
+    MangledName = getMangledName(GlobalDecl(FD));
+  else if (auto VD = dyn_cast<VarDecl>(D))
+    MangledName = getMangledName(GlobalDecl(VD));
+  else
+    assert(0 && "Unsupported pragma export Decl type");
+
+  if (llvm::GlobalValue *GV = GetGlobalValue(MangledName)) {
+    GV->setVisibility(llvm::GlobalValue::DefaultVisibility);
+    GV->setDSOLocal(false);
+  }
+}
+
 CharUnits CodeGenModule::GetTargetTypeStoreSize(llvm::Type *Ty) const {
   return Context.toCharUnitsFromBits(
       getDataLayout().getTypeStoreSizeInBits(Ty));
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index c58bb88035ca8a..2e9a0617491f60 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1364,6 +1364,8 @@ class CodeGenModule : public CodeGenTypeCache {
 
   void EmitExternalDeclaration(const DeclaratorDecl *D);
 
+  void EmitPragmaExport(const Decl *D);
+
   void EmitVTable(CXXRecordDecl *Class);
 
   void RefreshTypeCacheForClass(const CXXRecordDecl *Class);
diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp b/clang/lib/CodeGen/ModuleBuilder.cpp
index d4e0ab0339a8b0..7658d97af01840 100644
--- a/clang/lib/CodeGen/ModuleBuilder.cpp
+++ b/clang/lib/CodeGen/ModuleBuilder.cpp
@@ -314,7 +314,11 @@ namespace {
       Builder->EmitExternalDeclaration(D);
     }
 
-    void HandleVTable(CXXRecordDecl *RD) override {
+    void CompletePragmaExport(Decl *D) override {
+      Builder->EmitPragmaExport(D);
+    }
+
+  void HandleVTable(CXXRecordDecl *RD) override {
       if (Diags.hasUnrecoverableErrorOccurred())
         return;
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index 074e0556ecd2ad..a1e7ddbe389080 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -37,6 +37,11 @@ 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");
+  }
+
   // Pass "-fno-sized-deallocation" only when the user hasn't manually enabled
   // or disabled sized deallocations.
   if (!DriverArgs.hasArgNoClaim(options::OPT_fsized_deallocation,
@@ -149,11 +154,10 @@ void zos::Linker::ConstructJob(Compilation &C, const JobAction &JA,
     StringRef OutputName = Output.getFilename();
     // Strip away the last file suffix in presence from output name and add
     // a new .x suffix.
-    size_t Suffix = OutputName.find_last_of('.');
-    const char *SideDeckName =
-        Args.MakeArgString(OutputName.substr(0, Suffix) + ".x");
+    SmallString<128> SideDeckName = OutputName;
+    llvm::sys::path::replace_extension(SideDeckName, "x");
     CmdArgs.push_back("-x");
-    CmdArgs.push_back(SideDeckName);
+    CmdArgs.push_back(Args.MakeArgString(SideDeckName));
   } else {
     // We need to direct side file to /dev/null to suppress linker warning when
     // the object file contains exported symbols, and -shared or
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 122a05be1c039a..680e029019b8e6 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4449,6 +4449,12 @@ void Parser::ParseDeclarationSpecifiers(
       isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
       break;
 
+    case tok::kw__Export:
+      // If we find kw__Export, it is being applied to a var or function
+      // This will be handled in ParseDeclaratorInternal()
+      goto DoneWithDeclSpec;
+      break;
+
     // friend
     case tok::kw_friend:
       if (DSContext == DeclSpecContext::DSC_class) {
@@ -6174,6 +6180,7 @@ bool Parser::isDeclarationSpecifier(
   case tok::kw_virtual:
   case tok::kw_explicit:
   case tok::kw__Noreturn:
+  case tok::kw__Export:
 
     // alignment-specifier
   case tok::kw__Alignas:
@@ -6765,6 +6772,17 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
 
   tok::TokenKind Kind = Tok.getKind();
 
+  // If this variable or function is marked as _Export, set the bit
+  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 6f0f5a0311bc18..5be80011d92bb2 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1751,6 +1751,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
   // If attributes exist after tag, parse them.
   for (;;) {
     MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
+    // If the token is _Export, set the bits
+    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/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index cc6f18b5b319f9..af892115f1f8dd 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -401,6 +401,12 @@ struct PragmaMaxTokensTotalHandler : public PragmaHandler {
                     Token &FirstToken) override;
 };
 
+struct PragmaExportHandler : public PragmaHandler {
+  explicit PragmaExportHandler() : PragmaHandler("export") {}
+  void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
+                    Token &FirstToken) override;
+};
+
 struct PragmaRISCVHandler : public PragmaHandler {
   PragmaRISCVHandler(Sema &Actions)
       : PragmaHandler("riscv"), Actions(Actions) {}
@@ -564,6 +570,11 @@ void Parser::initializePragmaHandlers() {
   MaxTokensTotalPragmaHandler = std::make_unique<PragmaMaxTokensTotalHandler>();
   PP.AddPragmaHandler("clang", MaxTokensTotalPragmaHandler.get());
 
+  if (getLangOpts().ZOSExt) {
+    ExportHandler = std::make_unique<PragmaExportHandler>();
+    PP.AddPragmaHandler(ExportHandler.get());
+  }
+
   if (getTargetInfo().getTriple().isRISCV()) {
     RISCVPragmaHandler = std::make_unique<PragmaRISCVHandler>(Actions);
     PP.AddPragmaHandler("clang", RISCVPragmaHandler.get());
@@ -698,6 +709,11 @@ void Parser::resetPragmaHandlers() {
   PP.RemovePragmaHandler("clang", MaxTokensTotalPragmaHandler.get());
   MaxTokensTotalPragmaHandler.reset();
 
+  if (getLangOpts().ZOSExt) {
+    PP.RemovePragmaHandler(ExportHandler.get());
+    ExportHandler.reset();
+  }
+
   if (getTargetInfo().getTriple().isRISCV()) {
     PP....
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Oct 3, 2024

@llvm/pr-subscribers-backend-systemz

Author: Sean Perry (perry-ca)

Changes

The z/OS operating system uses the linker to control what symbols are exported from shared libraries. The compilation step sets a bit on each symbol that should be exported from a shared library and then the linker marks these as exported from the shared library. The linker also produces what is called a side deck that is a list of all the exported symbols. This is then used as an import file during the link process for programs (or other shared libraries) that link against a shared library. The default for compilation is to only export symbols that the user wants to export.

Control over what is exported is done by:

  • making the default for visibility to be hidden
  • using the _Export keyword or #pragma export to indicate what symbols should be exported. These are features the existing compilers on z/OS use.
  • using attribute((visibility,"default")) to mark the symbol as exported.

Note: Parser::HandlePragmaExport() has an extra layer of call to parse the pragma. We have some more pragmas that have very similar syntax. I'll be upstreaming those next. It is easier to leave that layer in than to remove it for this PR and then add it back.


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

28 Files Affected:

  • (modified) clang/include/clang/AST/ASTConsumer.h (+3)
  • (modified) clang/include/clang/Basic/Attr.td (+6)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+21)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+5)
  • (modified) clang/include/clang/Parse/Parser.h (+13)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+33-3)
  • (modified) clang/include/clang/Sema/Sema.h (+30)
  • (modified) clang/lib/CodeGen/BackendConsumer.h (+1)
  • (modified) clang/lib/CodeGen/CodeGenAction.cpp (+4)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+15)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+2)
  • (modified) clang/lib/CodeGen/ModuleBuilder.cpp (+5-1)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+8-4)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+18)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+6)
  • (modified) clang/lib/Parse/ParsePragma.cpp (+213)
  • (modified) clang/lib/Parse/Parser.cpp (+3)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+6)
  • (modified) clang/lib/Sema/Sema.cpp (+21)
  • (modified) clang/lib/Sema/SemaAttr.cpp (+162)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+25)
  • (added) clang/test/CodeGen/attr-export-failing.cpp (+4)
  • (added) clang/test/CodeGen/attr-export.cpp (+51)
  • (added) clang/test/CodeGen/pragma-export.c (+39)
  • (added) clang/test/CodeGen/pragma-export.cpp (+82)
  • (added) clang/test/CodeGen/zos-pragmas.c (+11)
  • (added) clang/test/CodeGen/zos-pragmas.cpp (+11)
diff --git a/clang/include/clang/AST/ASTConsumer.h b/clang/include/clang/AST/ASTConsumer.h
index 447f2592d23595..b15d53e700c679 100644
--- a/clang/include/clang/AST/ASTConsumer.h
+++ b/clang/include/clang/AST/ASTConsumer.h
@@ -108,6 +108,9 @@ class ASTConsumer {
   /// completed.
   virtual void CompleteExternalDeclaration(DeclaratorDecl *D) {}
 
+  /// CompletePragmaExport - complete #pragma export statements.
+  virtual void CompletePragmaExport(Decl *D) {}
+
   /// Callback invoked when an MSInheritanceAttr has been attached to a
   /// CXXRecordDecl.
   virtual void AssignInheritanceModel(CXXRecordDecl *RD) {}
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index fbcbf0ed416416..884c4147cf1285 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4520,6 +4520,12 @@ def ReleaseHandle : InheritableParamAttr {
   let Documentation = [ReleaseHandleDocs];
 }
 
+def zOSExport : InheritableAttr {
+  let Spellings = [CustomKeyword<"_Export">];
+  let Subjects = SubjectList<[Function, Var, CXXRecord]>;
+  let Documentation = [zOSExportDocs];
+}
+
 def UnsafeBufferUsage : InheritableAttr {
   let Spellings = [Clang<"unsafe_buffer_usage">];
   let Subjects = SubjectList<[Function, Field]>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index b1512e22ee2dd4..88bd8e605adf6c 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6863,6 +6863,27 @@ attribute requires a string literal argument to identify the handle being releas
   }];
 }
 
+def zOSExportDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+Use the _Export keyword with a function name or external variable to declare
+that it is to be exported (made available to other modules). You must define
+the object name in the same translation unit in which you use the _Export
+keyword. For example:
+
+.. code-block:: c
+
+  int _Export anthony(float);
+
+This statement exports the function anthony, if you define the function in the
+translation unit. The _Export keyword must immediately precede the object name.
+If you apply the _Export keyword to a class, the compiler automatically exports
+all static data members and member functions of the class. However, if you want
+it to apply to individual class members, then you must apply it to each member
+that can be referenced.
+  }];
+}
+
 def UnsafeBufferUsageDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dc84110ef78211..aaf547de1a48bd 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1129,6 +1129,13 @@ def err_pragma_pop_visibility_mismatch : Error<
   "#pragma visibility pop with no matching #pragma visibility push">;
 def note_surrounding_namespace_starts_here : Note<
   "surrounding namespace with visibility attribute starts here">;
+def warn_failed_to_resolve_pragma : Warning<
+  "failed to resolve '#pragma %0' to a declaration">,
+  InGroup<IgnoredPragmas>;
+def warn_pragma_not_applied : Warning<
+  "#pragma %0 is applicable to symbols with external linkage only; "
+  "not applied to %1">,
+  InGroup<IgnoredPragmas>;
 def err_pragma_loop_invalid_argument_type : Error<
   "invalid argument of type %0; expected an integer type">;
 def err_pragma_loop_compatibility : Error<
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index c5c3838407cf48..afef3b84a6985f 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -345,6 +345,8 @@ KEYWORD(__func__                    , KEYALL)
 KEYWORD(__objc_yes                  , KEYALL)
 KEYWORD(__objc_no                   , KEYALL)
 
+// z/OS specific keywords
+KEYWORD(_Export                     , KEYZOS)
 
 // C++ 2.11p1: Keywords.
 KEYWORD(asm                         , KEYCXX|KEYGNU)
@@ -1003,6 +1005,9 @@ PRAGMA_ANNOTATION(pragma_fp)
 // Annotation for the attribute pragma directives - #pragma clang attribute ...
 PRAGMA_ANNOTATION(pragma_attribute)
 
+// Annotation for C/C++ #pragma export(ident)
+PRAGMA_ANNOTATION(pragma_export)
+
 // Annotation for the riscv pragma directives - #pragma clang riscv intrinsic ...
 PRAGMA_ANNOTATION(pragma_riscv)
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index eb8a851da7e04e..eee7009b8615a7 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -220,6 +220,7 @@ class Parser : public CodeCompletionHandler {
   std::unique_ptr<PragmaHandler> AttributePragmaHandler;
   std::unique_ptr<PragmaHandler> MaxTokensHerePragmaHandler;
   std::unique_ptr<PragmaHandler> MaxTokensTotalPragmaHandler;
+  std::unique_ptr<PragmaHandler> ExportHandler;
   std::unique_ptr<PragmaHandler> RISCVPragmaHandler;
 
   std::unique_ptr<CommentHandler> CommentSemaHandler;
@@ -854,6 +855,18 @@ class Parser : public CodeCompletionHandler {
 
   void HandlePragmaAttribute();
 
+  /// Helper functions for handling zOS pragmas.
+  NestedNameSpecifier *zOSParseIdentifier(StringRef PragmaName,
+                                          IdentifierInfo *IdentName);
+  bool zOSParseParameterList(StringRef PragmaName,
+                             std::optional<SmallVector<QualType, 4>> &TypeList,
+                             Qualifiers &CVQual);
+  bool zOSHandlePragmaHelper(tok::TokenKind);
+
+  /// Handle the annotation token produced for
+  /// #pragma export ...
+  void HandlePragmaExport();
+
   /// GetLookAheadToken - This peeks ahead N tokens and returns that token
   /// without consuming any tokens.  LookAhead(0) returns 'Tok', LookAhead(1)
   /// returns the token after Tok, etc.
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 06243f2624876f..d03a6b11d26ab8 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -398,6 +398,8 @@ class DeclSpec {
   unsigned FS_virtual_specified : 1;
   LLVM_PREFERRED_TYPE(bool)
   unsigned FS_noreturn_specified : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned export_specified : 1;
 
   // friend-specifier
   LLVM_PREFERRED_TYPE(bool)
@@ -444,6 +446,7 @@ class DeclSpec {
   SourceLocation FS_forceinlineLoc;
   SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
   SourceLocation TQ_pipeLoc;
+  SourceLocation exportLoc;
 
   WrittenBuiltinSpecs writtenBS;
   void SaveWrittenBuiltinSpecs();
@@ -492,7 +495,8 @@ 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),
+        FS_noreturn_specified(false), export_specified(false),
+        FriendSpecifiedFirst(false),
         ConstexprSpecifier(
             static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
         Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
@@ -661,7 +665,10 @@ class DeclSpec {
   bool isNoreturnSpecified() const { return FS_noreturn_specified; }
   SourceLocation getNoreturnSpecLoc() const { return FS_noreturnLoc; }
 
-  void ClearFunctionSpecs() {
+  bool isExportSpecified() const { return export_specified; }
+  SourceLocation getExportSpecLoc() const { return exportLoc; }
+
+    void ClearFunctionSpecs() {
     FS_inline_specified = false;
     FS_inlineLoc = SourceLocation();
     FS_forceinline_specified = false;
@@ -811,6 +818,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 +1964,10 @@ class Declarator {
   LLVM_PREFERRED_TYPE(bool)
   unsigned InlineStorageUsed : 1;
 
+  /// Indicates whether this is set as _Export
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1;
+
   /// Indicates whether this declarator has an initializer.
   LLVM_PREFERRED_TYPE(bool)
   unsigned HasInitializer : 1;
@@ -2001,6 +2014,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 +2046,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 +2126,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,6 +2154,7 @@ class Declarator {
     HasInitializer = false;
     ObjCIvar = false;
     ObjCWeakProperty = false;
+    ExportSpecified = false;
     CommaLoc = SourceLocation();
     EllipsisLoc = SourceLocation();
     PackIndexingExpr = nullptr;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index bede971ce0191b..44bc9bcc22d4b0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1906,6 +1906,36 @@ class Sema final : public SemaBase {
   ActOnPragmaMSFunction(SourceLocation Loc,
                         const llvm::SmallVectorImpl<StringRef> &NoBuiltins);
 
+  /// A label from a C++ #pragma export, for a symbol that we
+  /// haven't seen the declaration for yet. The TypeList is the argument list
+  /// the function must match if HasTypeList is true.
+  struct SymbolLabel {
+    std::optional<SmallVector<QualType, 4>> TypeList;
+    StringRef MappedName;
+    SourceLocation NameLoc;
+    bool HasTypeList;
+    Qualifiers CVQual;
+  };
+
+  typedef SmallVector<SymbolLabel, 1> PendingSymbolOverloads;
+  typedef llvm::DenseMap<NestedNameSpecifier *, PendingSymbolOverloads>
+      SymbolNames;
+  SymbolNames PendingExportNames;
+
+  FunctionDecl *tryFunctionLookUp(NestedNameSpecifier *NestedName,
+                                  SourceLocation NameLoc);
+
+  /// trySymbolLookUp try to look up a decl matching the nested specifier
+  /// with optional type list.
+  NamedDecl *trySymbolLookUp(NestedNameSpecifier *NestedName,
+                             const clang::Sema::SymbolLabel &Label);
+
+  /// ActonPragmaExport - called on well-formed '\#pragma export'.
+  void ActOnPragmaExport(NestedNameSpecifier *NestedId,
+                         SourceLocation ExportNameLoc,
+                         std::optional<SmallVector<QualType, 4>> &&TypeList,
+                         Qualifiers CVQual);
+
   /// Only called on function definitions; if there is a pragma in scope
   /// with the effect of a range-based optnone, consider marking the function
   /// with attribute optnone.
diff --git a/clang/lib/CodeGen/BackendConsumer.h b/clang/lib/CodeGen/BackendConsumer.h
index a023d29cbd1d73..48ae73b4d25dfe 100644
--- a/clang/lib/CodeGen/BackendConsumer.h
+++ b/clang/lib/CodeGen/BackendConsumer.h
@@ -108,6 +108,7 @@ class BackendConsumer : public ASTConsumer {
   void HandleTagDeclRequiredDefinition(const TagDecl *D) override;
   void CompleteTentativeDefinition(VarDecl *D) override;
   void CompleteExternalDeclaration(DeclaratorDecl *D) override;
+  void CompletePragmaExport(Decl *D) override;
   void AssignInheritanceModel(CXXRecordDecl *RD) override;
   void HandleVTable(CXXRecordDecl *RD) override;
 
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp
index c9f9b688d0d8a2..830f605fb6ad78 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -380,6 +380,10 @@ void BackendConsumer::CompleteExternalDeclaration(DeclaratorDecl *D) {
   Gen->CompleteExternalDeclaration(D);
 }
 
+void BackendConsumer::CompletePragmaExport(Decl *D) {
+  Gen->CompletePragmaExport(D);
+}
+
 void BackendConsumer::AssignInheritanceModel(CXXRecordDecl *RD) {
   Gen->AssignInheritanceModel(RD);
 }
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 25c1c496a4f27f..599ec5bab83bce 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -5275,6 +5275,21 @@ void CodeGenModule::EmitExternalDeclaration(const DeclaratorDecl *D) {
     EmitExternalFunctionDeclaration(FD);
 }
 
+void CodeGenModule::EmitPragmaExport(const Decl *D) {
+  StringRef MangledName;
+  if (auto FD = dyn_cast<FunctionDecl>(D))
+    MangledName = getMangledName(GlobalDecl(FD));
+  else if (auto VD = dyn_cast<VarDecl>(D))
+    MangledName = getMangledName(GlobalDecl(VD));
+  else
+    assert(0 && "Unsupported pragma export Decl type");
+
+  if (llvm::GlobalValue *GV = GetGlobalValue(MangledName)) {
+    GV->setVisibility(llvm::GlobalValue::DefaultVisibility);
+    GV->setDSOLocal(false);
+  }
+}
+
 CharUnits CodeGenModule::GetTargetTypeStoreSize(llvm::Type *Ty) const {
   return Context.toCharUnitsFromBits(
       getDataLayout().getTypeStoreSizeInBits(Ty));
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index c58bb88035ca8a..2e9a0617491f60 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1364,6 +1364,8 @@ class CodeGenModule : public CodeGenTypeCache {
 
   void EmitExternalDeclaration(const DeclaratorDecl *D);
 
+  void EmitPragmaExport(const Decl *D);
+
   void EmitVTable(CXXRecordDecl *Class);
 
   void RefreshTypeCacheForClass(const CXXRecordDecl *Class);
diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp b/clang/lib/CodeGen/ModuleBuilder.cpp
index d4e0ab0339a8b0..7658d97af01840 100644
--- a/clang/lib/CodeGen/ModuleBuilder.cpp
+++ b/clang/lib/CodeGen/ModuleBuilder.cpp
@@ -314,7 +314,11 @@ namespace {
       Builder->EmitExternalDeclaration(D);
     }
 
-    void HandleVTable(CXXRecordDecl *RD) override {
+    void CompletePragmaExport(Decl *D) override {
+      Builder->EmitPragmaExport(D);
+    }
+
+  void HandleVTable(CXXRecordDecl *RD) override {
       if (Diags.hasUnrecoverableErrorOccurred())
         return;
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index 074e0556ecd2ad..a1e7ddbe389080 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -37,6 +37,11 @@ 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");
+  }
+
   // Pass "-fno-sized-deallocation" only when the user hasn't manually enabled
   // or disabled sized deallocations.
   if (!DriverArgs.hasArgNoClaim(options::OPT_fsized_deallocation,
@@ -149,11 +154,10 @@ void zos::Linker::ConstructJob(Compilation &C, const JobAction &JA,
     StringRef OutputName = Output.getFilename();
     // Strip away the last file suffix in presence from output name and add
     // a new .x suffix.
-    size_t Suffix = OutputName.find_last_of('.');
-    const char *SideDeckName =
-        Args.MakeArgString(OutputName.substr(0, Suffix) + ".x");
+    SmallString<128> SideDeckName = OutputName;
+    llvm::sys::path::replace_extension(SideDeckName, "x");
     CmdArgs.push_back("-x");
-    CmdArgs.push_back(SideDeckName);
+    CmdArgs.push_back(Args.MakeArgString(SideDeckName));
   } else {
     // We need to direct side file to /dev/null to suppress linker warning when
     // the object file contains exported symbols, and -shared or
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 122a05be1c039a..680e029019b8e6 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4449,6 +4449,12 @@ void Parser::ParseDeclarationSpecifiers(
       isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
       break;
 
+    case tok::kw__Export:
+      // If we find kw__Export, it is being applied to a var or function
+      // This will be handled in ParseDeclaratorInternal()
+      goto DoneWithDeclSpec;
+      break;
+
     // friend
     case tok::kw_friend:
       if (DSContext == DeclSpecContext::DSC_class) {
@@ -6174,6 +6180,7 @@ bool Parser::isDeclarationSpecifier(
   case tok::kw_virtual:
   case tok::kw_explicit:
   case tok::kw__Noreturn:
+  case tok::kw__Export:
 
     // alignment-specifier
   case tok::kw__Alignas:
@@ -6765,6 +6772,17 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
 
   tok::TokenKind Kind = Tok.getKind();
 
+  // If this variable or function is marked as _Export, set the bit
+  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 6f0f5a0311bc18..5be80011d92bb2 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1751,6 +1751,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
   // If attributes exist after tag, parse them.
   for (;;) {
     MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
+    // If the token is _Export, set the bits
+    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/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index cc6f18b5b319f9..af892115f1f8dd 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -401,6 +401,12 @@ struct PragmaMaxTokensTotalHandler : public PragmaHandler {
                     Token &FirstToken) override;
 };
 
+struct PragmaExportHandler : public PragmaHandler {
+  explicit PragmaExportHandler() : PragmaHandler("export") {}
+  void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
+                    Token &FirstToken) override;
+};
+
 struct PragmaRISCVHandler : public PragmaHandler {
   PragmaRISCVHandler(Sema &Actions)
       : PragmaHandler("riscv"), Actions(Actions) {}
@@ -564,6 +570,11 @@ void Parser::initializePragmaHandlers() {
   MaxTokensTotalPragmaHandler = std::make_unique<PragmaMaxTokensTotalHandler>();
   PP.AddPragmaHandler("clang", MaxTokensTotalPragmaHandler.get());
 
+  if (getLangOpts().ZOSExt) {
+    ExportHandler = std::make_unique<PragmaExportHandler>();
+    PP.AddPragmaHandler(ExportHandler.get());
+  }
+
   if (getTargetInfo().getTriple().isRISCV()) {
     RISCVPragmaHandler = std::make_unique<PragmaRISCVHandler>(Actions);
     PP.AddPragmaHandler("clang", RISCVPragmaHandler.get());
@@ -698,6 +709,11 @@ void Parser::resetPragmaHandlers() {
   PP.RemovePragmaHandler("clang", MaxTokensTotalPragmaHandler.get());
   MaxTokensTotalPragmaHandler.reset();
 
+  if (getLangOpts().ZOSExt) {
+    PP.RemovePragmaHandler(ExportHandler.get());
+    ExportHandler.reset();
+  }
+
   if (getTargetInfo().getTriple().isRISCV()) {
     PP....
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Oct 3, 2024

@llvm/pr-subscribers-clang-codegen

Author: Sean Perry (perry-ca)

Changes

The z/OS operating system uses the linker to control what symbols are exported from shared libraries. The compilation step sets a bit on each symbol that should be exported from a shared library and then the linker marks these as exported from the shared library. The linker also produces what is called a side deck that is a list of all the exported symbols. This is then used as an import file during the link process for programs (or other shared libraries) that link against a shared library. The default for compilation is to only export symbols that the user wants to export.

Control over what is exported is done by:

  • making the default for visibility to be hidden
  • using the _Export keyword or #pragma export to indicate what symbols should be exported. These are features the existing compilers on z/OS use.
  • using attribute((visibility,"default")) to mark the symbol as exported.

Note: Parser::HandlePragmaExport() has an extra layer of call to parse the pragma. We have some more pragmas that have very similar syntax. I'll be upstreaming those next. It is easier to leave that layer in than to remove it for this PR and then add it back.


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

28 Files Affected:

  • (modified) clang/include/clang/AST/ASTConsumer.h (+3)
  • (modified) clang/include/clang/Basic/Attr.td (+6)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+21)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+5)
  • (modified) clang/include/clang/Parse/Parser.h (+13)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+33-3)
  • (modified) clang/include/clang/Sema/Sema.h (+30)
  • (modified) clang/lib/CodeGen/BackendConsumer.h (+1)
  • (modified) clang/lib/CodeGen/CodeGenAction.cpp (+4)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+15)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+2)
  • (modified) clang/lib/CodeGen/ModuleBuilder.cpp (+5-1)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+8-4)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+18)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+6)
  • (modified) clang/lib/Parse/ParsePragma.cpp (+213)
  • (modified) clang/lib/Parse/Parser.cpp (+3)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+6)
  • (modified) clang/lib/Sema/Sema.cpp (+21)
  • (modified) clang/lib/Sema/SemaAttr.cpp (+162)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+25)
  • (added) clang/test/CodeGen/attr-export-failing.cpp (+4)
  • (added) clang/test/CodeGen/attr-export.cpp (+51)
  • (added) clang/test/CodeGen/pragma-export.c (+39)
  • (added) clang/test/CodeGen/pragma-export.cpp (+82)
  • (added) clang/test/CodeGen/zos-pragmas.c (+11)
  • (added) clang/test/CodeGen/zos-pragmas.cpp (+11)
diff --git a/clang/include/clang/AST/ASTConsumer.h b/clang/include/clang/AST/ASTConsumer.h
index 447f2592d23595..b15d53e700c679 100644
--- a/clang/include/clang/AST/ASTConsumer.h
+++ b/clang/include/clang/AST/ASTConsumer.h
@@ -108,6 +108,9 @@ class ASTConsumer {
   /// completed.
   virtual void CompleteExternalDeclaration(DeclaratorDecl *D) {}
 
+  /// CompletePragmaExport - complete #pragma export statements.
+  virtual void CompletePragmaExport(Decl *D) {}
+
   /// Callback invoked when an MSInheritanceAttr has been attached to a
   /// CXXRecordDecl.
   virtual void AssignInheritanceModel(CXXRecordDecl *RD) {}
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index fbcbf0ed416416..884c4147cf1285 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4520,6 +4520,12 @@ def ReleaseHandle : InheritableParamAttr {
   let Documentation = [ReleaseHandleDocs];
 }
 
+def zOSExport : InheritableAttr {
+  let Spellings = [CustomKeyword<"_Export">];
+  let Subjects = SubjectList<[Function, Var, CXXRecord]>;
+  let Documentation = [zOSExportDocs];
+}
+
 def UnsafeBufferUsage : InheritableAttr {
   let Spellings = [Clang<"unsafe_buffer_usage">];
   let Subjects = SubjectList<[Function, Field]>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index b1512e22ee2dd4..88bd8e605adf6c 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6863,6 +6863,27 @@ attribute requires a string literal argument to identify the handle being releas
   }];
 }
 
+def zOSExportDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+Use the _Export keyword with a function name or external variable to declare
+that it is to be exported (made available to other modules). You must define
+the object name in the same translation unit in which you use the _Export
+keyword. For example:
+
+.. code-block:: c
+
+  int _Export anthony(float);
+
+This statement exports the function anthony, if you define the function in the
+translation unit. The _Export keyword must immediately precede the object name.
+If you apply the _Export keyword to a class, the compiler automatically exports
+all static data members and member functions of the class. However, if you want
+it to apply to individual class members, then you must apply it to each member
+that can be referenced.
+  }];
+}
+
 def UnsafeBufferUsageDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dc84110ef78211..aaf547de1a48bd 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1129,6 +1129,13 @@ def err_pragma_pop_visibility_mismatch : Error<
   "#pragma visibility pop with no matching #pragma visibility push">;
 def note_surrounding_namespace_starts_here : Note<
   "surrounding namespace with visibility attribute starts here">;
+def warn_failed_to_resolve_pragma : Warning<
+  "failed to resolve '#pragma %0' to a declaration">,
+  InGroup<IgnoredPragmas>;
+def warn_pragma_not_applied : Warning<
+  "#pragma %0 is applicable to symbols with external linkage only; "
+  "not applied to %1">,
+  InGroup<IgnoredPragmas>;
 def err_pragma_loop_invalid_argument_type : Error<
   "invalid argument of type %0; expected an integer type">;
 def err_pragma_loop_compatibility : Error<
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index c5c3838407cf48..afef3b84a6985f 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -345,6 +345,8 @@ KEYWORD(__func__                    , KEYALL)
 KEYWORD(__objc_yes                  , KEYALL)
 KEYWORD(__objc_no                   , KEYALL)
 
+// z/OS specific keywords
+KEYWORD(_Export                     , KEYZOS)
 
 // C++ 2.11p1: Keywords.
 KEYWORD(asm                         , KEYCXX|KEYGNU)
@@ -1003,6 +1005,9 @@ PRAGMA_ANNOTATION(pragma_fp)
 // Annotation for the attribute pragma directives - #pragma clang attribute ...
 PRAGMA_ANNOTATION(pragma_attribute)
 
+// Annotation for C/C++ #pragma export(ident)
+PRAGMA_ANNOTATION(pragma_export)
+
 // Annotation for the riscv pragma directives - #pragma clang riscv intrinsic ...
 PRAGMA_ANNOTATION(pragma_riscv)
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index eb8a851da7e04e..eee7009b8615a7 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -220,6 +220,7 @@ class Parser : public CodeCompletionHandler {
   std::unique_ptr<PragmaHandler> AttributePragmaHandler;
   std::unique_ptr<PragmaHandler> MaxTokensHerePragmaHandler;
   std::unique_ptr<PragmaHandler> MaxTokensTotalPragmaHandler;
+  std::unique_ptr<PragmaHandler> ExportHandler;
   std::unique_ptr<PragmaHandler> RISCVPragmaHandler;
 
   std::unique_ptr<CommentHandler> CommentSemaHandler;
@@ -854,6 +855,18 @@ class Parser : public CodeCompletionHandler {
 
   void HandlePragmaAttribute();
 
+  /// Helper functions for handling zOS pragmas.
+  NestedNameSpecifier *zOSParseIdentifier(StringRef PragmaName,
+                                          IdentifierInfo *IdentName);
+  bool zOSParseParameterList(StringRef PragmaName,
+                             std::optional<SmallVector<QualType, 4>> &TypeList,
+                             Qualifiers &CVQual);
+  bool zOSHandlePragmaHelper(tok::TokenKind);
+
+  /// Handle the annotation token produced for
+  /// #pragma export ...
+  void HandlePragmaExport();
+
   /// GetLookAheadToken - This peeks ahead N tokens and returns that token
   /// without consuming any tokens.  LookAhead(0) returns 'Tok', LookAhead(1)
   /// returns the token after Tok, etc.
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 06243f2624876f..d03a6b11d26ab8 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -398,6 +398,8 @@ class DeclSpec {
   unsigned FS_virtual_specified : 1;
   LLVM_PREFERRED_TYPE(bool)
   unsigned FS_noreturn_specified : 1;
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned export_specified : 1;
 
   // friend-specifier
   LLVM_PREFERRED_TYPE(bool)
@@ -444,6 +446,7 @@ class DeclSpec {
   SourceLocation FS_forceinlineLoc;
   SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
   SourceLocation TQ_pipeLoc;
+  SourceLocation exportLoc;
 
   WrittenBuiltinSpecs writtenBS;
   void SaveWrittenBuiltinSpecs();
@@ -492,7 +495,8 @@ 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),
+        FS_noreturn_specified(false), export_specified(false),
+        FriendSpecifiedFirst(false),
         ConstexprSpecifier(
             static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
         Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
@@ -661,7 +665,10 @@ class DeclSpec {
   bool isNoreturnSpecified() const { return FS_noreturn_specified; }
   SourceLocation getNoreturnSpecLoc() const { return FS_noreturnLoc; }
 
-  void ClearFunctionSpecs() {
+  bool isExportSpecified() const { return export_specified; }
+  SourceLocation getExportSpecLoc() const { return exportLoc; }
+
+    void ClearFunctionSpecs() {
     FS_inline_specified = false;
     FS_inlineLoc = SourceLocation();
     FS_forceinline_specified = false;
@@ -811,6 +818,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 +1964,10 @@ class Declarator {
   LLVM_PREFERRED_TYPE(bool)
   unsigned InlineStorageUsed : 1;
 
+  /// Indicates whether this is set as _Export
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ExportSpecified : 1;
+
   /// Indicates whether this declarator has an initializer.
   LLVM_PREFERRED_TYPE(bool)
   unsigned HasInitializer : 1;
@@ -2001,6 +2014,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 +2046,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 +2126,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,6 +2154,7 @@ class Declarator {
     HasInitializer = false;
     ObjCIvar = false;
     ObjCWeakProperty = false;
+    ExportSpecified = false;
     CommaLoc = SourceLocation();
     EllipsisLoc = SourceLocation();
     PackIndexingExpr = nullptr;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index bede971ce0191b..44bc9bcc22d4b0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1906,6 +1906,36 @@ class Sema final : public SemaBase {
   ActOnPragmaMSFunction(SourceLocation Loc,
                         const llvm::SmallVectorImpl<StringRef> &NoBuiltins);
 
+  /// A label from a C++ #pragma export, for a symbol that we
+  /// haven't seen the declaration for yet. The TypeList is the argument list
+  /// the function must match if HasTypeList is true.
+  struct SymbolLabel {
+    std::optional<SmallVector<QualType, 4>> TypeList;
+    StringRef MappedName;
+    SourceLocation NameLoc;
+    bool HasTypeList;
+    Qualifiers CVQual;
+  };
+
+  typedef SmallVector<SymbolLabel, 1> PendingSymbolOverloads;
+  typedef llvm::DenseMap<NestedNameSpecifier *, PendingSymbolOverloads>
+      SymbolNames;
+  SymbolNames PendingExportNames;
+
+  FunctionDecl *tryFunctionLookUp(NestedNameSpecifier *NestedName,
+                                  SourceLocation NameLoc);
+
+  /// trySymbolLookUp try to look up a decl matching the nested specifier
+  /// with optional type list.
+  NamedDecl *trySymbolLookUp(NestedNameSpecifier *NestedName,
+                             const clang::Sema::SymbolLabel &Label);
+
+  /// ActonPragmaExport - called on well-formed '\#pragma export'.
+  void ActOnPragmaExport(NestedNameSpecifier *NestedId,
+                         SourceLocation ExportNameLoc,
+                         std::optional<SmallVector<QualType, 4>> &&TypeList,
+                         Qualifiers CVQual);
+
   /// Only called on function definitions; if there is a pragma in scope
   /// with the effect of a range-based optnone, consider marking the function
   /// with attribute optnone.
diff --git a/clang/lib/CodeGen/BackendConsumer.h b/clang/lib/CodeGen/BackendConsumer.h
index a023d29cbd1d73..48ae73b4d25dfe 100644
--- a/clang/lib/CodeGen/BackendConsumer.h
+++ b/clang/lib/CodeGen/BackendConsumer.h
@@ -108,6 +108,7 @@ class BackendConsumer : public ASTConsumer {
   void HandleTagDeclRequiredDefinition(const TagDecl *D) override;
   void CompleteTentativeDefinition(VarDecl *D) override;
   void CompleteExternalDeclaration(DeclaratorDecl *D) override;
+  void CompletePragmaExport(Decl *D) override;
   void AssignInheritanceModel(CXXRecordDecl *RD) override;
   void HandleVTable(CXXRecordDecl *RD) override;
 
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp
index c9f9b688d0d8a2..830f605fb6ad78 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -380,6 +380,10 @@ void BackendConsumer::CompleteExternalDeclaration(DeclaratorDecl *D) {
   Gen->CompleteExternalDeclaration(D);
 }
 
+void BackendConsumer::CompletePragmaExport(Decl *D) {
+  Gen->CompletePragmaExport(D);
+}
+
 void BackendConsumer::AssignInheritanceModel(CXXRecordDecl *RD) {
   Gen->AssignInheritanceModel(RD);
 }
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 25c1c496a4f27f..599ec5bab83bce 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -5275,6 +5275,21 @@ void CodeGenModule::EmitExternalDeclaration(const DeclaratorDecl *D) {
     EmitExternalFunctionDeclaration(FD);
 }
 
+void CodeGenModule::EmitPragmaExport(const Decl *D) {
+  StringRef MangledName;
+  if (auto FD = dyn_cast<FunctionDecl>(D))
+    MangledName = getMangledName(GlobalDecl(FD));
+  else if (auto VD = dyn_cast<VarDecl>(D))
+    MangledName = getMangledName(GlobalDecl(VD));
+  else
+    assert(0 && "Unsupported pragma export Decl type");
+
+  if (llvm::GlobalValue *GV = GetGlobalValue(MangledName)) {
+    GV->setVisibility(llvm::GlobalValue::DefaultVisibility);
+    GV->setDSOLocal(false);
+  }
+}
+
 CharUnits CodeGenModule::GetTargetTypeStoreSize(llvm::Type *Ty) const {
   return Context.toCharUnitsFromBits(
       getDataLayout().getTypeStoreSizeInBits(Ty));
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index c58bb88035ca8a..2e9a0617491f60 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1364,6 +1364,8 @@ class CodeGenModule : public CodeGenTypeCache {
 
   void EmitExternalDeclaration(const DeclaratorDecl *D);
 
+  void EmitPragmaExport(const Decl *D);
+
   void EmitVTable(CXXRecordDecl *Class);
 
   void RefreshTypeCacheForClass(const CXXRecordDecl *Class);
diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp b/clang/lib/CodeGen/ModuleBuilder.cpp
index d4e0ab0339a8b0..7658d97af01840 100644
--- a/clang/lib/CodeGen/ModuleBuilder.cpp
+++ b/clang/lib/CodeGen/ModuleBuilder.cpp
@@ -314,7 +314,11 @@ namespace {
       Builder->EmitExternalDeclaration(D);
     }
 
-    void HandleVTable(CXXRecordDecl *RD) override {
+    void CompletePragmaExport(Decl *D) override {
+      Builder->EmitPragmaExport(D);
+    }
+
+  void HandleVTable(CXXRecordDecl *RD) override {
       if (Diags.hasUnrecoverableErrorOccurred())
         return;
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index 074e0556ecd2ad..a1e7ddbe389080 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -37,6 +37,11 @@ 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");
+  }
+
   // Pass "-fno-sized-deallocation" only when the user hasn't manually enabled
   // or disabled sized deallocations.
   if (!DriverArgs.hasArgNoClaim(options::OPT_fsized_deallocation,
@@ -149,11 +154,10 @@ void zos::Linker::ConstructJob(Compilation &C, const JobAction &JA,
     StringRef OutputName = Output.getFilename();
     // Strip away the last file suffix in presence from output name and add
     // a new .x suffix.
-    size_t Suffix = OutputName.find_last_of('.');
-    const char *SideDeckName =
-        Args.MakeArgString(OutputName.substr(0, Suffix) + ".x");
+    SmallString<128> SideDeckName = OutputName;
+    llvm::sys::path::replace_extension(SideDeckName, "x");
     CmdArgs.push_back("-x");
-    CmdArgs.push_back(SideDeckName);
+    CmdArgs.push_back(Args.MakeArgString(SideDeckName));
   } else {
     // We need to direct side file to /dev/null to suppress linker warning when
     // the object file contains exported symbols, and -shared or
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 122a05be1c039a..680e029019b8e6 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4449,6 +4449,12 @@ void Parser::ParseDeclarationSpecifiers(
       isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
       break;
 
+    case tok::kw__Export:
+      // If we find kw__Export, it is being applied to a var or function
+      // This will be handled in ParseDeclaratorInternal()
+      goto DoneWithDeclSpec;
+      break;
+
     // friend
     case tok::kw_friend:
       if (DSContext == DeclSpecContext::DSC_class) {
@@ -6174,6 +6180,7 @@ bool Parser::isDeclarationSpecifier(
   case tok::kw_virtual:
   case tok::kw_explicit:
   case tok::kw__Noreturn:
+  case tok::kw__Export:
 
     // alignment-specifier
   case tok::kw__Alignas:
@@ -6765,6 +6772,17 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
 
   tok::TokenKind Kind = Tok.getKind();
 
+  // If this variable or function is marked as _Export, set the bit
+  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 6f0f5a0311bc18..5be80011d92bb2 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1751,6 +1751,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
   // If attributes exist after tag, parse them.
   for (;;) {
     MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
+    // If the token is _Export, set the bits
+    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/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index cc6f18b5b319f9..af892115f1f8dd 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -401,6 +401,12 @@ struct PragmaMaxTokensTotalHandler : public PragmaHandler {
                     Token &FirstToken) override;
 };
 
+struct PragmaExportHandler : public PragmaHandler {
+  explicit PragmaExportHandler() : PragmaHandler("export") {}
+  void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
+                    Token &FirstToken) override;
+};
+
 struct PragmaRISCVHandler : public PragmaHandler {
   PragmaRISCVHandler(Sema &Actions)
       : PragmaHandler("riscv"), Actions(Actions) {}
@@ -564,6 +570,11 @@ void Parser::initializePragmaHandlers() {
   MaxTokensTotalPragmaHandler = std::make_unique<PragmaMaxTokensTotalHandler>();
   PP.AddPragmaHandler("clang", MaxTokensTotalPragmaHandler.get());
 
+  if (getLangOpts().ZOSExt) {
+    ExportHandler = std::make_unique<PragmaExportHandler>();
+    PP.AddPragmaHandler(ExportHandler.get());
+  }
+
   if (getTargetInfo().getTriple().isRISCV()) {
     RISCVPragmaHandler = std::make_unique<PragmaRISCVHandler>(Actions);
     PP.AddPragmaHandler("clang", RISCVPragmaHandler.get());
@@ -698,6 +709,11 @@ void Parser::resetPragmaHandlers() {
   PP.RemovePragmaHandler("clang", MaxTokensTotalPragmaHandler.get());
   MaxTokensTotalPragmaHandler.reset();
 
+  if (getLangOpts().ZOSExt) {
+    PP.RemovePragmaHandler(ExportHandler.get());
+    ExportHandler.reset();
+  }
+
   if (getTargetInfo().getTriple().isRISCV()) {
     PP....
[truncated]

Copy link

github-actions bot commented Oct 3, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@perry-ca perry-ca changed the title Add visibility features for z/OS (eg. _Export, pragma export) [SystemZ][z/OS] Add visibility features for z/OS (eg. _Export, pragma export) Oct 4, 2024
Copy link
Contributor

@abhina-sree abhina-sree left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -0,0 +1,5 @@
// REQUIRES: systemz-registered-target
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do these tests actually require systemz-registered-target?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This question is still open, but also, these look like they should be tested in Sema and not CodeGen. Further, it's missing the -verify line so these expected diagnostics aren't actually being tested.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should there be diagnostics (and corresponding testing) for ignored/meaningless uses of _Export?

e.g.,

typedef int _Export ty;
ty x;
int f(int _Export x);
static int _Export s;
struct S {
  int _Export nonstaticdatamember;
};
void g() {
  int _Export automatic;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, there should be. As well as potentially confusing uses of _Export as in int _Export x, y;.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The int _Export x, y; is tested in attr-export.cpp

int _Export var3, var4, _Export var5;

if (existingValue != VisibilityAttr::Default)
Diag(DS.getExportSpecLoc(), diag::err_mismatched_visibility);
} else {
Tag->addAttr(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure adding implicit attributes is necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When -fvisibility=hidden (which is the default on z/OS), etc this is needed to ensure the symbol is exported. This is analogous to a declaration with the default visibility attribute specified.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My concern here is that you have code scattered in multiple places to control the visibility; you have two different places you add a visibility attribute, and then you override the visibility in codegen. Having overlapping ways of specifying the visibility will likely lead to weird bugs in edge cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally valid. The intention was to have these be another way to specify __attribute__((visibility("default"))). I've rewritten the code to these issues and have hopefully addressed all of your concerns.


/// trySymbolLookUp try to look up a decl matching the nested specifier
/// with optional type list.
NamedDecl *trySymbolLookUp(NestedNameSpecifier *NestedName,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please name this so it's clear it's specifically the lookup algorithm for pragma export.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I'll pick a name the works with the other pragmas too.

// Check if arguments match.
for (unsigned i = 0; i != FD->getNumParams(); ++i) {
const ParmVarDecl *PVD = FD->getParamDecl(i);
QualType ParmType = PVD->getOriginalType().getCanonicalType();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Using getOriginalType() here seems weird; does that really produce the result you want? Please add tests with function parameters that decay (array/function types).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. This code does work for a case like:

void func(int []);
#pragma export(func(int[]))

but not for:

void func(int []);
#pragma export(func(int*))

(or vice versa)

The later case should work. I'll add code to use getType() and then to decay the types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@efriedma-quic I've found a problem related to this comment. If I have a pragma like:

void fd6(int (*)()) {}
#pragma export (fd6(int (*)()))

I noticed that the type chain for the function arguments when I parse them on the pragma has the ParenType. eg:

PointerType 0xc7957d5eeb0 'int (*)(void)'
`-ParenType 0xc7957d5ee80 'int (void)' sugar
  `-FunctionProtoType 0xc7957d5eb90 'int (void)' cdecl
    `-BuiltinType 0xc7957d2e570 'int'

This results in a type mismatch when I do the QualType compare in this code. Any idea on how to solve this? Thanks

SymbolNames;
SymbolNames PendingExportNames;

FunctionDecl *tryFunctionLookUp(NestedNameSpecifier *NestedName,
Copy link
Collaborator

Choose a reason for hiding this comment

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

tryFunctionLookUp is dead code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used by another pragma I'm going to upstream next. This isn't used by pragma export but it is used by other pragmas we'll upstream after this one. I missed this when removing all of that code and can remove it for now if you wish.

@Endilll Endilll removed their request for review January 15, 2025 18:49
Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

Thanks for this! Some things that are missing:

  • a release note in clang/docs/ReleaseNotes.rst
  • Parsing and Sema tests

Also, it looks like these are two separate (but related) features, one for the pragma and another for _Export. Is that the case? If so, they should be split into separate PRs.

def zOSExportDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Use the _Export keyword with a function name or external variable to declare
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Use the _Export keyword with a function name or external variable to declare
Use the ``_Export`` keyword on a function or external variable to declare

let Content = [{
Use the _Export keyword with a function name or external variable to declare
that it is to be exported (made available to other modules). You must define
the object name in the same translation unit in which you use the _Export
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
the object name in the same translation unit in which you use the _Export
the object name in the same translation unit in which you use the ``_Export``

Comment on lines 7517 to 7522
This statement exports the function anthony, if you define the function in the
translation unit. The _Export keyword must immediately precede the object name.
If you apply the _Export keyword to a class, the compiler automatically exports
all static data members and member functions of the class. However, if you want
it to apply to individual class members, then you must apply it to each member
that can be referenced.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
This statement exports the function anthony, if you define the function in the
translation unit. The _Export keyword must immediately precede the object name.
If you apply the _Export keyword to a class, the compiler automatically exports
all static data members and member functions of the class. However, if you want
it to apply to individual class members, then you must apply it to each member
that can be referenced.
This statement exports the function ``anthony``, if you define the function in the
translation unit. The ``_Export`` keyword must immediately precede the declaration name.
If you apply the ``_Export`` keyword to a class, the compiler automatically exports
all static data members and member functions of the class. However, if you want
it to apply to individual class members, then you must apply it to each member
that can be referenced.

@@ -444,6 +446,7 @@ class DeclSpec {
SourceLocation FS_forceinlineLoc;
SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
SourceLocation TQ_pipeLoc;
SourceLocation exportLoc;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
SourceLocation exportLoc;
SourceLocation ExportLoc;

@@ -398,6 +398,8 @@ class DeclSpec {
unsigned FS_virtual_specified : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned FS_noreturn_specified : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned export_specified : 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
unsigned export_specified : 1;
unsigned ExportSpecified : 1; // zOS extension

Comment on lines 4 to 14
// Testing missing declarations.
#pragma export(d0) // expected-warning{{failed to resolve '#pragma export' to a declaration}}
#pragma export(f9) // expected-warning{{failed to resolve '#pragma export' to a declaration}}
#pragma export(f0(int)) // expected-warning{{failed to resolve '#pragma export' to a declaration}}
#pragma export(f3(double, double, double)) // expected-warning{{failed to resolve '#pragma export' to a declaration}}

// Testing pragma export after decl.
void f0(void) {}
static void sf0(void) {} // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf0'}}
int v0;
static int s0; // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's0'}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

These look like they should be tested in Sema not CodeGen.

Comment on lines 4 to 26
// Testing missing declarations.
#pragma export(d0) // expected-warning{{failed to resolve '#pragma export' to a declaration}}
#pragma export(f9) // expected-warning{{failed to resolve '#pragma export' to a declaration}}

// Testing pragma export after decl.
void f0(void) {}
static void sf0(void) {} // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf0'}}
int v0;
static int s0; // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's0'}}
#pragma export(f0)
#pragma export(sf0)
#pragma export(v0)
#pragma export(s0)

// Testing pragma export before decl.
#pragma export(f1)
#pragma export(sf1)
#pragma export(v1)
#pragma export(s1)
void f1(void) {}
static void sf1(void) {} // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf1'}}
int v1;
static int s1; // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's1'}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

These look like they should be testing in Sema rather than in CodeGen.

@@ -0,0 +1,5 @@
// REQUIRES: systemz-registered-target
Copy link
Collaborator

Choose a reason for hiding this comment

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

This question is still open, but also, these look like they should be tested in Sema and not CodeGen. Further, it's missing the -verify line so these expected diagnostics aren't actually being tested.

@@ -4250,6 +4250,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
// The TypeSourceInfo that this function returns will not be a null type.
// If there is an error, this function will fill in a dummy type as fallback.
QualType T = declSpecType;
fprintf(stderr, "SDP: === GetFullTypeForDeclarator\n"); T->dump(); fprintf(stderr, "SDP: === GetFullTypeForDeclarator\n");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Debugging code?

@@ -5722,6 +5723,7 @@ TypeSourceInfo *Sema::GetTypeForDeclarator(Declarator &D) {

TypeSourceInfo *ReturnTypeInfo = nullptr;
QualType T = GetDeclSpecTypeForDeclarator(state, ReturnTypeInfo);
fprintf(stderr, "SDP: === GetTypeForDeclarator\n"); T->dump(); fprintf(stderr, "SDP: === GetTypeForDeclarator\n");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Debugging code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah. I merged in the latest code and the PR got updated. I'll remove it. I'm sorting out a problem so not ready for a review again. I wish I could put the PR in draft mode.

Copy link
Contributor Author

@perry-ca perry-ca left a comment

Choose a reason for hiding this comment

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

@AaronBallman Thanks for the feedback. I'll make the changes and look into why _Exxport wasn't implemented like the other declspecs.

I did consider these to be the same feature (two ways to set the same information). If it's easier for reviewing I can look into splitting the PR.

SymbolNames;
SymbolNames PendingExportNames;

FunctionDecl *tryFunctionLookUp(NestedNameSpecifier *NestedName,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used by another pragma I'm going to upstream next. This isn't used by pragma export but it is used by other pragmas we'll upstream after this one. I missed this when removing all of that code and can remove it for now if you wish.


/// trySymbolLookUp try to look up a decl matching the nested specifier
/// with optional type list.
NamedDecl *trySymbolLookUp(NestedNameSpecifier *NestedName,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I'll pick a name the works with the other pragmas too.

// Check if arguments match.
for (unsigned i = 0; i != FD->getNumParams(); ++i) {
const ParmVarDecl *PVD = FD->getParamDecl(i);
QualType ParmType = PVD->getOriginalType().getCanonicalType();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. This code does work for a case like:

void func(int []);
#pragma export(func(int[]))

but not for:

void func(int []);
#pragma export(func(int*))

(or vice versa)

The later case should work. I'll add code to use getType() and then to decay the types.

// Check if arguments match.
for (unsigned i = 0; i != FD->getNumParams(); ++i) {
const ParmVarDecl *PVD = FD->getParamDecl(i);
QualType ParmType = PVD->getOriginalType().getCanonicalType();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@efriedma-quic I've found a problem related to this comment. If I have a pragma like:

void fd6(int (*)()) {}
#pragma export (fd6(int (*)()))

I noticed that the type chain for the function arguments when I parse them on the pragma has the ParenType. eg:

PointerType 0xc7957d5eeb0 'int (*)(void)'
`-ParenType 0xc7957d5ee80 'int (void)' sugar
  `-FunctionProtoType 0xc7957d5eb90 'int (void)' cdecl
    `-BuiltinType 0xc7957d2e570 'int'

This results in a type mismatch when I do the QualType compare in this code. Any idea on how to solve this? Thanks

@@ -5722,6 +5723,7 @@ TypeSourceInfo *Sema::GetTypeForDeclarator(Declarator &D) {

TypeSourceInfo *ReturnTypeInfo = nullptr;
QualType T = GetDeclSpecTypeForDeclarator(state, ReturnTypeInfo);
fprintf(stderr, "SDP: === GetTypeForDeclarator\n"); T->dump(); fprintf(stderr, "SDP: === GetTypeForDeclarator\n");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah. I merged in the latest code and the PR got updated. I'll remove it. I'm sorting out a problem so not ready for a review again. I wish I could put the PR in draft mode.

@AaronBallman
Copy link
Collaborator

I did consider these to be the same feature (two ways to set the same information). If it's easier for reviewing I can look into splitting the PR.

I don't insist on a split, FWIW.

@perry-ca
Copy link
Contributor Author

perry-ca commented Apr 9, 2025

@AaronBallman @efriedma-quic. I believe I have addressed all of your comments. I moved the tests into Parser/Sema and added some more (and fixed problems these found). Thanks for the feedback.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

I didn't spot any further concerns, but I'd appreciate a second set of eyes on this given the size of the changes.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

There seems to be a LOT of added complexity for the #pragma export, to the point that I'm not sure of the value compared to the keyword, and if we're going to keep it, we need to spend quite a bit more time on it.

As far as the keyword itself, I think there are quite a few more diagnostics we should look at, particularly for 'completeness'. I don't really like the error that is being diagnosed (just 'cannot be qualified'), as that is really quite vague. We need to try harder on that.

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). You must define
the object name in the same translation unit in which you use the ``_Export``
Copy link
Collaborator

Choose a reason for hiding this comment

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

This documentation should also mention the pragma spelling and document the whole thing. Also, do we diagnose that anothony isn't defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't diagnose that anthony isn't defined. This was never diagnosed in the existing z/OS compiler and neither with the visibility attribute. The wording is a little strong. We should say the object is only exported if it is also defined in the translation unit.


This statement exports the function ``anthony``, if you define the function in the
translation unit. The ``_Export`` keyword must immediately precede the declaration name.
If you apply the ``_Export`` keyword to a class, the compiler automatically exports
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
If you apply the ``_Export`` keyword to a class, the compiler automatically exports
If you apply the ``_Export`` keyword to a class or struct, the compiler automatically exports

What does it do with unions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unions are handled the same way as classes or structs. Any member function or static data member of the union will be exported.

@@ -4504,6 +4504,11 @@ void Parser::ParseDeclarationSpecifiers(
isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
break;

case tok::kw__Export:
// We're done with the declaration-specifiers.
goto DoneWithDeclSpec;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this mean _Export has to be last? Not a parser guy here, but this one doesn't feel right to me? @AaronBallman can you double check here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_Export is part of the declarator. If you see the keyword, the declspec is done and and you are now in the declarator.

Copy link
Collaborator

Choose a reason for hiding this comment

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

_Export is not really a declaration specifier (at least in the traditional implementation).
It is more like a C++11 attribute except that it lives on the left of a declarator-id instead of on the right.
In some realizations, the (less clean) conceptual model is that it is a declarator like * is, except it binds more strongly than (<function parameters>) or [<array bound>].

@perry-ca, I don't see any test in this PR for _Export in positions like:

int (*_Export x)(void) = 0;

Is the above accepted with this PR? Which conceptual model is being implemented here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test case above is valid and exports x.

@x = global ptr null, align 8

I'll add a test case to cover this.

You are correct. This keyword is expected to appear right before the identifier of the symbol being exported.

@@ -401,6 +401,12 @@ struct PragmaMaxTokensTotalHandler : public PragmaHandler {
Token &FirstToken) override;
};

struct PragmaExportHandler : public PragmaHandler {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This patch is big enough... I am beginning to wonder if we should split this pragma off into a separate patch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. Multiple requests now. I'll move the keyword _Export into a new PR. Do you prefer two new ones or keep this one going just for the pragma?

NestedNameSpecifier *
Parser::zOSParseIdentifier(StringRef PragmaName,
const IdentifierInfo *IdentName) {
NestedNameSpecifier *NestedId = nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this for the grammar of? Are you just trying to get the name of a thing? I did something similar, see : ParseOpenACCIDExpression. For C++ you can use ParseCXXIdExpression, and I emulated it for C.

Copy link
Contributor Author

@perry-ca perry-ca May 20, 2025

Choose a reason for hiding this comment

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

The grammar for the pragma is:

'#pragma' 'export' '(' name  ',' string-literal ')'

or

'#pragma' 'export' '(' qualified-name '(' argument-list ')' cv-qualifiers ',' string-literal ')'

The first syntax is straight forward. The name matches functions declared as extern "C" in C++. The second syntax provides matching for functions with C++ linkage. The qualified name here isn't an expression. It is like the declarator-id and will match a future declaration. I haven't seen anything that does what this code does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:SystemZ clang:codegen IR generation bugs: mangling, exceptions, etc. 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.

7 participants