[clang] [SystemZ][z/OS] Implement #pragma export (PR #141671)
Sean Perry via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 26 21:58:15 PDT 2025
https://github.com/perry-ca updated https://github.com/llvm/llvm-project/pull/141671
>From 33413248f61e899f6d4dbbddec9c1cec7bf0e44a Mon Sep 17 00:00:00 2001
From: Sean Perry <perry at ca.ibm.com>
Date: Fri, 23 May 2025 04:36:46 +0000
Subject: [PATCH 1/4] #pragma export support
---
clang/docs/ReleaseNotes.rst | 5 +
.../clang/Basic/DiagnosticSemaKinds.td | 10 +
clang/include/clang/Basic/TokenKinds.def | 3 +
clang/include/clang/Parse/Parser.h | 12 +
clang/include/clang/Sema/Sema.h | 37 +++
clang/lib/Driver/ToolChains/ZOS.cpp | 4 +
clang/lib/Parse/ParsePragma.cpp | 218 ++++++++++++++++++
clang/lib/Parse/ParseStmt.cpp | 8 +
clang/lib/Parse/Parser.cpp | 3 +
clang/lib/Sema/Sema.cpp | 6 +
clang/lib/Sema/SemaAttr.cpp | 166 +++++++++++++
clang/lib/Sema/SemaDecl.cpp | 105 +++++++++
clang/lib/Sema/SemaDeclAttr.cpp | 9 +
clang/test/CodeGen/pragma-export.c | 44 ++++
clang/test/CodeGen/pragma-export.cpp | 122 ++++++++++
clang/test/CodeGen/zos-pragmas.c | 11 +
clang/test/CodeGen/zos-pragmas.cpp | 11 +
clang/test/Parser/pragma-export.c | 15 ++
clang/test/Parser/pragma-export.cpp | 21 ++
clang/test/Sema/pragma-export-failing.c | 42 ++++
clang/test/Sema/pragma-export-failing.cpp | 25 ++
21 files changed, 877 insertions(+)
create mode 100644 clang/test/CodeGen/pragma-export.c
create mode 100644 clang/test/CodeGen/pragma-export.cpp
create mode 100644 clang/test/CodeGen/zos-pragmas.c
create mode 100644 clang/test/CodeGen/zos-pragmas.cpp
create mode 100644 clang/test/Parser/pragma-export.c
create mode 100644 clang/test/Parser/pragma-export.cpp
create mode 100644 clang/test/Sema/pragma-export-failing.c
create mode 100644 clang/test/Sema/pragma-export-failing.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 573ae97bff710..8dca697126026 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -886,6 +886,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 78b36ceb88125..cbaccee01487d 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 fe93df94438cb..94d5b9acd6e0a 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.
@@ -3801,6 +3834,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,
@@ -4871,6 +4906,8 @@ class Sema final : public SemaBase {
TypeVisibilityAttr::VisibilityType Vis);
VisibilityAttr *mergeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI,
VisibilityAttr::VisibilityType Vis);
+ void mergeVisibilityType(Decl *D, SourceLocation Loc,
+ VisibilityAttr::VisibilityType Type);
SectionAttr *mergeSectionAttr(Decl *D, const AttributeCommonInfo &CI,
StringRef Name);
diff --git a/clang/lib/Driver/ToolChains/ZOS.cpp b/clang/lib/Driver/ToolChains/ZOS.cpp
index c5ad3ef1b00f1..371623b83abd3 100644
--- a/clang/lib/Driver/ToolChains/ZOS.cpp
+++ b/clang/lib/Driver/ToolChains/ZOS.cpp
@@ -37,6 +37,10 @@ void ZOS::addClangTargetOptions(const ArgList &DriverArgs,
options::OPT_fno_aligned_allocation))
CC1Args.push_back("-faligned-alloc-unavailable");
+ if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,
+ options::OPT_fvisibility_ms_compat))
+ CC1Args.push_back("-fvisibility=hidden");
+
if (DriverArgs.hasFlag(options::OPT_fxl_pragma_pack,
options::OPT_fno_xl_pragma_pack, true))
CC1Args.push_back("-fxl-pragma-pack");
diff --git a/clang/lib/Parse/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 55a768580d393..8cfb75ec58691 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -843,6 +843,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<NamespaceDecl>(D) ||
+ isa<CXXRecordDecl>(D)))
+ F.erase();
+ }
+ F.done();
+
+ auto MatchDecl = [this, Name, Label](DeclContext *DC) -> NamedDecl * {
+ auto LRes = DC->lookup(DeclarationName(Name));
+ for (auto *I : LRes) {
+ if (isa<VarDecl>(I))
+ return I;
+ if (isa<FunctionDecl>(I)) {
+ FunctionDecl *FD = dyn_cast<FunctionDecl>(I);
+
+ // All function parameters must match if specified in pragma otherwise,
+ // we accept a function found by lookup only if it's the only one.
+ if ((Label.TypeList.has_value() &&
+ typeListMatchesSymbolLabel(FD, Label)) ||
+ (!Label.TypeList.has_value() && LRes.isSingleResult()))
+ return FD;
+ }
+ }
+ return nullptr;
+ };
+
+ // global variable or function in a namespace.
+ if (NamespaceDecl *ND = Result.getAsSingle<NamespaceDecl>()) {
+ if (ND->getIdentifierNamespace() == Decl::IDNS_Namespace) {
+ return MatchDecl(ND);
+ }
+ }
+
+ // data or function member.
+ if (CXXRecordDecl *RD = Result.getAsSingle<CXXRecordDecl>()) {
+ return MatchDecl(RD);
+ }
+
+ // either a variable, or a non-overloaded function, or an overloaded
+ // function with extern "C" linkage.
+ if (!Label.TypeList.has_value()) {
+ if (Result.isSingleResult()) {
+ NamedDecl *ND = Result.getFoundDecl();
+ if (isa<VarDecl>(ND))
+ return ND;
+ if (FunctionDecl *FD = dyn_cast<FunctionDecl>(ND)) {
+ if (!getLangOpts().CPlusPlus || FD->isExternC())
+ return FD;
+ else
+ return nullptr;
+ }
+ return ND;
+ }
+ if (Result.isOverloadedResult()) {
+ for (auto *Iter : Result) {
+ FunctionDecl *FD = dyn_cast<FunctionDecl>(Iter);
+ if (FD && FD->isExternC())
+ return FD;
+ }
+ return nullptr;
+ }
+ return nullptr;
+ }
+
+ // Loop over all the found decls and see if the arguments match
+ // any of the results.
+ for (LookupResult::iterator I = Result.begin(); I != Result.end(); ++I) {
+ NamedDecl *ND = (*I)->getUnderlyingDecl();
+ FunctionDecl *FD = dyn_cast<FunctionDecl>(ND);
+ if (FD && typeListMatchesSymbolLabel(FD, Label)) {
+ return FD;
+ }
+ }
+ return nullptr;
+}
+
+void Sema::ActOnPragmaExport(NestedNameSpecifier *NestedId,
+ SourceLocation NameLoc,
+ std::optional<SmallVector<QualType, 4>> &&TypeList,
+ Qualifiers CVQual) {
+ SymbolLabel Label;
+ Label.NameLoc = NameLoc;
+ Label.CVQual = CVQual;
+ Label.TypeList = std::move(TypeList);
+ Label.NestedNameId = NestedId;
+ Label.Used = false;
+
+ NamedDecl *PrevDecl = tryLookupSymbolLabel(Label);
+ if (PrevDecl && (isa<FunctionDecl>(PrevDecl) || isa<VarDecl>(PrevDecl))) {
+ if (PrevDecl->hasExternalFormalLinkage()) {
+ if (auto *FD = dyn_cast<FunctionDecl>(PrevDecl)) {
+ if (FD->hasBody())
+ Diag(NameLoc, diag::warn_pragma_not_applied_to_defined_symbol)
+ << "export";
+ else
+ mergeVisibilityType(PrevDecl, NameLoc, VisibilityAttr::Default);
+ } else {
+ auto *VD = dyn_cast<VarDecl>(PrevDecl);
+ assert(VD);
+ if (VD->hasDefinition() == VarDecl::Definition)
+ Diag(NameLoc, diag::warn_pragma_not_applied_to_defined_symbol)
+ << "export";
+ else
+ mergeVisibilityType(PrevDecl, NameLoc, VisibilityAttr::Default);
+ }
+ } else
+ Diag(NameLoc, diag::warn_pragma_not_applied) << "export" << PrevDecl;
+ Label.Used = true;
+ }
+ if (!Label.Used) {
+ PendingExportedNames[NestedId->getAsIdentifier()].push_back(Label);
+ }
+}
+
typedef std::vector<std::pair<unsigned, SourceLocation> > VisStack;
enum : unsigned { NoVisibility = ~0U };
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 814f81cb64cae..64cec75f60bac 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7514,6 +7514,109 @@ static void emitReadOnlyPlacementAttrWarning(Sema &S, const VarDecl *VD) {
}
}
+// Checks if the given label matches the named declaration.
+bool Sema::isNamedDeclSameAsSymbolLabel(NamedDecl *D,
+ Sema::SymbolLabel &Label) {
+ const DeclContext *Ctx = D->getDeclContext();
+
+ // Check the name.
+ NestedNameSpecifier *NS = Label.NestedNameId;
+ if (NS->getAsIdentifier()->getName() != D->getIdentifier()->getName())
+ return false;
+ NS = NS->getPrefix();
+
+ if (NS) {
+ // For ObjC methods and properties, look through categories and use the
+ // interface as context.
+ if (auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
+ if (auto *ID = MD->getClassInterface())
+ Ctx = ID;
+ } else if (auto *PD = dyn_cast<ObjCPropertyDecl>(D)) {
+ if (auto *MD = PD->getGetterMethodDecl())
+ if (auto *ID = MD->getClassInterface())
+ Ctx = ID;
+ } else if (auto *ID = dyn_cast<ObjCIvarDecl>(D)) {
+ if (auto *CI = ID->getContainingInterface())
+ Ctx = CI;
+ }
+
+ // Check named contexts.
+ if (Ctx->isFunctionOrMethod())
+ return false;
+
+ DeclarationName NameInScope = D->getDeclName();
+ for (; NS && Ctx; Ctx = Ctx->getParent()) {
+ // Suppress anonymous namespace.
+ if (isa<NamespaceDecl>(Ctx) &&
+ cast<NamespaceDecl>(Ctx)->isAnonymousNamespace())
+ continue;
+
+ // Suppress inline namespace if it doesn't make the result ambiguous.
+ if (Ctx->isInlineNamespace() && NameInScope &&
+ cast<NamespaceDecl>(Ctx)->isRedundantInlineQualifierFor(NameInScope))
+ continue;
+
+ // Skip non-named contexts such as linkage specifications and ExportDecls.
+ const NamedDecl *ND = dyn_cast<NamedDecl>(Ctx);
+ if (!ND)
+ continue;
+
+ // Fail if the sequence of nested name identifiers is shorter.
+ if (!NS)
+ return false;
+
+ // Fail if the names are not equal.
+ if (NS->getAsIdentifier()->getName() != ND->getIdentifier()->getName())
+ return false;
+
+ NameInScope = ND->getDeclName();
+ NS = NS->getPrefix();
+ }
+
+ // Fail if the sequence of nested name identifiers is longer.
+ // It makes sure that both lists have the same length.
+ if (NS)
+ return false;
+ }
+
+ if (isa<VarDecl>(D) && !Label.TypeList.has_value())
+ return true;
+ if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ // All function parameters match if specified in pragma.
+ if (Label.TypeList.has_value())
+ return typeListMatchesSymbolLabel(FD, Label);
+ // There might be overloaded functions. However, with the available
+ // information it cn only be concluded that the functions are the same.
+ if (!getLangOpts().CPlusPlus || FD->isExternC())
+ return true;
+ }
+
+ return false;
+}
+
+void Sema::ProcessPragmaExport(DeclaratorDecl *NewD) {
+ if (PendingExportedNames.empty())
+ return;
+ IdentifierInfo *IdentName = NewD->getIdentifier();
+ if (IdentName == nullptr)
+ return;
+ auto PendingName = PendingExportedNames.find(IdentName);
+ if (PendingName != PendingExportedNames.end()) {
+ for (auto I = PendingName->second.begin(), E = PendingName->second.end();
+ I != E; ++I) {
+ auto &Label = *I;
+ if (!Label.Used && isNamedDeclSameAsSymbolLabel(NewD, Label)) {
+ Label.Used = true;
+ if (NewD->hasExternalFormalLinkage())
+ mergeVisibilityType(NewD, Label.NameLoc, VisibilityAttr::Default);
+ else
+ Diag(Label.NameLoc, diag::warn_pragma_not_applied)
+ << "export" << NewD;
+ }
+ }
+ }
+}
+
// Checks if VD is declared at global scope or with C language linkage.
static bool isMainVar(DeclarationName Name, VarDecl *VD) {
return Name.getAsIdentifierInfo() &&
@@ -8212,6 +8315,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
CheckShadow(NewVD, ShadowedDecl, Previous);
ProcessPragmaWeak(S, NewVD);
+ ProcessPragmaExport(NewVD);
// If this is the first declaration of an extern C variable, update
// the map of such variables.
@@ -10863,6 +10967,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
}
ProcessPragmaWeak(S, NewFD);
+ ProcessPragmaExport(NewFD);
checkAttributesAfterMerging(*this, *NewFD);
AddKnownFunctionAttributes(NewFD);
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 8ce51cc2882bf..6e3e1104a20b4 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -2632,6 +2632,15 @@ static void handleExternalSourceSymbolAttr(Sema &S, Decl *D,
S.Context, AL, Language, DefinedIn, IsGeneratedDeclaration, USR));
}
+void Sema::mergeVisibilityType(Decl *D, SourceLocation Loc,
+ VisibilityAttr::VisibilityType Value) {
+ if (VisibilityAttr *Attr = D->getAttr<VisibilityAttr>()) {
+ if (Attr->getVisibility() != Value)
+ Diag(Loc, diag::err_mismatched_visibility);
+ } else
+ D->addAttr(VisibilityAttr::CreateImplicit(Context, Value));
+}
+
template <class T>
static T *mergeVisibilityAttr(Sema &S, Decl *D, const AttributeCommonInfo &CI,
typename T::VisibilityType value) {
diff --git a/clang/test/CodeGen/pragma-export.c b/clang/test/CodeGen/pragma-export.c
new file mode 100644
index 0000000000000..094fd0c6206b7
--- /dev/null
+++ b/clang/test/CodeGen/pragma-export.c
@@ -0,0 +1,44 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 %s -emit-llvm -fzos-extensions -triple s390x-none-zos -fvisibility=hidden -o - | FileCheck %s
+
+// Testing pragma export after decl.
+void f0(void) {}
+int v0;
+int vd = 2;
+#pragma export(f0)
+#pragma export(v0)
+#pragma export(vd)
+
+// Testing pragma export before decl.
+#pragma export(f1)
+#pragma export(v1)
+void f1(void) {}
+int v1;
+
+void f2(void);
+
+void t0(void) { f2();}
+
+#pragma export(f2)
+void f2(void) {}
+
+int func() {
+ int local;
+#pragma export(local)
+#pragma export(l2)
+ int l2;
+ return local+l2;
+}
+
+int local = 2;
+int l2 =4;
+
+// CHECK: @vd = hidden global i32
+// CHECK: @local = hidden global i32
+// CHECK: @l2 = hidden global i32
+// CHECK: @v0 = global i32
+// CHECK: @v1 = global i32
+// CHECK: define hidden void @f0()
+// CHECK: define void @f1()
+// CHECK: define hidden void @t0()
+// CHECK: define void @f2()
diff --git a/clang/test/CodeGen/pragma-export.cpp b/clang/test/CodeGen/pragma-export.cpp
new file mode 100644
index 0000000000000..aa780887bd272
--- /dev/null
+++ b/clang/test/CodeGen/pragma-export.cpp
@@ -0,0 +1,122 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -x c++ %s -emit-llvm -triple s390x-none-zos -fzos-extensions -fvisibility=hidden -o - | FileCheck %s
+
+// Testing pragma export after decl.
+void f0(void) {}
+int v0;
+#pragma export(f0(void))
+#pragma export(v0)
+
+// Testing pragma export before decl.
+#pragma export(f1(void))
+#pragma export(v1)
+void f1(void) {}
+int v1;
+
+// Testing overloaded functions.
+#pragma export(f2(double, double))
+#pragma export(f2(int))
+void f2(double, double) {}
+void f2(int) {}
+void f2(int, int) {}
+
+void f3(double) {}
+void f3(int, double) {}
+void f3(double, double) {}
+#pragma export(f3(double))
+#pragma export(f3(int, double))
+
+void f2(void) {}
+
+void t0(void) {
+ f2();
+}
+
+// Test type decay in arguments
+
+#pragma export(fd1(int[]))
+#pragma export(fd2(int*))
+#pragma export(fd3(int[]))
+#pragma export(fd4(int*))
+void fd1(int []) { }
+void fd2(int []) { }
+void fd3(int *) { }
+void fd4(int *) { }
+
+
+#pragma export (fd5(int ()))
+#pragma export (fd6(int (*)()))
+#pragma export (fd7(int ()))
+#pragma export (fd8(int (*)()))
+void fd5(int ()) {}
+void fd6(int ()) {}
+void fd7(int (*)()) {}
+void fd8(int (*)()) {}
+
+
+// Testing pragma export after decl and usage.
+#pragma export(f2(void))
+
+// Testing pragma export with namespace.
+void f5(void) {}
+void f5a(void) {}
+#pragma export(N0::f2a(void))
+namespace N0 {
+void f0(void) {}
+void f1(void) {}
+void f2(void) {}
+void f3(void) {}
+void f5(void) {}
+#pragma export(f0(void))
+#pragma export(N0::f1(void))
+#pragma export(f5(void))
+#pragma export(f0a(void))
+#pragma export(N0::f1a(void))
+#pragma export(f5a(void))
+void f0a(void) {}
+void f1a(void) {}
+void f2a(void) {}
+void f3a(void) {}
+void f5a(void) {}
+} // namespace N0
+#pragma export(N0::f2(void))
+
+void f10(int);
+#pragma export(f10)
+extern "C" void f10(double) {}
+void f10(int) {}
+
+// CHECK: @v0 = hidden global i32 0
+// CHECK: @v1 = global i32 0
+// CHECK: define hidden void @_Z2f0v()
+// CHECK: define void @_Z2f1v()
+// CHECK: define void @_Z2f2dd(double noundef %0, double noundef %1)
+// CHECK: define void @_Z2f2i(i32 noundef signext %0)
+// CHECK: define hidden void @_Z2f2ii(i32 noundef signext %0, i32 noundef signext %1)
+// CHECK: define hidden void @_Z2f3d(double noundef %0)
+// CHECK: define hidden void @_Z2f3id(i32 noundef signext %0, double noundef %1)
+// CHECK: define hidden void @_Z2f3dd(double noundef %0, double noundef %1)
+// CHECK: define hidden void @_Z2f2v()
+// CHECK: define hidden void @_Z2t0v()
+// CHECK: define void @_Z3fd1Pi(ptr noundef %0)
+// CHECK: define void @_Z3fd2Pi(ptr noundef %0)
+// CHECK: define void @_Z3fd3Pi(ptr noundef %0)
+// CHECK: define void @_Z3fd4Pi(ptr noundef %0)
+// CHECK: define void @_Z3fd5PFivE(ptr noundef %0)
+// CHECK: define void @_Z3fd6PFivE(ptr noundef %0)
+// CHECK: define void @_Z3fd7PFivE(ptr noundef %0)
+// CHECK: define void @_Z3fd8PFivE(ptr noundef %0)
+// CHECK: define hidden void @_Z2f5v()
+// CHECK: define hidden void @_Z3f5av()
+// CHECK: define hidden void @_ZN2N02f0Ev()
+// CHECK: define hidden void @_ZN2N02f1Ev()
+// CHECK: define hidden void @_ZN2N02f2Ev()
+// CHECK: define hidden void @_ZN2N02f3Ev()
+// CHECK: define hidden void @_ZN2N02f5Ev()
+// CHECK: define void @_ZN2N03f0aEv()
+// CHECK: define hidden void @_ZN2N03f1aEv()
+// CHECK: define void @_ZN2N03f2aEv()
+// CHECK: define hidden void @_ZN2N03f3aEv()
+// CHECK: define void @_ZN2N03f5aEv()
+// CHECK: define void @f10(double noundef %0) #0 {
+// CHECK: define hidden void @_Z3f10i(i32 noundef signext %0) #0 {
diff --git a/clang/test/CodeGen/zos-pragmas.c b/clang/test/CodeGen/zos-pragmas.c
new file mode 100644
index 0000000000000..e2bd03a33e20a
--- /dev/null
+++ b/clang/test/CodeGen/zos-pragmas.c
@@ -0,0 +1,11 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -emit-llvm -triple s390x-none-zos -fvisibility=hidden %s -o - | FileCheck %s
+
+int a,b,c;
+#pragma export(a) export(b) export(c)
+
+void foo(void);
+
+// CHECK: @a = global i32 0, align 4
+// CHECK: @b = global i32 0, align 4
+// CHECK: @c = global i32 0, align 4
diff --git a/clang/test/CodeGen/zos-pragmas.cpp b/clang/test/CodeGen/zos-pragmas.cpp
new file mode 100644
index 0000000000000..65e428796039e
--- /dev/null
+++ b/clang/test/CodeGen/zos-pragmas.cpp
@@ -0,0 +1,11 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -x c++ -emit-llvm -triple s390x-none-zos -fvisibility=hidden %s -o - | FileCheck %s
+
+#pragma export(a) export(b) export(c)
+int a,b,c;
+
+void foo(void);
+
+// CHECK: @a = global i32 0, align 4
+// CHECK: @b = global i32 0, align 4
+// CHECK: @c = global i32 0, align 4
diff --git a/clang/test/Parser/pragma-export.c b/clang/test/Parser/pragma-export.c
new file mode 100644
index 0000000000000..e78fa21242c77
--- /dev/null
+++ b/clang/test/Parser/pragma-export.c
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -triple s390x-ibm-zos -fsyntax-only -verify %s
+
+int x;
+
+#pragma export x // expected-warning {{missing '(' after '#pragma export' - ignoring}}
+#pragma export // expected-warning {{missing '(' after '#pragma export' - ignoring}}
+#pragma export( // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export(x // expected-warning {{missing ')' after '#pragma export' - ignoring}}
+#pragma export(::x) // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export(x)
+
+void f() {
+}
+
+#pragma export(f()) // expected-warning {{missing ')' after '#pragma export' - ignoring}}
diff --git a/clang/test/Parser/pragma-export.cpp b/clang/test/Parser/pragma-export.cpp
new file mode 100644
index 0000000000000..5f1656041b3d3
--- /dev/null
+++ b/clang/test/Parser/pragma-export.cpp
@@ -0,0 +1,21 @@
+// RUN: %clang_cc1 -x c++ -triple s390x-ibm-zos -fsyntax-only -verify %s
+
+extern int i;
+#pragma export(:: // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export(::) // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export(::i)
+
+struct S {
+ static int i;
+};
+#pragma export(S:: // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export(S::i // expected-warning {{missing ')' after '#pragma export' - ignoring}}
+#pragma export(S::i)
+
+void f(int);
+void f(double, double);
+#pragma export(f( // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export(f() // expected-warning {{missing ')' after '#pragma export' - ignoring}}
+#pragma export(f(int) // expected-warning {{missing ')' after '#pragma export' - ignoring}}
+#pragma export(f(double,) // expected-warning {{missing ')' after '#pragma export' - ignoring}}
+#pragma export(f(double,double))
diff --git a/clang/test/Sema/pragma-export-failing.c b/clang/test/Sema/pragma-export-failing.c
new file mode 100644
index 0000000000000..57bf97e628e32
--- /dev/null
+++ b/clang/test/Sema/pragma-export-failing.c
@@ -0,0 +1,42 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -triple s390x-none-zos -fzos-extensions %s -fsyntax-only -verify
+
+#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(sf1) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf1'}}
+#pragma export(s1) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's1'}}
+static void sf1(void) {}
+static int s1;
+
+static void sf0(void) {}
+int v0;
+static int s0;
+#pragma export(sf0) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf0'}}
+#pragma export(s0) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's0'}}
+
+#pragma export(f1) // expected-error {{visibility does not match previous declaration}}
+int f1() __attribute__((visibility("hidden")));
+int f2() __attribute__((visibility("hidden")));
+#pragma export(f2) // expected-error {{visibility does not match previous declaration}}
+
+
+int hoo() __attribute__((visibility("hidden")));
+
+int foo() { return 4; }
+#pragma export(foo) // expected-warning {{#pragma export can only applied before a symbol is defined}}
+
+int var = 4;
+#pragma export(var) // expected-warning {{#pragma export can only applied before a symbol is defined}}
+
+int func() {
+ int local;
+#pragma export(local) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'local'}}
+#pragma export(l2) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'l2'}}
+ int l2;
+ return local+l2;
+}
+
+int local = 2;
+int l2 =4;
+
diff --git a/clang/test/Sema/pragma-export-failing.cpp b/clang/test/Sema/pragma-export-failing.cpp
new file mode 100644
index 0000000000000..908395c899cf5
--- /dev/null
+++ b/clang/test/Sema/pragma-export-failing.cpp
@@ -0,0 +1,25 @@
+// REQUIRES: systemz-registered-target
+// RUN: %clang_cc1 -x c++ -triple s390x-none-zos -fzos-extensions %s -fsyntax-only -verify
+
+#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}}
+
+#pragma export(N::sf1(void)) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf1'}}
+#pragma export(N::s1) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's1'}}
+namespace N {
+static void sf1(void) {}
+static int s1;
+
+static void sf0(void) {}
+int v0;
+static int s0;
+}
+#pragma export(N::sf0(void)) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf0'}}
+#pragma export(N::s0) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's0'}}
+
+void f10(int);
+#pragma export(f10) // expected-warning{{failed to resolve '#pragma export' to a declaration}}
+
+#pragma export(f11) // expected-warning{{failed to resolve '#pragma export' to a declaration}}
+void f11(int);
+
>From c3bdc3a24b5e73c770f7f3a0801dc4a64ac8205f Mon Sep 17 00:00:00 2001
From: Sean Perry <perry at ca.ibm.com>
Date: Thu, 26 Jun 2025 20:30:22 +0000
Subject: [PATCH 2/4] only accept export(id)
---
clang/include/clang/Sema/Sema.h | 15 +--
clang/lib/Parse/ParsePragma.cpp | 20 +--
clang/lib/Sema/Sema.cpp | 9 +-
clang/lib/Sema/SemaAttr.cpp | 142 +---------------------
clang/lib/Sema/SemaDecl.cpp | 82 ++-----------
clang/test/.clang-format-ignore | 0
clang/test/CodeGen/pragma-export.cpp | 107 ++++------------
clang/test/Parser/pragma-export.cpp | 17 +--
clang/test/Sema/pragma-export-failing.cpp | 12 +-
9 files changed, 64 insertions(+), 340 deletions(-)
create mode 100644 clang/test/.clang-format-ignore
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 3eab936e899a6..14180655aa801 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2282,12 +2282,8 @@ class Sema final : public SemaBase {
/// 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.
+ IdentifierInfo *IdentId;
bool Used;
};
@@ -2301,15 +2297,10 @@ class Sema final : public SemaBase {
bool isNamedDeclSameAsSymbolLabel(NamedDecl *D,
clang::Sema::SymbolLabel &Label);
- typedef SmallVector<SymbolLabel, 1> PendingPragmaExportOverloads;
- llvm::DenseMap<IdentifierInfo *, PendingPragmaExportOverloads>
- PendingExportedNames;
+ llvm::DenseMap<IdentifierInfo *, SymbolLabel> PendingExportedNames;
/// ActonPragmaExport - called on well-formed '\#pragma export'.
- void ActOnPragmaExport(NestedNameSpecifier *NestedId,
- SourceLocation ExportNameLoc,
- std::optional<SmallVector<QualType, 4>> &&TypeList,
- Qualifiers CVQual);
+ void ActOnPragmaExport(IdentifierInfo *IdentId, SourceLocation ExportNameLoc);
/// Only called on function definitions; if there is a pragma in scope
/// with the effect of a range-based optnone, consider marking the function
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index ac8f37c5e4c4c..b57e0d2549120 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -1512,8 +1512,7 @@ bool Parser::zOSHandlePragmaHelper(tok::TokenKind PragmaKind) {
// 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))) {
+ if (Tok.isNot(tok::identifier)) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_identifier)
<< PragmaName;
return false;
@@ -1521,19 +1520,7 @@ bool Parser::zOSHandlePragmaHelper(tok::TokenKind PragmaKind) {
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;
- }
+ PP.Lex(Tok);
if (Tok.isNot(tok::r_paren)) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_rparen)
@@ -1542,8 +1529,7 @@ bool Parser::zOSHandlePragmaHelper(tok::TokenKind PragmaKind) {
}
PP.Lex(Tok);
- Actions.ActOnPragmaExport(NestedId, IdentNameLoc, std::move(TypeList),
- CVQual);
+ Actions.ActOnPragmaExport(IdentName, IdentNameLoc);
// Because export is also a C++ keyword, we also check for that.
if (Tok.is(tok::identifier) || Tok.is(tok::kw_export)) {
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 0f3af893343f2..86a60a8b87ef1 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1478,10 +1478,11 @@ void Sema::ActOnEndOfTranslationUnit() {
}
// 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";
+ for (auto &Iter : PendingExportedNames) {
+ 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 de00dad5a17a7..e1aa29af84c38 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -1328,146 +1328,14 @@ 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<NamespaceDecl>(D) ||
- isa<CXXRecordDecl>(D)))
- F.erase();
- }
- F.done();
-
- auto MatchDecl = [this, Name, Label](DeclContext *DC) -> NamedDecl * {
- auto LRes = DC->lookup(DeclarationName(Name));
- for (auto *I : LRes) {
- if (isa<VarDecl>(I))
- return I;
- if (isa<FunctionDecl>(I)) {
- FunctionDecl *FD = dyn_cast<FunctionDecl>(I);
-
- // All function parameters must match if specified in pragma otherwise,
- // we accept a function found by lookup only if it's the only one.
- if ((Label.TypeList.has_value() &&
- typeListMatchesSymbolLabel(FD, Label)) ||
- (!Label.TypeList.has_value() && LRes.isSingleResult()))
- return FD;
- }
- }
- return nullptr;
- };
-
- // global variable or function in a namespace.
- if (NamespaceDecl *ND = Result.getAsSingle<NamespaceDecl>()) {
- if (ND->getIdentifierNamespace() == Decl::IDNS_Namespace) {
- return MatchDecl(ND);
- }
- }
-
- // data or function member.
- if (CXXRecordDecl *RD = Result.getAsSingle<CXXRecordDecl>()) {
- return MatchDecl(RD);
- }
-
- // either a variable, or a non-overloaded function, or an overloaded
- // function with extern "C" linkage.
- if (!Label.TypeList.has_value()) {
- if (Result.isSingleResult()) {
- NamedDecl *ND = Result.getFoundDecl();
- if (isa<VarDecl>(ND))
- return ND;
- if (FunctionDecl *FD = dyn_cast<FunctionDecl>(ND)) {
- if (!getLangOpts().CPlusPlus || FD->isExternC())
- return FD;
- else
- return nullptr;
- }
- return ND;
- }
- if (Result.isOverloadedResult()) {
- for (auto *Iter : Result) {
- FunctionDecl *FD = dyn_cast<FunctionDecl>(Iter);
- if (FD && FD->isExternC())
- return FD;
- }
- return nullptr;
- }
- return nullptr;
- }
-
- // Loop over all the found decls and see if the arguments match
- // any of the results.
- for (LookupResult::iterator I = Result.begin(); I != Result.end(); ++I) {
- NamedDecl *ND = (*I)->getUnderlyingDecl();
- FunctionDecl *FD = dyn_cast<FunctionDecl>(ND);
- if (FD && typeListMatchesSymbolLabel(FD, Label)) {
- return FD;
- }
- }
- return nullptr;
-}
-
-void Sema::ActOnPragmaExport(NestedNameSpecifier *NestedId,
- SourceLocation NameLoc,
- std::optional<SmallVector<QualType, 4>> &&TypeList,
- Qualifiers CVQual) {
+void Sema::ActOnPragmaExport(IdentifierInfo *IdentId, SourceLocation NameLoc) {
SymbolLabel Label;
+ Label.IdentId = IdentId;
Label.NameLoc = NameLoc;
- Label.CVQual = CVQual;
- Label.TypeList = std::move(TypeList);
- Label.NestedNameId = NestedId;
Label.Used = false;
- NamedDecl *PrevDecl = tryLookupSymbolLabel(Label);
+ NamedDecl *PrevDecl =
+ LookupSingleName(TUScope, IdentId, NameLoc, LookupOrdinaryName);
if (PrevDecl && (isa<FunctionDecl>(PrevDecl) || isa<VarDecl>(PrevDecl))) {
if (PrevDecl->hasExternalFormalLinkage()) {
if (auto *FD = dyn_cast<FunctionDecl>(PrevDecl)) {
@@ -1490,7 +1358,7 @@ void Sema::ActOnPragmaExport(NestedNameSpecifier *NestedId,
Label.Used = true;
}
if (!Label.Used) {
- PendingExportedNames[NestedId->getAsIdentifier()].push_back(Label);
+ PendingExportedNames[IdentId] = Label;
}
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 98ad21314285a..e1dc77b1108bd 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7519,73 +7519,13 @@ bool Sema::isNamedDeclSameAsSymbolLabel(NamedDecl *D,
const DeclContext *Ctx = D->getDeclContext();
// Check the name.
- NestedNameSpecifier *NS = Label.NestedNameId;
- if (NS->getAsIdentifier()->getName() != D->getIdentifier()->getName())
+ if (Label.IdentId != D->getIdentifier())
return false;
- NS = NS->getPrefix();
-
- if (NS) {
- // For ObjC methods and properties, look through categories and use the
- // interface as context.
- if (auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
- if (auto *ID = MD->getClassInterface())
- Ctx = ID;
- } else if (auto *PD = dyn_cast<ObjCPropertyDecl>(D)) {
- if (auto *MD = PD->getGetterMethodDecl())
- if (auto *ID = MD->getClassInterface())
- Ctx = ID;
- } else if (auto *ID = dyn_cast<ObjCIvarDecl>(D)) {
- if (auto *CI = ID->getContainingInterface())
- Ctx = CI;
- }
-
- // Check named contexts.
- if (Ctx->isFunctionOrMethod())
- return false;
-
- DeclarationName NameInScope = D->getDeclName();
- for (; NS && Ctx; Ctx = Ctx->getParent()) {
- // Suppress anonymous namespace.
- if (isa<NamespaceDecl>(Ctx) &&
- cast<NamespaceDecl>(Ctx)->isAnonymousNamespace())
- continue;
-
- // Suppress inline namespace if it doesn't make the result ambiguous.
- if (Ctx->isInlineNamespace() && NameInScope &&
- cast<NamespaceDecl>(Ctx)->isRedundantInlineQualifierFor(NameInScope))
- continue;
- // Skip non-named contexts such as linkage specifications and ExportDecls.
- const NamedDecl *ND = dyn_cast<NamedDecl>(Ctx);
- if (!ND)
- continue;
-
- // Fail if the sequence of nested name identifiers is shorter.
- if (!NS)
- return false;
-
- // Fail if the names are not equal.
- if (NS->getAsIdentifier()->getName() != ND->getIdentifier()->getName())
- return false;
-
- NameInScope = ND->getDeclName();
- NS = NS->getPrefix();
- }
-
- // Fail if the sequence of nested name identifiers is longer.
- // It makes sure that both lists have the same length.
- if (NS)
- return false;
- }
-
- if (isa<VarDecl>(D) && !Label.TypeList.has_value())
+ if (isa<VarDecl>(D))
return true;
if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
// All function parameters match if specified in pragma.
- if (Label.TypeList.has_value())
- return typeListMatchesSymbolLabel(FD, Label);
- // There might be overloaded functions. However, with the available
- // information it cn only be concluded that the functions are the same.
if (!getLangOpts().CPlusPlus || FD->isExternC())
return true;
}
@@ -7601,17 +7541,13 @@ void Sema::ProcessPragmaExport(DeclaratorDecl *NewD) {
return;
auto PendingName = PendingExportedNames.find(IdentName);
if (PendingName != PendingExportedNames.end()) {
- for (auto I = PendingName->second.begin(), E = PendingName->second.end();
- I != E; ++I) {
- auto &Label = *I;
- if (!Label.Used && isNamedDeclSameAsSymbolLabel(NewD, Label)) {
- Label.Used = true;
- if (NewD->hasExternalFormalLinkage())
- mergeVisibilityType(NewD, Label.NameLoc, VisibilityAttr::Default);
- else
- Diag(Label.NameLoc, diag::warn_pragma_not_applied)
- << "export" << NewD;
- }
+ auto &Label = PendingName->second;
+ if (!Label.Used && isNamedDeclSameAsSymbolLabel(NewD, Label)) {
+ Label.Used = true;
+ if (NewD->hasExternalFormalLinkage())
+ mergeVisibilityType(NewD, Label.NameLoc, VisibilityAttr::Default);
+ else
+ Diag(Label.NameLoc, diag::warn_pragma_not_applied) << "export" << NewD;
}
}
}
diff --git a/clang/test/.clang-format-ignore b/clang/test/.clang-format-ignore
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/CodeGen/pragma-export.cpp b/clang/test/CodeGen/pragma-export.cpp
index aa780887bd272..6f5a025e23bd5 100644
--- a/clang/test/CodeGen/pragma-export.cpp
+++ b/clang/test/CodeGen/pragma-export.cpp
@@ -2,84 +2,46 @@
// RUN: %clang_cc1 -x c++ %s -emit-llvm -triple s390x-none-zos -fzos-extensions -fvisibility=hidden -o - | FileCheck %s
// Testing pragma export after decl.
-void f0(void) {}
+extern "C" void f0(void) {}
int v0;
-#pragma export(f0(void))
+#pragma export(f0)
#pragma export(v0)
// Testing pragma export before decl.
-#pragma export(f1(void))
+#pragma export(f1)
#pragma export(v1)
-void f1(void) {}
+extern "C" void f1(void) {}
int v1;
// Testing overloaded functions.
-#pragma export(f2(double, double))
-#pragma export(f2(int))
+#pragma export(f2)
void f2(double, double) {}
-void f2(int) {}
+extern "C" void f2(int) {}
void f2(int, int) {}
-void f3(double) {}
+extern "C" void f3(double) {}
void f3(int, double) {}
void f3(double, double) {}
-#pragma export(f3(double))
-#pragma export(f3(int, double))
+#pragma export(f3)
-void f2(void) {}
+extern "C" void f2b(void) {}
void t0(void) {
- f2();
+ f2b();
}
-// Test type decay in arguments
-
-#pragma export(fd1(int[]))
-#pragma export(fd2(int*))
-#pragma export(fd3(int[]))
-#pragma export(fd4(int*))
-void fd1(int []) { }
-void fd2(int []) { }
-void fd3(int *) { }
-void fd4(int *) { }
-
-
-#pragma export (fd5(int ()))
-#pragma export (fd6(int (*)()))
-#pragma export (fd7(int ()))
-#pragma export (fd8(int (*)()))
-void fd5(int ()) {}
-void fd6(int ()) {}
-void fd7(int (*)()) {}
-void fd8(int (*)()) {}
-
-
// Testing pragma export after decl and usage.
-#pragma export(f2(void))
+#pragma export(f2b)
// Testing pragma export with namespace.
-void f5(void) {}
-void f5a(void) {}
-#pragma export(N0::f2a(void))
+extern "C" void f5(void) {}
+extern "C" void f5a(void) {}
namespace N0 {
-void f0(void) {}
-void f1(void) {}
-void f2(void) {}
-void f3(void) {}
void f5(void) {}
-#pragma export(f0(void))
-#pragma export(N0::f1(void))
-#pragma export(f5(void))
-#pragma export(f0a(void))
-#pragma export(N0::f1a(void))
-#pragma export(f5a(void))
-void f0a(void) {}
-void f1a(void) {}
-void f2a(void) {}
-void f3a(void) {}
+#pragma export(f5)
+#pragma export(f5a)
void f5a(void) {}
} // namespace N0
-#pragma export(N0::f2(void))
void f10(int);
#pragma export(f10)
@@ -88,35 +50,20 @@ void f10(int) {}
// CHECK: @v0 = hidden global i32 0
// CHECK: @v1 = global i32 0
-// CHECK: define hidden void @_Z2f0v()
-// CHECK: define void @_Z2f1v()
-// CHECK: define void @_Z2f2dd(double noundef %0, double noundef %1)
-// CHECK: define void @_Z2f2i(i32 noundef signext %0)
+// CHECK: define hidden void @f0()
+// CHECK: define void @f1()
+// CHECK: define hidden void @_Z2f2dd(double noundef %0, double noundef %1)
+// CHECK: define void @f2(i32 noundef signext %0)
// CHECK: define hidden void @_Z2f2ii(i32 noundef signext %0, i32 noundef signext %1)
-// CHECK: define hidden void @_Z2f3d(double noundef %0)
+// CHECK: define hidden void @f3(double noundef %0)
// CHECK: define hidden void @_Z2f3id(i32 noundef signext %0, double noundef %1)
// CHECK: define hidden void @_Z2f3dd(double noundef %0, double noundef %1)
-// CHECK: define hidden void @_Z2f2v()
+// CHECK: define hidden void @f2b()
// CHECK: define hidden void @_Z2t0v()
-// CHECK: define void @_Z3fd1Pi(ptr noundef %0)
-// CHECK: define void @_Z3fd2Pi(ptr noundef %0)
-// CHECK: define void @_Z3fd3Pi(ptr noundef %0)
-// CHECK: define void @_Z3fd4Pi(ptr noundef %0)
-// CHECK: define void @_Z3fd5PFivE(ptr noundef %0)
-// CHECK: define void @_Z3fd6PFivE(ptr noundef %0)
-// CHECK: define void @_Z3fd7PFivE(ptr noundef %0)
-// CHECK: define void @_Z3fd8PFivE(ptr noundef %0)
-// CHECK: define hidden void @_Z2f5v()
-// CHECK: define hidden void @_Z3f5av()
-// CHECK: define hidden void @_ZN2N02f0Ev()
-// CHECK: define hidden void @_ZN2N02f1Ev()
-// CHECK: define hidden void @_ZN2N02f2Ev()
-// CHECK: define hidden void @_ZN2N02f3Ev()
+// CHECK: define hidden void @f5()
+// CHECK: define hidden void @f5a()
// CHECK: define hidden void @_ZN2N02f5Ev()
-// CHECK: define void @_ZN2N03f0aEv()
-// CHECK: define hidden void @_ZN2N03f1aEv()
-// CHECK: define void @_ZN2N03f2aEv()
-// CHECK: define hidden void @_ZN2N03f3aEv()
-// CHECK: define void @_ZN2N03f5aEv()
-// CHECK: define void @f10(double noundef %0) #0 {
-// CHECK: define hidden void @_Z3f10i(i32 noundef signext %0) #0 {
+// CHECK: define hidden void @_ZN2N03f5aEv()
+// CHECK: define hidden void @f10(double noundef %0)
+// CHECK: define void @_Z3f10i(i32 noundef signext %0)
+
diff --git a/clang/test/Parser/pragma-export.cpp b/clang/test/Parser/pragma-export.cpp
index 5f1656041b3d3..91d2e162bcfec 100644
--- a/clang/test/Parser/pragma-export.cpp
+++ b/clang/test/Parser/pragma-export.cpp
@@ -1,21 +1,16 @@
// RUN: %clang_cc1 -x c++ -triple s390x-ibm-zos -fsyntax-only -verify %s
extern int i;
-#pragma export(:: // expected-warning {{expected identifier in '#pragma export' - ignored}}
-#pragma export(::) // expected-warning {{expected identifier in '#pragma export' - ignored}}
-#pragma export(::i)
+#pragma export( // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export() // expected-warning {{expected identifier in '#pragma export' - ignored}}
+#pragma export(i)
struct S {
static int i;
};
-#pragma export(S:: // expected-warning {{expected identifier in '#pragma export' - ignored}}
-#pragma export(S::i // expected-warning {{missing ')' after '#pragma export' - ignoring}}
-#pragma export(S::i)
+#pragma export(S::i) // expected-warning {{missing ')' after '#pragma export' - ignoring}}
void f(int);
void f(double, double);
-#pragma export(f( // expected-warning {{expected identifier in '#pragma export' - ignored}}
-#pragma export(f() // expected-warning {{missing ')' after '#pragma export' - ignoring}}
-#pragma export(f(int) // expected-warning {{missing ')' after '#pragma export' - ignoring}}
-#pragma export(f(double,) // expected-warning {{missing ')' after '#pragma export' - ignoring}}
-#pragma export(f(double,double))
+#pragma export(f // expected-warning {{missing ')' after '#pragma export' - ignoring}}
+#pragma export(f( // expected-warning {{missing ')' after '#pragma export' - ignoring}}
diff --git a/clang/test/Sema/pragma-export-failing.cpp b/clang/test/Sema/pragma-export-failing.cpp
index 908395c899cf5..9763adcff5f16 100644
--- a/clang/test/Sema/pragma-export-failing.cpp
+++ b/clang/test/Sema/pragma-export-failing.cpp
@@ -1,11 +1,11 @@
// REQUIRES: systemz-registered-target
// RUN: %clang_cc1 -x c++ -triple s390x-none-zos -fzos-extensions %s -fsyntax-only -verify
-#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}}
+#pragma export(f0(int)) // expected-warning{{missing ')' after '#pragma export' - ignoring}}
+#pragma export(f3(double, double, double)) // expected-warning{{missing ')' after '#pragma export' - ignoring}}
-#pragma export(N::sf1(void)) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf1'}}
-#pragma export(N::s1) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's1'}}
+#pragma export(N::sf1(void)) // expected-warning{{missing ')' after '#pragma export' - ignoring}}
+#pragma export(N::s1) // expected-warning{{missing ')' after '#pragma export' - ignoring}}
namespace N {
static void sf1(void) {}
static int s1;
@@ -14,8 +14,8 @@ static void sf0(void) {}
int v0;
static int s0;
}
-#pragma export(N::sf0(void)) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 'sf0'}}
-#pragma export(N::s0) // expected-warning{{#pragma export is applicable to symbols with external linkage only; not applied to 's0'}}
+#pragma export(N::sf0(void)) // expected-warning{{missing ')' after '#pragma export' - ignoring}}
+#pragma export(N::s0) // expected-warning{{missing ')' after '#pragma export' - ignoring}}
void f10(int);
#pragma export(f10) // expected-warning{{failed to resolve '#pragma export' to a declaration}}
>From 66177a45b005e8f9d00db19b4db38de677e5e455 Mon Sep 17 00:00:00 2001
From: Sean Perry <perry at ca.ibm.com>
Date: Thu, 26 Jun 2025 20:44:07 +0000
Subject: [PATCH 3/4] update release notes
---
clang/docs/ReleaseNotes.rst | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 91e720db67547..8d1605ee789e6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1005,7 +1005,9 @@ AVR Support
SystemZ Support
^^^^^^^^^^^^^^^
-- Add support for `#pragma export` for z/OS
+- Add support for `#pragma export` for z/OS. This is a pragma used to export function and variable
+ with external linkage from shared libraries. It provides compatibility with the IBM XL C/C++
+ compiler.
DWARF Support in Clang
----------------------
>From 8da74175dcac9003bef728a82e1c18c2ea2e8b27 Mon Sep 17 00:00:00 2001
From: Sean Perry <perry at ca.ibm.com>
Date: Fri, 27 Jun 2025 05:00:38 +0000
Subject: [PATCH 4/4] re-org the checks to simplify the logic
---
clang/include/clang/Parse/Parser.h | 5 --
clang/include/clang/Sema/Sema.h | 7 ---
clang/lib/Parse/ParsePragma.cpp | 84 +---------------------------
clang/lib/Sema/SemaAttr.cpp | 52 +++++++++--------
clang/lib/Sema/SemaDecl.cpp | 3 -
clang/test/CodeGen/pragma-export.cpp | 4 +-
6 files changed, 33 insertions(+), 122 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 36e513d75abef..4b4ccf63b2289 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7137,11 +7137,6 @@ 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
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f6641d5afa1cc..2b0ce3122063a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2294,13 +2294,6 @@ class Sema final : public SemaBase {
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);
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index c10cb6a0c7667..6384a79767eca 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -1402,88 +1402,9 @@ 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;
@@ -1524,11 +1445,8 @@ bool Parser::zOSHandlePragmaHelper(tok::TokenKind PragmaKind) {
// 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
+ if (PragmaName != "export")
PP.Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
<< PragmaName;
} else if (Tok.isNot(tok::eof)) {
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 96a0dd69078b9..af7d7636d8af2 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -1336,30 +1336,38 @@ void Sema::ActOnPragmaExport(IdentifierInfo *IdentId, SourceLocation NameLoc) {
NamedDecl *PrevDecl =
LookupSingleName(TUScope, IdentId, NameLoc, LookupOrdinaryName);
- if (PrevDecl && (isa<FunctionDecl>(PrevDecl) || isa<VarDecl>(PrevDecl))) {
- if (PrevDecl->hasExternalFormalLinkage()) {
- if (auto *FD = dyn_cast<FunctionDecl>(PrevDecl)) {
- if (FD->hasBody())
- Diag(NameLoc, diag::warn_pragma_not_applied_to_defined_symbol)
- << "export";
- else
- mergeVisibilityType(PrevDecl, NameLoc, VisibilityAttr::Default);
- } else {
- auto *VD = dyn_cast<VarDecl>(PrevDecl);
- assert(VD);
- if (VD->hasDefinition() == VarDecl::Definition)
- Diag(NameLoc, diag::warn_pragma_not_applied_to_defined_symbol)
- << "export";
- else
- mergeVisibilityType(PrevDecl, NameLoc, VisibilityAttr::Default);
- }
- } else
- Diag(NameLoc, diag::warn_pragma_not_applied) << "export" << PrevDecl;
- Label.Used = true;
- }
- if (!Label.Used) {
+ if (!PrevDecl) {
PendingExportedNames[IdentId] = Label;
+ return;
+ }
+
+ if (auto *FD = dyn_cast<FunctionDecl>(PrevDecl->getCanonicalDecl())) {
+ if (getLangOpts().CPlusPlus && !FD->isExternC()) {
+ PendingExportedNames[IdentId] = Label;
+ return;
+ }
+ if (!FD->hasExternalFormalLinkage()) {
+ Diag(NameLoc, diag::warn_pragma_not_applied) << "export" << PrevDecl;
+ return;
+ }
+ if (FD->hasBody()) {
+ Diag(NameLoc, diag::warn_pragma_not_applied_to_defined_symbol)
+ << "export";
+ return;
+ }
+ } else if (auto *VD = dyn_cast<VarDecl>(PrevDecl->getCanonicalDecl())) {
+ if (!VD->hasExternalFormalLinkage()) {
+ Diag(NameLoc, diag::warn_pragma_not_applied) << "export" << PrevDecl;
+ return;
+ }
+ if (VD->hasDefinition() == VarDecl::Definition) {
+ Diag(NameLoc, diag::warn_pragma_not_applied_to_defined_symbol)
+ << "export";
+ return;
+ }
}
+ mergeVisibilityType(PrevDecl->getCanonicalDecl(), NameLoc,
+ VisibilityAttr::Default);
}
typedef std::vector<std::pair<unsigned, SourceLocation> > VisStack;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 06b09997698f7..702e605defdf6 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7519,8 +7519,6 @@ static void emitReadOnlyPlacementAttrWarning(Sema &S, const VarDecl *VD) {
// Checks if the given label matches the named declaration.
bool Sema::isNamedDeclSameAsSymbolLabel(NamedDecl *D,
Sema::SymbolLabel &Label) {
- const DeclContext *Ctx = D->getDeclContext();
-
// Check the name.
if (Label.IdentId != D->getIdentifier())
return false;
@@ -7528,7 +7526,6 @@ bool Sema::isNamedDeclSameAsSymbolLabel(NamedDecl *D,
if (isa<VarDecl>(D))
return true;
if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
- // All function parameters match if specified in pragma.
if (!getLangOpts().CPlusPlus || FD->isExternC())
return true;
}
diff --git a/clang/test/CodeGen/pragma-export.cpp b/clang/test/CodeGen/pragma-export.cpp
index 6f5a025e23bd5..531afbd659234 100644
--- a/clang/test/CodeGen/pragma-export.cpp
+++ b/clang/test/CodeGen/pragma-export.cpp
@@ -64,6 +64,6 @@ void f10(int) {}
// CHECK: define hidden void @f5a()
// CHECK: define hidden void @_ZN2N02f5Ev()
// CHECK: define hidden void @_ZN2N03f5aEv()
-// CHECK: define hidden void @f10(double noundef %0)
-// CHECK: define void @_Z3f10i(i32 noundef signext %0)
+// CHECK: define void @f10(double noundef %0)
+// CHECK: define hidden void @_Z3f10i(i32 noundef signext %0)
More information about the cfe-commits
mailing list