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 <pe...@ca.ibm.com> Date: Fri, 23 May 2025 04:36:46 +0000 Subject: [PATCH 1/3] #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 <pe...@ca.ibm.com> Date: Thu, 26 Jun 2025 20:30:22 +0000 Subject: [PATCH 2/3] 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 <pe...@ca.ibm.com> Date: Thu, 26 Jun 2025 20:44:07 +0000 Subject: [PATCH 3/3] 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 ---------------------- _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits