Skip to content

[SystemZ][z/OS] Implement #pragma export #141671

New issue

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

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

Already on GitHub? Sign in to your account

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

Conversation

perry-ca
Copy link
Contributor

Implement the export pragma that is used in the z/OS XL C/C++ compiler to indicate that an external symbol is to be exported from the shared library. The syntax for the pragma is:

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

For C++ the name needs to be declared extern "C".

C++ will also accept the syntax:

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

for functions with C++ linkage.

See the following for the XL documentation:

You will notice the XL documentation doesn't have the second form of the pragma. However, the syntax for #pragma map does. While we implement these pragmas in clang, we will common up syntax differences.

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

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

llvmbot commented May 27, 2025

@llvm/pr-subscribers-backend-systemz

@llvm/pr-subscribers-clang-driver

Author: Sean Perry (perry-ca)

Changes

Implement the export pragma that is used in the z/OS XL C/C++ compiler to indicate that an external symbol is to be exported from the shared library. The syntax for the pragma is:

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

For C++ the name needs to be declared extern "C".

C++ will also accept the syntax:

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

for functions with C++ linkage.

See the following for the XL documentation:

You will notice the XL documentation doesn't have the second form of the pragma. However, the syntax for #pragma map does. While we implement these pragmas in clang, we will common up syntax differences.

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


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

21 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+10)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+3)
  • (modified) clang/include/clang/Parse/Parser.h (+12)
  • (modified) clang/include/clang/Sema/Sema.h (+37)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+4)
  • (modified) clang/lib/Parse/ParsePragma.cpp (+218)
  • (modified) clang/lib/Parse/ParseStmt.cpp (+8)
  • (modified) clang/lib/Parse/Parser.cpp (+3)
  • (modified) clang/lib/Sema/Sema.cpp (+6)
  • (modified) clang/lib/Sema/SemaAttr.cpp (+166)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+105)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+9)
  • (added) clang/test/CodeGen/pragma-export.c (+44)
  • (added) clang/test/CodeGen/pragma-export.cpp (+122)
  • (added) clang/test/CodeGen/zos-pragmas.c (+11)
  • (added) clang/test/CodeGen/zos-pragmas.cpp (+11)
  • (added) clang/test/Parser/pragma-export.c (+15)
  • (added) clang/test/Parser/pragma-export.cpp (+21)
  • (added) clang/test/Sema/pragma-export-failing.c (+42)
  • (added) clang/test/Sema/pragma-export-failing.cpp (+25)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 35ab1461e7b89..66195349fc63c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -918,6 +918,11 @@ WebAssembly Support
 AVR Support
 ^^^^^^^^^^^
 
+SystemZ Support
+^^^^^^^^^^^^^^^
+
+- Add support for `#pragma export` for z/OS
+
 DWARF Support in Clang
 ----------------------
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b63cc8a11b136..65e6cc265555a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1216,6 +1216,16 @@ 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 warn_pragma_not_applied_to_defined_symbol : Warning<
+  "#pragma %0 can only applied before a symbol is defined">,
+  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 94e72fea56a68..5adf0519be8c8 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -1015,6 +1015,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 e6492b81dfff8..0fca8b786ac8c 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7019,6 +7019,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;
 
   /// Initialize all pragma handlers.
@@ -7136,6 +7137,17 @@ class Parser : public CodeCompletionHandler {
 
   void HandlePragmaAttribute();
 
+  NestedNameSpecifier *zOSParseIdentifier(StringRef PragmaName,
+                                          const 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();
+
   ///@}
 
   //
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 6680a459a77fd..3eab936e899a6 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2278,6 +2278,39 @@ 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;
+    SourceLocation NameLoc;
+    bool HasTypeList;
+    Qualifiers CVQual;
+    NestedNameSpecifier
+        *NestedNameId; // Nested name identifier for type lookup.
+    bool Used;
+  };
+
+  bool typeListMatchesSymbolLabel(FunctionDecl *FD,
+                                  const clang::Sema::SymbolLabel &Label);
+
+  /// tryLookupSymbolLabel try to look up a decl matching the nested
+  //  specifier with optional type list.
+  NamedDecl *tryLookupSymbolLabel(const clang::Sema::SymbolLabel &Label);
+
+  bool isNamedDeclSameAsSymbolLabel(NamedDecl *D,
+                                    clang::Sema::SymbolLabel &Label);
+
+  typedef SmallVector<SymbolLabel, 1> PendingPragmaExportOverloads;
+  llvm::DenseMap<IdentifierInfo *, PendingPragmaExportOverloads>
+      PendingExportedNames;
+
+  /// 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.
@@ -3802,6 +3835,8 @@ class Sema final : public SemaBase {
   void warnOnReservedIdentifier(const NamedDecl *D);
   void warnOnCTypeHiddenInCPlusPlus(const NamedDecl *D);
 
+  void ProcessPragmaExport(DeclaratorDecl *newDecl);
+
   Decl *ActOnDeclarator(Scope *S, Declarator &D);
 
   NamedDecl *HandleDeclarator(Scope *S, Declarator &D,
@@ -4872,6 +4907,8 @@ class Sema final : public SemaBase {
                           TypeVisibilityAttr::VisibilityType Vis);
   VisibilityAttr *mergeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI,
                                       VisibilityAttr::VisibilityType Vis);
+  void mergeVisibilityType(Decl *D, SourceLocation Loc,
+                           VisibilityAttr::VisibilityType Type);
   SectionAttr *mergeSectionAttr(Decl *D, const AttributeCommonInfo &CI,
                                 StringRef Name);
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index d05202972825d..10e7bcf711068 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -36,6 +36,10 @@ void ZOS::addClangTargetOptions(const ArgList &DriverArgs,
                                 options::OPT_fno_aligned_allocation))
     CC1Args.push_back("-faligned-alloc-unavailable");
 
+  if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,
+                         options::OPT_fvisibility_ms_compat))
+    CC1Args.push_back("-fvisibility=hidden");
+
   if (DriverArgs.hasFlag(options::OPT_fxl_pragma_pack,
                          options::OPT_fno_xl_pragma_pack, true))
     CC1Args.push_back("-fxl-pragma-pack");
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 4e67fd033b9aa..ac8f37c5e4c4c 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -405,6 +405,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) {}
@@ -568,6 +574,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());
@@ -702,6 +713,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.RemovePragmaHandler("clang", RISCVPragmaHandler.get());
     RISCVPragmaHandler.reset();
@@ -1395,6 +1411,171 @@ bool Parser::HandlePragmaMSAllocText(StringRef PragmaName,
   return true;
 }
 
+NestedNameSpecifier *
+Parser::zOSParseIdentifier(StringRef PragmaName,
+                           const IdentifierInfo *IdentName) {
+  NestedNameSpecifier *NestedId = nullptr;
+  if (PP.getLangOpts().CPlusPlus) {
+    if (Tok.is(tok::coloncolon)) {
+      // Nothing to do.
+    } else if (Actions.CurContext->isNamespace()) {
+      auto *NS = cast<NamespaceDecl>(Actions.CurContext);
+      NestedId =
+          NestedNameSpecifier::Create(Actions.Context, NS->getIdentifier());
+      NestedId =
+          NestedNameSpecifier::Create(Actions.Context, NestedId, IdentName);
+      PP.Lex(Tok);
+    } else {
+      NestedId = NestedNameSpecifier::Create(Actions.Context, IdentName);
+      PP.Lex(Tok);
+    }
+    while (Tok.is(tok::coloncolon)) {
+      PP.Lex(Tok);
+      if (Tok.isNot(tok::identifier)) {
+        PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
+            << PragmaName;
+        return nullptr;
+      }
+      IdentifierInfo *II = Tok.getIdentifierInfo();
+      NestedId = NestedNameSpecifier::Create(Actions.Context, NestedId, II);
+      PP.Lex(Tok);
+    }
+  } else {
+    NestedId = NestedNameSpecifier::Create(Actions.Context, IdentName);
+    PP.Lex(Tok);
+  }
+  return NestedId;
+}
+
+bool Parser::zOSParseParameterList(
+    StringRef PragmaName, std::optional<SmallVector<QualType, 4>> &TypeList,
+    Qualifiers &CVQual) {
+  if (Tok.is(tok::l_paren)) {
+    TypeList = SmallVector<QualType, 4>();
+    PP.Lex(Tok);
+    while (Tok.isNot(tok::eof) && !Tok.is(tok::r_paren)) {
+      TypeResult TResult = ParseTypeName(nullptr);
+      if (!TResult.isInvalid()) {
+        QualType QT = TResult.get().get();
+        if (!QT.getTypePtr()->isVoidType()) {
+          TypeList->push_back(QT);
+        }
+      }
+      if (Tok.is(tok::comma) || Tok.is(tok::identifier))
+        PP.Lex(Tok);
+    }
+    if (Tok.is(tok::r_paren))
+      PP.Lex(Tok);
+    else {
+      // We ate the whole line trying to find the right paren of the parameter
+      // list.
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
+          << PragmaName;
+      return false;
+    }
+
+    if (TypeList.has_value())
+      while (Tok.is(tok::kw_const) || Tok.is(tok::kw_volatile)) {
+        if (Tok.is(tok::kw_const)) {
+          CVQual.addConst();
+        } else {
+          assert(Tok.is(tok::kw_volatile));
+          CVQual.addVolatile();
+        }
+        PP.Lex(Tok);
+      }
+  }
+  return true;
+}
+
+bool Parser::zOSHandlePragmaHelper(tok::TokenKind PragmaKind) {
+  assert(Tok.is(PragmaKind));
+
+  bool IsPragmaExport = PragmaKind == tok::annot_pragma_export;
+  assert(IsPragmaExport);
+  StringRef PragmaName = "export";
+
+  using namespace clang::charinfo;
+  auto *TheTokens =
+      (std::pair<std::unique_ptr<Token[]>, size_t> *)Tok.getAnnotationValue();
+  PP.EnterTokenStream(std::move(TheTokens->first), TheTokens->second, true,
+                      false);
+  ConsumeAnnotationToken();
+
+  do {
+    PP.Lex(Tok);
+    if (Tok.isNot(tok::l_paren)) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_lparen)
+          << PragmaName;
+      return false;
+    }
+
+    // C++ could have a nested name, or be qualified with ::.
+    PP.Lex(Tok);
+    if (Tok.isNot(tok::identifier) &&
+        !(PP.getLangOpts().CPlusPlus && Tok.is(tok::coloncolon))) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
+          << PragmaName;
+      return false;
+    }
+
+    IdentifierInfo *IdentName = Tok.getIdentifierInfo();
+    SourceLocation IdentNameLoc = Tok.getLocation();
+    NestedNameSpecifier *NestedId = zOSParseIdentifier(PragmaName, IdentName);
+    if (!NestedId)
+      return false;
+
+    // C++ can have a paramater list for overloaded functions.
+    // Try to parse the argument types.
+    std::optional<SmallVector<QualType, 4>> TypeList;
+    Qualifiers CVQual;
+
+    if (PP.getLangOpts().CPlusPlus && Tok.is(tok::l_paren)) {
+      if (!zOSParseParameterList(PragmaName, TypeList, CVQual))
+        return false;
+    }
+
+    if (Tok.isNot(tok::r_paren)) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_rparen)
+          << PragmaName;
+      return false;
+    }
+
+    PP.Lex(Tok);
+    Actions.ActOnPragmaExport(NestedId, IdentNameLoc, std::move(TypeList),
+                              CVQual);
+
+    // Because export is also a C++ keyword, we also check for that.
+    if (Tok.is(tok::identifier) || Tok.is(tok::kw_export)) {
+      IsPragmaExport = false;
+      PragmaName = Tok.getIdentifierInfo()->getName();
+      if (PragmaName == "export")
+        IsPragmaExport = true;
+      else
+        PP.Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
+            << PragmaName;
+    } else if (Tok.isNot(tok::eof)) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
+          << PragmaName;
+      return false;
+    }
+  } while (Tok.isNot(tok::eof));
+  PP.Lex(Tok);
+  return true;
+}
+
+void Parser::HandlePragmaExport() {
+  assert(Tok.is(tok::annot_pragma_export));
+
+  if (!zOSHandlePragmaHelper(tok::annot_pragma_export)) {
+    // Parsing pragma failed, and has been diagnosed.  Slurp up the
+    // tokens until eof (really end of line) to prevent follow-on errors.
+    while (Tok.isNot(tok::eof))
+      PP.Lex(Tok);
+    PP.Lex(Tok);
+  }
+}
+
 static std::string PragmaLoopHintString(Token PragmaName, Token Option) {
   StringRef Str = PragmaName.getIdentifierInfo()->getName();
   std::string ClangLoopStr("clang loop ");
@@ -4149,6 +4330,43 @@ void PragmaMaxTokensTotalHandler::HandlePragma(Preprocessor &PP,
   PP.overrideMaxTokens(MaxTokens, Loc);
 }
 
+static void zOSPragmaHandlerHelper(Preprocessor &PP, Token &Tok,
+                                   tok::TokenKind TokKind) {
+  Token EoF, AnnotTok;
+  EoF.startToken();
+  EoF.setKind(tok::eof);
+  AnnotTok.startToken();
+  AnnotTok.setKind(TokKind);
+  AnnotTok.setLocation(Tok.getLocation());
+  AnnotTok.setAnnotationEndLoc(Tok.getLocation());
+  SmallVector<Token, 8> TokenVector;
+  // Suck up all of the tokens before the eod.
+  for (; Tok.isNot(tok::eod); PP.Lex(Tok)) {
+    TokenVector.push_back(Tok);
+    AnnotTok.setAnnotationEndLoc(Tok.getLocation());
+  }
+  // Add a sentinel EoF token to the end of the list.
+  EoF.setLocation(Tok.getLocation());
+  TokenVector.push_back(EoF);
+  // We must allocate this array with new because EnterTokenStream is going to
+  // delete it later.
+  markAsReinjectedForRelexing(TokenVector);
+  auto TokenArray = std::make_unique<Token[]>(TokenVector.size());
+  std::copy(TokenVector.begin(), TokenVector.end(), TokenArray.get());
+  auto Value = new (PP.getPreprocessorAllocator())
+      std::pair<std::unique_ptr<Token[]>, size_t>(std::move(TokenArray),
+                                                  TokenVector.size());
+  AnnotTok.setAnnotationValue(Value);
+  PP.EnterToken(AnnotTok, /*IsReinject*/ false);
+}
+
+/// Handle #pragma export.
+void PragmaExportHandler::HandlePragma(Preprocessor &PP,
+                                       PragmaIntroducer Introducer,
+                                       Token &FirstToken) {
+  zOSPragmaHandlerHelper(PP, FirstToken, tok::annot_pragma_export);
+}
+
 // Handle '#pragma clang riscv intrinsic vector'.
 //        '#pragma clang riscv intrinsic sifive_vector'.
 //        '#pragma clang riscv intrinsic andes_vector'.
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index c788723023c8b..2b9e5d7f0c9df 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -488,6 +488,11 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
     ProhibitAttributes(GNUAttrs);
     HandlePragmaAttribute();
     return StmtEmpty();
+  case tok::annot_pragma_export:
+    ProhibitAttributes(CXX11Attrs);
+    ProhibitAttributes(GNUAttrs);
+    HandlePragmaExport();
+    return StmtEmpty();
   }
 
   // If we reached this code, the statement must end in a semicolon.
@@ -1012,6 +1017,9 @@ void Parser::ParseCompoundStatementLeadingPragmas() {
     case tok::annot_pragma_dump:
       HandlePragmaDump();
       break;
+    case tok::annot_pragma_export:
+      HandlePragmaExport();
+      break;
     default:
       checkForPragmas = false;
       break;
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index db65c05cc114a..84094f347b4e1 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -842,6 +842,9 @@ Parser::ParseExternalDeclaration(ParsedAttributes &Attrs,
   case tok::annot_pragma_attribute:
     HandlePragmaAttribute();
     return nullptr;
+  case tok::annot_pragma_export:
+    HandlePragmaExport();
+    return nullptr;
   case tok::semi:
     // Either a C++11 empty-declaration or attribute-declaration.
     SingleDecl =
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 1901d19b14dfc..0f3af893343f2 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1477,6 +1477,12 @@ void Sema::ActOnEndOfTranslationUnit() {
     Consumer.CompleteExternalDeclaration(D);
   }
 
+  // Visit all pending #pragma export.
+  for (auto &Iter : PendingExportedNames)
+    for (auto &Exported : Iter.second)
+      if (!Exported.Used)
+        Diag(Exported.NameLoc, diag::warn_failed_to_resolve_pragma) << "export";
+
   if (LangOpts.HLSL)
     HLSL().ActOnEndOfTranslationUnit(getASTContext().getTranslationUnitDecl());
 
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 44726c4cea123..de00dad5a17a7 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -1328,6 +1328,172 @@ void Sema::AddImplicitMSFunctionNoBuiltinAttr(FunctionDecl *FD) {
     FD->addAttr(NoBuiltinAttr::CreateImplicit(Context, V.data(), V.size()));
 }
 
+static QualType getCanonicalParamType(ASTContext &C, QualType T) {
+  return C.getCanonicalParamType(T);
+}
+
+bool Sema::typeListMatchesSymbolLabel(FunctionDecl *FD,
+                                      const clang::Sema::SymbolLabel &Label) {
+  assert(Label.TypeList.has_value());
+  if (FD->getNumParams() != Label.TypeList->size()) {
+    return false;
+  }
+
+  // Check if arguments match.
+  for (unsigned i = 0; i != FD->getNumParams(); ++i) {
+    const ParmVarDecl *PVD = FD->getParamDecl(i);
+    QualType ParmType = PVD->getType().getCanonicalType();
+
+    QualType MapArgType =
+        getCanonicalParamType(Context, (*Label.TypeList)[i].getCanonicalType());
+
+    if (ParmType != MapArgType)
+      return false;
+  }
+
+  if (isa<CXXMethodDecl>(FD)) {
+    // Check if CV qualifiers match.
+    const clang::CXXMethodDecl *const MFD =
+        clang::cast<clang::CXXMethodDecl>(FD);
+    if (MFD && (MFD->isConst() != Label.CVQual.hasConst() ||
+                MFD->isVolatile() != Label.CVQual.hasVolatile())) {
+      return false;
+    }
+  } else if (Label.CVQual.hasConst() || Label.CVQual.hasVolatile())
+    return false;
+
+  return true;
+}
+
+NamedDecl *Sema::tryLookupSymbolLabel(const clang::Sema::SymbolLabel &Label) {
+
+  NestedNameSpecifier *NestedName = Label.NestedNameId;
+  assert(!NestedName->getPrefix() ||
+         NestedName->getPrefix()->getKind() == NestedNameSpecifier::Identifier);
+  IdentifierInfo *Prefix =
+      NestedName->getPrefix() ? NestedName->getPrefix()->getAsIdentifier() : 0;
+  IdentifierInfo *Name = NestedName->getAsIdentifier();
+  LookupResult Result(*this, (Prefix ? Prefix : Name), Label.NameLoc,
+                      LookupOrdinaryName);
+  LookupName(Result, TUScope);
+
+  // Filter down to just a function, namespace or class.
+  LookupResult::Filter F = Result.makeFilter();
+  while (F.hasNext()) {
+    NamedDecl *D = F.next();
+    if (!(isa<FunctionDecl>(D) || isa<VarDecl>(D) || isa<NamespaceD...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented May 27, 2025

@llvm/pr-subscribers-clang

Author: Sean Perry (perry-ca)

Changes

Implement the export pragma that is used in the z/OS XL C/C++ compiler to indicate that an external symbol is to be exported from the shared library. The syntax for the pragma is:

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

For C++ the name needs to be declared extern "C".

C++ will also accept the syntax:

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

for functions with C++ linkage.

See the following for the XL documentation:

You will notice the XL documentation doesn't have the second form of the pragma. However, the syntax for #pragma map does. While we implement these pragmas in clang, we will common up syntax differences.

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


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

21 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+10)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+3)
  • (modified) clang/include/clang/Parse/Parser.h (+12)
  • (modified) clang/include/clang/Sema/Sema.h (+37)
  • (modified) clang/lib/Driver/ToolChains/ZOS.cpp (+4)
  • (modified) clang/lib/Parse/ParsePragma.cpp (+218)
  • (modified) clang/lib/Parse/ParseStmt.cpp (+8)
  • (modified) clang/lib/Parse/Parser.cpp (+3)
  • (modified) clang/lib/Sema/Sema.cpp (+6)
  • (modified) clang/lib/Sema/SemaAttr.cpp (+166)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+105)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+9)
  • (added) clang/test/CodeGen/pragma-export.c (+44)
  • (added) clang/test/CodeGen/pragma-export.cpp (+122)
  • (added) clang/test/CodeGen/zos-pragmas.c (+11)
  • (added) clang/test/CodeGen/zos-pragmas.cpp (+11)
  • (added) clang/test/Parser/pragma-export.c (+15)
  • (added) clang/test/Parser/pragma-export.cpp (+21)
  • (added) clang/test/Sema/pragma-export-failing.c (+42)
  • (added) clang/test/Sema/pragma-export-failing.cpp (+25)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 35ab1461e7b89..66195349fc63c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -918,6 +918,11 @@ WebAssembly Support
 AVR Support
 ^^^^^^^^^^^
 
+SystemZ Support
+^^^^^^^^^^^^^^^
+
+- Add support for `#pragma export` for z/OS
+
 DWARF Support in Clang
 ----------------------
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b63cc8a11b136..65e6cc265555a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1216,6 +1216,16 @@ 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 warn_pragma_not_applied_to_defined_symbol : Warning<
+  "#pragma %0 can only applied before a symbol is defined">,
+  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 94e72fea56a68..5adf0519be8c8 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -1015,6 +1015,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 e6492b81dfff8..0fca8b786ac8c 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7019,6 +7019,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;
 
   /// Initialize all pragma handlers.
@@ -7136,6 +7137,17 @@ class Parser : public CodeCompletionHandler {
 
   void HandlePragmaAttribute();
 
+  NestedNameSpecifier *zOSParseIdentifier(StringRef PragmaName,
+                                          const 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();
+
   ///@}
 
   //
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 6680a459a77fd..3eab936e899a6 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2278,6 +2278,39 @@ 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;
+    SourceLocation NameLoc;
+    bool HasTypeList;
+    Qualifiers CVQual;
+    NestedNameSpecifier
+        *NestedNameId; // Nested name identifier for type lookup.
+    bool Used;
+  };
+
+  bool typeListMatchesSymbolLabel(FunctionDecl *FD,
+                                  const clang::Sema::SymbolLabel &Label);
+
+  /// tryLookupSymbolLabel try to look up a decl matching the nested
+  //  specifier with optional type list.
+  NamedDecl *tryLookupSymbolLabel(const clang::Sema::SymbolLabel &Label);
+
+  bool isNamedDeclSameAsSymbolLabel(NamedDecl *D,
+                                    clang::Sema::SymbolLabel &Label);
+
+  typedef SmallVector<SymbolLabel, 1> PendingPragmaExportOverloads;
+  llvm::DenseMap<IdentifierInfo *, PendingPragmaExportOverloads>
+      PendingExportedNames;
+
+  /// 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.
@@ -3802,6 +3835,8 @@ class Sema final : public SemaBase {
   void warnOnReservedIdentifier(const NamedDecl *D);
   void warnOnCTypeHiddenInCPlusPlus(const NamedDecl *D);
 
+  void ProcessPragmaExport(DeclaratorDecl *newDecl);
+
   Decl *ActOnDeclarator(Scope *S, Declarator &D);
 
   NamedDecl *HandleDeclarator(Scope *S, Declarator &D,
@@ -4872,6 +4907,8 @@ class Sema final : public SemaBase {
                           TypeVisibilityAttr::VisibilityType Vis);
   VisibilityAttr *mergeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI,
                                       VisibilityAttr::VisibilityType Vis);
+  void mergeVisibilityType(Decl *D, SourceLocation Loc,
+                           VisibilityAttr::VisibilityType Type);
   SectionAttr *mergeSectionAttr(Decl *D, const AttributeCommonInfo &CI,
                                 StringRef Name);
 
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index d05202972825d..10e7bcf711068 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -36,6 +36,10 @@ void ZOS::addClangTargetOptions(const ArgList &DriverArgs,
                                 options::OPT_fno_aligned_allocation))
     CC1Args.push_back("-faligned-alloc-unavailable");
 
+  if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,
+                         options::OPT_fvisibility_ms_compat))
+    CC1Args.push_back("-fvisibility=hidden");
+
   if (DriverArgs.hasFlag(options::OPT_fxl_pragma_pack,
                          options::OPT_fno_xl_pragma_pack, true))
     CC1Args.push_back("-fxl-pragma-pack");
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 4e67fd033b9aa..ac8f37c5e4c4c 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -405,6 +405,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) {}
@@ -568,6 +574,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());
@@ -702,6 +713,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.RemovePragmaHandler("clang", RISCVPragmaHandler.get());
     RISCVPragmaHandler.reset();
@@ -1395,6 +1411,171 @@ bool Parser::HandlePragmaMSAllocText(StringRef PragmaName,
   return true;
 }
 
+NestedNameSpecifier *
+Parser::zOSParseIdentifier(StringRef PragmaName,
+                           const IdentifierInfo *IdentName) {
+  NestedNameSpecifier *NestedId = nullptr;
+  if (PP.getLangOpts().CPlusPlus) {
+    if (Tok.is(tok::coloncolon)) {
+      // Nothing to do.
+    } else if (Actions.CurContext->isNamespace()) {
+      auto *NS = cast<NamespaceDecl>(Actions.CurContext);
+      NestedId =
+          NestedNameSpecifier::Create(Actions.Context, NS->getIdentifier());
+      NestedId =
+          NestedNameSpecifier::Create(Actions.Context, NestedId, IdentName);
+      PP.Lex(Tok);
+    } else {
+      NestedId = NestedNameSpecifier::Create(Actions.Context, IdentName);
+      PP.Lex(Tok);
+    }
+    while (Tok.is(tok::coloncolon)) {
+      PP.Lex(Tok);
+      if (Tok.isNot(tok::identifier)) {
+        PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
+            << PragmaName;
+        return nullptr;
+      }
+      IdentifierInfo *II = Tok.getIdentifierInfo();
+      NestedId = NestedNameSpecifier::Create(Actions.Context, NestedId, II);
+      PP.Lex(Tok);
+    }
+  } else {
+    NestedId = NestedNameSpecifier::Create(Actions.Context, IdentName);
+    PP.Lex(Tok);
+  }
+  return NestedId;
+}
+
+bool Parser::zOSParseParameterList(
+    StringRef PragmaName, std::optional<SmallVector<QualType, 4>> &TypeList,
+    Qualifiers &CVQual) {
+  if (Tok.is(tok::l_paren)) {
+    TypeList = SmallVector<QualType, 4>();
+    PP.Lex(Tok);
+    while (Tok.isNot(tok::eof) && !Tok.is(tok::r_paren)) {
+      TypeResult TResult = ParseTypeName(nullptr);
+      if (!TResult.isInvalid()) {
+        QualType QT = TResult.get().get();
+        if (!QT.getTypePtr()->isVoidType()) {
+          TypeList->push_back(QT);
+        }
+      }
+      if (Tok.is(tok::comma) || Tok.is(tok::identifier))
+        PP.Lex(Tok);
+    }
+    if (Tok.is(tok::r_paren))
+      PP.Lex(Tok);
+    else {
+      // We ate the whole line trying to find the right paren of the parameter
+      // list.
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
+          << PragmaName;
+      return false;
+    }
+
+    if (TypeList.has_value())
+      while (Tok.is(tok::kw_const) || Tok.is(tok::kw_volatile)) {
+        if (Tok.is(tok::kw_const)) {
+          CVQual.addConst();
+        } else {
+          assert(Tok.is(tok::kw_volatile));
+          CVQual.addVolatile();
+        }
+        PP.Lex(Tok);
+      }
+  }
+  return true;
+}
+
+bool Parser::zOSHandlePragmaHelper(tok::TokenKind PragmaKind) {
+  assert(Tok.is(PragmaKind));
+
+  bool IsPragmaExport = PragmaKind == tok::annot_pragma_export;
+  assert(IsPragmaExport);
+  StringRef PragmaName = "export";
+
+  using namespace clang::charinfo;
+  auto *TheTokens =
+      (std::pair<std::unique_ptr<Token[]>, size_t> *)Tok.getAnnotationValue();
+  PP.EnterTokenStream(std::move(TheTokens->first), TheTokens->second, true,
+                      false);
+  ConsumeAnnotationToken();
+
+  do {
+    PP.Lex(Tok);
+    if (Tok.isNot(tok::l_paren)) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_lparen)
+          << PragmaName;
+      return false;
+    }
+
+    // C++ could have a nested name, or be qualified with ::.
+    PP.Lex(Tok);
+    if (Tok.isNot(tok::identifier) &&
+        !(PP.getLangOpts().CPlusPlus && Tok.is(tok::coloncolon))) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
+          << PragmaName;
+      return false;
+    }
+
+    IdentifierInfo *IdentName = Tok.getIdentifierInfo();
+    SourceLocation IdentNameLoc = Tok.getLocation();
+    NestedNameSpecifier *NestedId = zOSParseIdentifier(PragmaName, IdentName);
+    if (!NestedId)
+      return false;
+
+    // C++ can have a paramater list for overloaded functions.
+    // Try to parse the argument types.
+    std::optional<SmallVector<QualType, 4>> TypeList;
+    Qualifiers CVQual;
+
+    if (PP.getLangOpts().CPlusPlus && Tok.is(tok::l_paren)) {
+      if (!zOSParseParameterList(PragmaName, TypeList, CVQual))
+        return false;
+    }
+
+    if (Tok.isNot(tok::r_paren)) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_rparen)
+          << PragmaName;
+      return false;
+    }
+
+    PP.Lex(Tok);
+    Actions.ActOnPragmaExport(NestedId, IdentNameLoc, std::move(TypeList),
+                              CVQual);
+
+    // Because export is also a C++ keyword, we also check for that.
+    if (Tok.is(tok::identifier) || Tok.is(tok::kw_export)) {
+      IsPragmaExport = false;
+      PragmaName = Tok.getIdentifierInfo()->getName();
+      if (PragmaName == "export")
+        IsPragmaExport = true;
+      else
+        PP.Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
+            << PragmaName;
+    } else if (Tok.isNot(tok::eof)) {
+      PP.Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
+          << PragmaName;
+      return false;
+    }
+  } while (Tok.isNot(tok::eof));
+  PP.Lex(Tok);
+  return true;
+}
+
+void Parser::HandlePragmaExport() {
+  assert(Tok.is(tok::annot_pragma_export));
+
+  if (!zOSHandlePragmaHelper(tok::annot_pragma_export)) {
+    // Parsing pragma failed, and has been diagnosed.  Slurp up the
+    // tokens until eof (really end of line) to prevent follow-on errors.
+    while (Tok.isNot(tok::eof))
+      PP.Lex(Tok);
+    PP.Lex(Tok);
+  }
+}
+
 static std::string PragmaLoopHintString(Token PragmaName, Token Option) {
   StringRef Str = PragmaName.getIdentifierInfo()->getName();
   std::string ClangLoopStr("clang loop ");
@@ -4149,6 +4330,43 @@ void PragmaMaxTokensTotalHandler::HandlePragma(Preprocessor &PP,
   PP.overrideMaxTokens(MaxTokens, Loc);
 }
 
+static void zOSPragmaHandlerHelper(Preprocessor &PP, Token &Tok,
+                                   tok::TokenKind TokKind) {
+  Token EoF, AnnotTok;
+  EoF.startToken();
+  EoF.setKind(tok::eof);
+  AnnotTok.startToken();
+  AnnotTok.setKind(TokKind);
+  AnnotTok.setLocation(Tok.getLocation());
+  AnnotTok.setAnnotationEndLoc(Tok.getLocation());
+  SmallVector<Token, 8> TokenVector;
+  // Suck up all of the tokens before the eod.
+  for (; Tok.isNot(tok::eod); PP.Lex(Tok)) {
+    TokenVector.push_back(Tok);
+    AnnotTok.setAnnotationEndLoc(Tok.getLocation());
+  }
+  // Add a sentinel EoF token to the end of the list.
+  EoF.setLocation(Tok.getLocation());
+  TokenVector.push_back(EoF);
+  // We must allocate this array with new because EnterTokenStream is going to
+  // delete it later.
+  markAsReinjectedForRelexing(TokenVector);
+  auto TokenArray = std::make_unique<Token[]>(TokenVector.size());
+  std::copy(TokenVector.begin(), TokenVector.end(), TokenArray.get());
+  auto Value = new (PP.getPreprocessorAllocator())
+      std::pair<std::unique_ptr<Token[]>, size_t>(std::move(TokenArray),
+                                                  TokenVector.size());
+  AnnotTok.setAnnotationValue(Value);
+  PP.EnterToken(AnnotTok, /*IsReinject*/ false);
+}
+
+/// Handle #pragma export.
+void PragmaExportHandler::HandlePragma(Preprocessor &PP,
+                                       PragmaIntroducer Introducer,
+                                       Token &FirstToken) {
+  zOSPragmaHandlerHelper(PP, FirstToken, tok::annot_pragma_export);
+}
+
 // Handle '#pragma clang riscv intrinsic vector'.
 //        '#pragma clang riscv intrinsic sifive_vector'.
 //        '#pragma clang riscv intrinsic andes_vector'.
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index c788723023c8b..2b9e5d7f0c9df 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -488,6 +488,11 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
     ProhibitAttributes(GNUAttrs);
     HandlePragmaAttribute();
     return StmtEmpty();
+  case tok::annot_pragma_export:
+    ProhibitAttributes(CXX11Attrs);
+    ProhibitAttributes(GNUAttrs);
+    HandlePragmaExport();
+    return StmtEmpty();
   }
 
   // If we reached this code, the statement must end in a semicolon.
@@ -1012,6 +1017,9 @@ void Parser::ParseCompoundStatementLeadingPragmas() {
     case tok::annot_pragma_dump:
       HandlePragmaDump();
       break;
+    case tok::annot_pragma_export:
+      HandlePragmaExport();
+      break;
     default:
       checkForPragmas = false;
       break;
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index db65c05cc114a..84094f347b4e1 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -842,6 +842,9 @@ Parser::ParseExternalDeclaration(ParsedAttributes &Attrs,
   case tok::annot_pragma_attribute:
     HandlePragmaAttribute();
     return nullptr;
+  case tok::annot_pragma_export:
+    HandlePragmaExport();
+    return nullptr;
   case tok::semi:
     // Either a C++11 empty-declaration or attribute-declaration.
     SingleDecl =
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 1901d19b14dfc..0f3af893343f2 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1477,6 +1477,12 @@ void Sema::ActOnEndOfTranslationUnit() {
     Consumer.CompleteExternalDeclaration(D);
   }
 
+  // Visit all pending #pragma export.
+  for (auto &Iter : PendingExportedNames)
+    for (auto &Exported : Iter.second)
+      if (!Exported.Used)
+        Diag(Exported.NameLoc, diag::warn_failed_to_resolve_pragma) << "export";
+
   if (LangOpts.HLSL)
     HLSL().ActOnEndOfTranslationUnit(getASTContext().getTranslationUnitDecl());
 
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 44726c4cea123..de00dad5a17a7 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -1328,6 +1328,172 @@ void Sema::AddImplicitMSFunctionNoBuiltinAttr(FunctionDecl *FD) {
     FD->addAttr(NoBuiltinAttr::CreateImplicit(Context, V.data(), V.size()));
 }
 
+static QualType getCanonicalParamType(ASTContext &C, QualType T) {
+  return C.getCanonicalParamType(T);
+}
+
+bool Sema::typeListMatchesSymbolLabel(FunctionDecl *FD,
+                                      const clang::Sema::SymbolLabel &Label) {
+  assert(Label.TypeList.has_value());
+  if (FD->getNumParams() != Label.TypeList->size()) {
+    return false;
+  }
+
+  // Check if arguments match.
+  for (unsigned i = 0; i != FD->getNumParams(); ++i) {
+    const ParmVarDecl *PVD = FD->getParamDecl(i);
+    QualType ParmType = PVD->getType().getCanonicalType();
+
+    QualType MapArgType =
+        getCanonicalParamType(Context, (*Label.TypeList)[i].getCanonicalType());
+
+    if (ParmType != MapArgType)
+      return false;
+  }
+
+  if (isa<CXXMethodDecl>(FD)) {
+    // Check if CV qualifiers match.
+    const clang::CXXMethodDecl *const MFD =
+        clang::cast<clang::CXXMethodDecl>(FD);
+    if (MFD && (MFD->isConst() != Label.CVQual.hasConst() ||
+                MFD->isVolatile() != Label.CVQual.hasVolatile())) {
+      return false;
+    }
+  } else if (Label.CVQual.hasConst() || Label.CVQual.hasVolatile())
+    return false;
+
+  return true;
+}
+
+NamedDecl *Sema::tryLookupSymbolLabel(const clang::Sema::SymbolLabel &Label) {
+
+  NestedNameSpecifier *NestedName = Label.NestedNameId;
+  assert(!NestedName->getPrefix() ||
+         NestedName->getPrefix()->getKind() == NestedNameSpecifier::Identifier);
+  IdentifierInfo *Prefix =
+      NestedName->getPrefix() ? NestedName->getPrefix()->getAsIdentifier() : 0;
+  IdentifierInfo *Name = NestedName->getAsIdentifier();
+  LookupResult Result(*this, (Prefix ? Prefix : Name), Label.NameLoc,
+                      LookupOrdinaryName);
+  LookupName(Result, TUScope);
+
+  // Filter down to just a function, namespace or class.
+  LookupResult::Filter F = Result.makeFilter();
+  while (F.hasNext()) {
+    NamedDecl *D = F.next();
+    if (!(isa<FunctionDecl>(D) || isa<VarDecl>(D) || isa<NamespaceD...
[truncated]

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.

I only made it through the first bit of hte implementation, but parsing doesn't seem right to me, it doesn't recovery nicely as far as I can tell.

AS far as the design here, it isn't clear to me how this manages all of the C++ lookup/scoping/etc, and I don't see how the data structure provided would fix that.

Also, you seem to be doing some sort of parse during statement parsing, but no tests that show what this does/how this works when the scope isn't the local one? Or when this is in a function definition/etc.

ALSO: I don't see any template instantiation code, which this would need for statement/decl level declaration.

SystemZ Support
^^^^^^^^^^^^^^^

- Add support for `#pragma export` for z/OS
Copy link
Collaborator

Choose a reason for hiding this comment

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

We definitely need a much better intro/documentation here in the release notes.

/// 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;
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
std::optional<SmallVector<QualType, 4>> TypeList;
std::optional<SmallVector<QualType>> TypeList;

@@ -568,6 +574,11 @@ void Parser::initializePragmaHandlers() {
MaxTokensTotalPragmaHandler = std::make_unique<PragmaMaxTokensTotalHandler>();
PP.AddPragmaHandler("clang", MaxTokensTotalPragmaHandler.get());

if (getLangOpts().ZOSExt) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't particularly ZOS specific other than for compiler compat, right? Is there a good reason we couldn't allow this syntax on other compilers?

const IdentifierInfo *IdentName) {
NestedNameSpecifier *NestedId = nullptr;
if (PP.getLangOpts().CPlusPlus) {
if (Tok.is(tok::coloncolon)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Presumably this should ensure we only look up in the global namespace, right? VS local namespace?

<< PragmaName;
return nullptr;
}
IdentifierInfo *II = Tok.getIdentifierInfo();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Identifier isn't sufficient, what if this is naming a type inside a template? Or a dependent type?

bool Parser::zOSParseParameterList(
StringRef PragmaName, std::optional<SmallVector<QualType, 4>> &TypeList,
Qualifiers &CVQual) {
if (Tok.is(tok::l_paren)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We probably want to use a BalancedDelimiterTracker/etc for the parens.

if (!TResult.isInvalid()) {
QualType QT = TResult.get().get();
if (!QT.getTypePtr()->isVoidType()) {
TypeList->push_back(QT);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This feels lossy... Also, why are we allowing 'void' in this list at all?

}

if (TypeList.has_value())
while (Tok.is(tok::kw_const) || Tok.is(tok::kw_volatile)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This part seems wrong too, what about const before?

return false;
}
} while (Tok.isNot(tok::eof));
PP.Lex(Tok);
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is quite a bit on how htis parsing is working that doesn't seem right to me, but the parser code owner shoudl take a look.

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

Successfully merging this pull request may close these issues.

3 participants