https://github.com/Vipul-Cariappa updated https://github.com/llvm/llvm-project/pull/210893
>From b2d904db0c0a2832ced8979687bdbefef6777984 Mon Sep 17 00:00:00 2001 From: Vipul Cariappa <[email protected]> Date: Mon, 20 Jul 2026 11:02:25 +0530 Subject: [PATCH] [clang] delay parsing of all templated & internal-linkage functions The function bodies are only parsed if the function is used. Otherwise it remains unparsed. --- .../clang/Basic/DiagnosticDriverKinds.td | 5 + clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Options/Options.td | 6 + clang/include/clang/Sema/Sema.h | 26 +- clang/lib/Driver/ToolChains/Clang.cpp | 4 + clang/lib/Frontend/CompilerInvocation.cpp | 25 + clang/lib/Parse/ParseCXXInlineMethods.cpp | 27 +- clang/lib/Parse/Parser.cpp | 119 ++-- clang/lib/Sema/Sema.cpp | 6 + clang/lib/Sema/SemaExpr.cpp | 9 + clang/lib/Sema/SemaLookup.cpp | 100 ++- clang/lib/Sema/SemaTemplate.cpp | 23 + .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 10 + clang/test/Parser/ParseFunctionsOndemand.cpp | 602 ++++++++++++++++++ 14 files changed, 880 insertions(+), 83 deletions(-) create mode 100644 clang/test/Parser/ParseFunctionsOndemand.cpp diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index ae3eecc60fc78..995e2153c81b3 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -755,6 +755,11 @@ def warn_drv_fine_grained_bitfield_accesses_ignored : Warning< "option '-ffine-grained-bitfield-accesses' cannot be enabled together with a sanitizer; flag ignored">, InGroup<OptionIgnored>; +def warn_drv_parse_functions_ondemand_ignored : Warning< + "option '-fparse-functions-ondemand' is not supported when generating a " + "%select{precompiled header|module}0; flag ignored">, + InGroup<OptionIgnored>; + def err_drv_profile_instrument_use_path_with_no_kind : Error< "option '-fprofile-instrument-use-path=' requires -fprofile-instrument-use=<kind>">; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 3d63b9677e4df..648832c72be32 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -340,6 +340,7 @@ ENUM_LANGOPT(AddressSpaceMapMangling , AddrSpaceMapMangling, 2, ASMM_Target, Not LANGOPT(IncludeDefaultHeader, 1, 0, NotCompatible, "Include default header file for OpenCL") LANGOPT(DeclareOpenCLBuiltins, 1, 0, NotCompatible, "Declare OpenCL builtin functions") LANGOPT(DelayedTemplateParsing , 1, 0, Benign, "delayed template parsing") +LANGOPT(ParseFunctionsOnDemand , 1, 0, Benign, "on-demand function parsing") LANGOPT(BlocksRuntimeOptional , 1, 0, NotCompatible, "optional blocks runtime") LANGOPT( CompleteMemberPointers, 1, 0, NotCompatible, diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 41848b18f2e1b..81682b2c0b3bf 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3618,6 +3618,12 @@ defm delayed_template_parsing : BoolFOption<"delayed-template-parsing", "Parse templated function definitions at the end of the translation unit">, NegFlag<SetFalse, [], [], "Disable delayed template parsing">, BothFlags<[], [ClangOption, CLOption]>>; +defm parse_functions_ondemand : BoolFOption<"parse-functions-ondemand", + LangOpts<"ParseFunctionsOnDemand">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption, CC1Option], + "Parse internal-linkage function definitions on demand">, + NegFlag<SetFalse, [], [], "Disable on-demand function parsing">, + BothFlags<[], [ClangOption, CLOption]>>; def fms_memptr_rep_EQ : Joined<["-"], "fms-memptr-rep=">, Group<f_Group>, Visibility<[ClangOption, CC1Option]>, Values<"single,multiple,virtual">, NormalizedValuesScope<"LangOptions">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 8a30f6319bcef..6030f22016e34 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -12554,6 +12554,7 @@ class Sema final : public SemaBase { void MarkAsLateParsedTemplate(FunctionDecl *FD, Decl *FnD, CachedTokens &Toks); void UnmarkAsLateParsedTemplate(FunctionDecl *FD); + bool ParseLateFunctionDefinition(FunctionDecl *FD); bool IsInsideALocalClassWithinATemplateFunction(); /// We've found a use of a templated declaration that would trigger an @@ -14229,27 +14230,18 @@ class Sema final : public SemaBase { return; // Restore the set of pending vtables. - assert(S.VTableUses.empty() && - "VTableUses should be empty before it is discarded."); S.VTableUses.swap(S.SavedVTableUses.back()); + S.VTableUses.insert(S.VTableUses.end(), S.SavedVTableUses.back().begin(), + S.SavedVTableUses.back().end()); S.SavedVTableUses.pop_back(); // Restore the set of pending implicit instantiations. - if ((S.TUKind != TU_Prefix || !S.LangOpts.PCHInstantiateTemplates) && - AtEndOfTU) { - assert(S.PendingInstantiations.empty() && - "PendingInstantiations should be empty before it is discarded."); - S.PendingInstantiations.swap(S.SavedPendingInstantiations.back()); - S.SavedPendingInstantiations.pop_back(); - } else { - // Template instantiations in the PCH may be delayed until the TU. - S.PendingInstantiations.swap(S.SavedPendingInstantiations.back()); - S.PendingInstantiations.insert( - S.PendingInstantiations.end(), - S.SavedPendingInstantiations.back().begin(), - S.SavedPendingInstantiations.back().end()); - S.SavedPendingInstantiations.pop_back(); - } + S.PendingInstantiations.swap(S.SavedPendingInstantiations.back()); + S.PendingInstantiations.insert( + S.PendingInstantiations.end(), + S.SavedPendingInstantiations.back().begin(), + S.SavedPendingInstantiations.back().end()); + S.SavedPendingInstantiations.pop_back(); } GlobalEagerInstantiationScope(const GlobalEagerInstantiationScope &) = diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 660e61d7c5de3..734de2e98d1f6 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7846,6 +7846,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("-fdelayed-template-parsing"); } + if (Args.hasFlag(options::OPT_fparse_functions_ondemand, + options::OPT_fno_parse_functions_ondemand, false)) + CmdArgs.push_back("-fparse-functions-ondemand"); + if (Args.hasFlag(options::OPT_fpch_validate_input_files_content, options::OPT_fno_pch_validate_input_files_content, false)) CmdArgs.push_back("-fvalidate-ast-input-files-content"); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 6562cb3a9b135..baab5bd4d83cc 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -4126,6 +4126,11 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, #include "clang/Options/Options.inc" #undef LANG_OPTION_WITH_MARSHALLING + // delayed parsing has incompatible lookup semantics with ondemand parsing + if (Opts.ParseFunctionsOnDemand && Opts.DelayedTemplateParsing) + Diags.Report(diag::err_drv_argument_not_allowed_with) + << "-fparse-functions-ondemand" << "-fdelayed-template-parsing"; + // "Modules semantics" (e.g. cross-translation-unit declaration merging) are // needed for both Clang (header) modules and C++20 modules, so enable them // for either. @@ -5140,6 +5145,26 @@ bool CompilerInvocation::CreateFromArgsImpl( if (Res.getFrontendOpts().ProgramAction == frontend::RewriteObjC) LangOpts.ObjCExceptions = 1; + if (LangOpts.ParseFunctionsOnDemand) { + switch (Res.getFrontendOpts().ProgramAction) { + case frontend::GeneratePCH: + LangOpts.ParseFunctionsOnDemand = false; + Diags.Report(diag::warn_drv_parse_functions_ondemand_ignored) + << /*precompiled header*/ 0; + break; + case frontend::GenerateModule: + case frontend::GenerateModuleInterface: + case frontend::GenerateReducedModuleInterface: + case frontend::GenerateHeaderUnit: + LangOpts.ParseFunctionsOnDemand = false; + Diags.Report(diag::warn_drv_parse_functions_ondemand_ignored) + << /*module*/ 1; + break; + default: + break; + } + } + for (auto Warning : Res.getDiagnosticOpts().Warnings) { if (Warning == "misexpect" && !Diags.isIgnored(diag::warn_profile_data_misexpect, SourceLocation())) { diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp index be531e567046e..8a589970a99bf 100644 --- a/clang/lib/Parse/ParseCXXInlineMethods.cpp +++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp @@ -144,24 +144,31 @@ NamedDecl *Parser::ParseCXXInlineMethodDef( return FnD; } + FunctionDecl *FD = FnD ? FnD->getAsFunction() : nullptr; + bool IsDelayedTemplateFunction = + Actions.CurContext->isDependentContext() || + (TemplateInfo.Kind != ParsedTemplateKind::NonTemplate && + TemplateInfo.Kind != ParsedTemplateKind::ExplicitSpecialization); + // In delayed template parsing mode, if we are within a class template // or if we are about to parse function member template then consume // the tokens and store them for parsing at the end of the translation unit. - if (getLangOpts().DelayedTemplateParsing && - D.getFunctionDefinitionKind() == FunctionDefinitionKind::Definition && + // In on-demand parsing mode, internal-linkage member definitions are also + // handled in the same way. + if (D.getFunctionDefinitionKind() == FunctionDefinitionKind::Definition && !D.getDeclSpec().hasConstexprSpecifier() && - !(FnD && FnD->getAsFunction() && - FnD->getAsFunction()->getReturnType()->getContainedAutoType()) && - ((Actions.CurContext->isDependentContext() || - (TemplateInfo.Kind != ParsedTemplateKind::NonTemplate && - TemplateInfo.Kind != ParsedTemplateKind::ExplicitSpecialization)) && - !Actions.IsInsideALocalClassWithinATemplateFunction())) { + !(FD && FD->getReturnType()->getContainedAutoType()) && + !Actions.IsInsideALocalClassWithinATemplateFunction() && + ((getLangOpts().DelayedTemplateParsing && IsDelayedTemplateFunction) || + (getLangOpts().ParseFunctionsOnDemand && + ((FD && !FD->isExternallyVisible() && + FD->isDefinedOutsideFunctionOrMethod()) || + IsDelayedTemplateFunction)))) { CachedTokens Toks; LexTemplateFunctionForLateParsing(Toks); - if (FnD) { - FunctionDecl *FD = FnD->getAsFunction(); + if (FD) { Actions.CheckForFunctionRedefinition(FD); Actions.MarkAsLateParsedTemplate(FD, FnD, Toks); } diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 6a21acd9dc4ef..c6b6c5cc4a890 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -26,6 +26,7 @@ #include "clang/Sema/ParsedTemplate.h" #include "clang/Sema/Scope.h" #include "clang/Sema/SemaCodeCompletion.h" +#include "clang/Sema/SemaOpenMP.h" #include "llvm/ADT/STLForwardCompat.h" #include "llvm/Support/Path.h" #include "llvm/Support/TimeProfiler.h" @@ -1234,51 +1235,82 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D, Diag(AL.getLoc(), diag::warn_attribute_on_function_definition) << AL; } - // In delayed template parsing mode, for function template we consume the - // tokens and store them for late parsing at the end of the translation unit. - if (getLangOpts().DelayedTemplateParsing && Tok.isNot(tok::equal) && - TemplateInfo.Kind == ParsedTemplateKind::Template && - LateParsedAttrs->empty() && Actions.canDelayFunctionBody(D)) { - MultiTemplateParamsArg TemplateParameterLists(*TemplateInfo.TemplateParams); + MultiTemplateParamsArg TemplateParameterLists( + TemplateInfo.TemplateParams ? *TemplateInfo.TemplateParams + : MultiTemplateParamsArg{}); - ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope | - Scope::CompoundStmtScope); - Scope *ParentScope = getCurScope()->getParent(); + bool CanDelayFunctionBody = Actions.canDelayFunctionBody(D); + bool IsDelayedTemplateFunction = + getLangOpts().DelayedTemplateParsing && + TemplateInfo.Kind == ParsedTemplateKind::Template && CanDelayFunctionBody; + bool ShouldProbeInternalLinkage = getLangOpts().ParseFunctionsOnDemand && + !CurParsedObjCImpl && CanDelayFunctionBody; - D.setFunctionDefinitionKind(FunctionDefinitionKind::Definition); - Decl *DP = Actions.HandleDeclarator(ParentScope, D, - TemplateParameterLists); - D.complete(DP); - D.getMutableDeclSpec().abort(); + // Enter a scope for the function body. + ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope | + Scope::CompoundStmtScope); + Scope *ParentScope = getCurScope()->getParent(); - if (SkipFunctionBodies && (!DP || Actions.canSkipFunctionBody(DP)) && - trySkippingFunctionBody()) { - BodyScope.Exit(); - return Actions.ActOnSkippedFunctionBody(DP); - } + D.setFunctionDefinitionKind(FunctionDefinitionKind::Definition); + + // Check and handle if we are in an `omp begin/end declare variant` scope. + SmallVector<FunctionDecl *, 4> Bases; + if (getLangOpts().OpenMP && Actions.OpenMP().isInOpenMPDeclareVariantScope()) + Actions.OpenMP().ActOnStartOfFunctionDefinitionInOpenMPDeclareVariantScope( + ParentScope, D, TemplateParameterLists, Bases); + + Decl *FuncDecl = + Actions.HandleDeclarator(ParentScope, D, TemplateParameterLists); + + // Break out of the ParsingDeclarator context before we parse the body. + D.complete(FuncDecl); + + // Break out of the ParsingDeclSpec context, too. This const_cast is + // safe because we're always the sole owner. + D.getMutableDeclSpec().abort(); + + // Finish the OpenMP declare-variant handling on any early-return path. + auto FinishDeclareVariant = [&](Decl *D) { + if (!Bases.empty()) + Actions.OpenMP() + .ActOnFinishedFunctionDefinitionInOpenMPDeclareVariantScope(D, Bases); + }; + + // For function templates in delayed template parsing mode, and for + // internal-linkage functions in on-demand parsing mode, consume the tokens + // and store them for late parsing. + if (Tok.isNot(tok::equal) && LateParsedAttrs->empty() && + (IsDelayedTemplateFunction || ShouldProbeInternalLinkage)) { + FunctionDecl *FnD = FuncDecl ? FuncDecl->getAsFunction() : nullptr; + bool ShouldDelayFunction = + !FnD->isUsed(/*CheckUsedAttr=*/true) && + FnD->isDefinedOutsideFunctionOrMethod() && + (IsDelayedTemplateFunction || (FnD && !FnD->isExternallyVisible()) || + TemplateInfo.Kind == ParsedTemplateKind::Template); + if (ShouldDelayFunction) { + if (SkipFunctionBodies && + (!FuncDecl || Actions.canSkipFunctionBody(FuncDecl)) && + trySkippingFunctionBody()) { + BodyScope.Exit(); + FinishDeclareVariant(FuncDecl); + return Actions.ActOnSkippedFunctionBody(FuncDecl); + } - CachedTokens Toks; - LexTemplateFunctionForLateParsing(Toks); + CachedTokens Toks; + LexTemplateFunctionForLateParsing(Toks); - if (DP) { - FunctionDecl *FnD = DP->getAsFunction(); - Actions.CheckForFunctionRedefinition(FnD); - Actions.MarkAsLateParsedTemplate(FnD, DP, Toks); + if (FnD) { + Actions.CheckForFunctionRedefinition(FnD); + Actions.MarkAsLateParsedTemplate(FnD, FuncDecl, Toks); + } + FinishDeclareVariant(FuncDecl); + return FuncDecl; } - return DP; } + if (CurParsedObjCImpl && !TemplateInfo.TemplateParams && (Tok.is(tok::l_brace) || Tok.is(tok::kw_try) || Tok.is(tok::colon)) && Actions.CurContext->isTranslationUnit()) { - ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope | - Scope::CompoundStmtScope); - Scope *ParentScope = getCurScope()->getParent(); - - D.setFunctionDefinitionKind(FunctionDefinitionKind::Definition); - Decl *FuncDecl = Actions.HandleDeclarator(ParentScope, D, - MultiTemplateParamsArg()); - D.complete(FuncDecl); - D.getMutableDeclSpec().abort(); if (FuncDecl) { // Consume the tokens and store them for later parsing. StashAwayMethodOrFunctionBodyTokens(FuncDecl); @@ -1288,10 +1320,6 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D, // FIXME: Should we really fall through here? } - // Enter a scope for the function body. - ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope | - Scope::CompoundStmtScope); - // Parse function body eagerly if it is either '= delete;' or '= default;' as // ActOnStartOfFunctionDef needs to know whether the function is deleted. StringLiteral *DeletedMessage = nullptr; @@ -1336,11 +1364,9 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D, // Tell the actions module that we have entered a function definition with the // specified Declarator for the function. SkipBodyInfo SkipBody; - Decl *Res = Actions.ActOnStartOfFunctionDef(getCurScope(), D, - TemplateInfo.TemplateParams - ? *TemplateInfo.TemplateParams - : MultiTemplateParamsArg(), + Decl *Res = Actions.ActOnStartOfFunctionDef(getCurScope(), FuncDecl, &SkipBody, BodyKind); + FinishDeclareVariant(Res); if (SkipBody.ShouldSkip) { // Do NOT enter SkipFunctionBody if we already consumed the tokens. @@ -1361,13 +1387,6 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D, return Res; } - // Break out of the ParsingDeclarator context before we parse the body. - D.complete(Res); - - // Break out of the ParsingDeclSpec context, too. This const_cast is - // safe because we're always the sole owner. - D.getMutableDeclSpec().abort(); - if (BodyKind != Sema::FnBodyKind::Other) { Actions.SetFunctionBodyKind(Res, KWLoc, BodyKind, DeletedMessage); Stmt *GeneratedBody = Res ? Res->getBody() : nullptr; diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 9f962912148ab..cdc17bae67a18 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1276,6 +1276,12 @@ void Sema::ActOnEndOfTranslationUnitFragment(TUFragmentKind Kind) { { llvm::TimeTraceScope TimeScope("PerformPendingInstantiations"); PerformPendingInstantiations(); + + // PerformPendingInstantiations can serialize more definitions + while (!VTableUses.empty() || !PendingInstantiations.empty()) { + DefineUsedVTables(); + PerformPendingInstantiations(); + } } emitDeferredDiags(); diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 67d9ac4ad5cff..6b1aa597d2754 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -19018,6 +19018,15 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func, if (getLangOpts().CUDA) CUDA().CheckCall(Loc, Func); + if (const FunctionDecl *Definition; + getLangOpts().ParseFunctionsOnDemand && NeedDefinition && + Func->hasBody(Definition) && !Definition->getBody() && + Definition->isLateTemplateParsed()) { + FunctionDecl *LateParsedFD = const_cast<FunctionDecl *>(Definition); + LateParsedFD->setInstantiationIsPending(true); + PendingInstantiations.push_back(std::make_pair(LateParsedFD, Loc)); + } + // If we need a definition, try to create one. if (NeedDefinition && !Func->getBody()) { runWithSufficientStackSpace(Loc, [&] { diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp index 43129800e9813..38280a9e82546 100644 --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTContext.h" +#include "clang/AST/ASTLambda.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" @@ -1125,6 +1126,85 @@ static void DeclareImplicitMemberFunctionsWithName(Sema &S, } } +static bool IsVisibleAtLateParsedLookupPoint(Sema &S, SourceLocation LookupLoc, + NamedDecl *D) { + if (!S.getLangOpts().ParseFunctionsOnDemand) + return true; + + if (D->isImplicit()) + return true; + + // We are substituting template types. + // Example: + // template <typename T> + // typename T::result foo(T &callback) { return callback(); } + // struct CallMe { + // using result = int; + // result operator()() { return 1; } + // } callback; + // foo(callback); + // + // Lookup of CallMe::result needs to succeesd even thought + // CallMe::result is declared "after" the template being parsed + if (!S.CodeSynthesisContexts.empty() && isa<TypeDecl>(D)) { + switch (S.CodeSynthesisContexts.back().Kind) { + case Sema::CodeSynthesisContext::ExplicitTemplateArgumentSubstitution: + case Sema::CodeSynthesisContext::DeducedTemplateArgumentSubstitution: + case Sema::CodeSynthesisContext::LambdaExpressionSubstitution: + case Sema::CodeSynthesisContext::PriorTemplateArgumentSubstitution: + case Sema::CodeSynthesisContext::ConstraintSubstitution: + case Sema::CodeSynthesisContext::ParameterMappingSubstitution: + return true; + default: + break; + } + } + + // lambda call operator is accessible from everywhere + if (auto *CXXMD = dyn_cast<CXXMethodDecl>(D); + CXXMD && isLambdaCallOperator(CXXMD)) + return true; + + auto *FD = dyn_cast_or_null<FunctionDecl>(S.CurContext); + if (!FD || !FD->isLateTemplateParsed()) + return true; + + SourceLocation DeclLoc = D->getCanonicalDecl()->getLocation(); + + // can this be an assert on valid source locations at this point? + if (DeclLoc.isInvalid() || LookupLoc.isInvalid()) + return true; + + if (DeclLoc == LookupLoc || + S.getSourceManager().isBeforeInTranslationUnit(DeclLoc, LookupLoc)) + return true; + + const CXXRecordDecl *EnclosingRD = nullptr; + if (auto *CXXMD = dyn_cast<CXXMethodDecl>(FD)) + EnclosingRD = CXXMD->getParent(); + else if (FD->getFriendObjectKind() != Decl::FriendObjectKind::FOK_None) + EnclosingRD = dyn_cast<CXXRecordDecl>(FD->getLexicalDeclContext()); + + for (auto *RD = dyn_cast_or_null<RecordDecl>( + EnclosingRD ? EnclosingRD->getDefinition() : nullptr); + RD; RD = isa_and_nonnull<RecordDecl>(RD->getParent()) + ? dyn_cast<RecordDecl>(RD->getParent())->getDefinition() + : nullptr) { + if (RD->getEndLoc().isInvalid() || + S.getSourceManager().isBeforeInTranslationUnit(DeclLoc, + RD->getEndLoc())) + return true; + } + return false; +} + +static bool IsVisibleAtLateParsedLookupPoint(Sema &S, LookupResult &R, + NamedDecl *D) { + auto res = + IsVisibleAtLateParsedLookupPoint(S, R.getLookupNameInfo().getLoc(), D); + return res; +} + // Adds all qualifying matches for a name within a decl context to the // given lookup result. Returns true if any matches were found. static bool LookupDirect(Sema &S, LookupResult &R, const DeclContext *DC) { @@ -1139,8 +1219,10 @@ static bool LookupDirect(Sema &S, LookupResult &R, const DeclContext *DC) { DeclContext::lookup_result DR = DC->lookup(R.getLookupName()); for (NamedDecl *D : DR) { if ((D = R.getAcceptableDecl(D))) { - R.addDecl(D); - Found = true; + if (IsVisibleAtLateParsedLookupPoint(S, R, D)) { + R.addDecl(D); + Found = true; + } } } @@ -1347,7 +1429,8 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) { bool SearchNamespaceScope = true; // Check whether the IdResolver has anything in this scope. for (; I != IEnd && S->isDeclScope(*I); ++I) { - if (NamedDecl *ND = R.getAcceptableDecl(*I)) { + if (NamedDecl *ND = R.getAcceptableDecl(*I); + ND && IsVisibleAtLateParsedLookupPoint(*this, R, ND)) { if (NameKind == LookupRedeclarationWithLinkage && !(*I)->isTemplateParameter()) { // If it's a template parameter, we still find it, so we can diagnose @@ -1500,7 +1583,8 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) { // Check whether the IdResolver has anything in this scope. bool Found = false; for (; I != IEnd && S->isDeclScope(*I); ++I) { - if (NamedDecl *ND = R.getAcceptableDecl(*I)) { + if (NamedDecl *ND = R.getAcceptableDecl(*I); + ND && IsVisibleAtLateParsedLookupPoint(*this, R, ND)) { // We found something. Look for anything else in our scope // with this same name and in an acceptable identifier // namespace, so that we can construct an overload set if we @@ -2243,7 +2327,8 @@ bool Sema::LookupName(LookupResult &R, Scope *S, bool AllowBuiltinCreation, for (IdentifierResolver::iterator I = IdResolver.begin(Name), IEnd = IdResolver.end(); I != IEnd; ++I) - if (NamedDecl *D = R.getAcceptableDecl(*I)) { + if (NamedDecl *D = R.getAcceptableDecl(*I); + D && IsVisibleAtLateParsedLookupPoint(*this, R, D)) { if (NameKind == LookupRedeclarationWithLinkage) { // Determine whether this (or a previous) declaration is // out-of-scope. @@ -2297,7 +2382,8 @@ bool Sema::LookupName(LookupResult &R, Scope *S, bool AllowBuiltinCreation, } // If the declaration is in the right namespace and visible, add it. - if (NamedDecl *LastD = R.getAcceptableDecl(*LastI)) + if (NamedDecl *LastD = R.getAcceptableDecl(*LastI); + LastD && IsVisibleAtLateParsedLookupPoint(*this, R, LastD)) R.addDecl(LastD); } @@ -3919,6 +4005,8 @@ void Sema::ArgumentDependentLookup(DeclarationName Name, SourceLocation Loc, if (!isa<FunctionDecl>(Underlying) && !isa<FunctionTemplateDecl>(Underlying)) continue; + if (!IsVisibleAtLateParsedLookupPoint(*this, Loc, D)) + continue; // The declaration is visible to argument-dependent lookup if either // it's ordinarily visible or declared as a friend in an associated diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 643392833759d..2adba04703d98 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -11758,6 +11758,14 @@ void Sema::MarkAsLateParsedTemplate(FunctionDecl *FD, Decl *FnD, LateParsedTemplateMap.insert(std::make_pair(FD, std::move(LPT))); FD->setLateTemplateParsed(true); + + if (getLangOpts().ParseFunctionsOnDemand && !FD->instantiationIsPending() && + FD->isUsed(/*CheckUsedAttr=*/false)) { + // This function already has a declaration, and the declaration has been + // MarkFunctionReferenced + FD->setInstantiationIsPending(true); + PendingInstantiations.push_back(std::make_pair(FD, FD->getLocation())); + } } void Sema::UnmarkAsLateParsedTemplate(FunctionDecl *FD) { @@ -11766,6 +11774,21 @@ void Sema::UnmarkAsLateParsedTemplate(FunctionDecl *FD) { FD->setLateTemplateParsed(false); } +bool Sema::ParseLateFunctionDefinition(FunctionDecl *FD) { + if (!LateTemplateParser) + return false; + + if (FD->isFromASTFile() && ExternalSource) + ExternalSource->ReadLateParsedTemplates(LateParsedTemplateMap); + + auto LPTIter = LateParsedTemplateMap.find(FD); + if (LPTIter == LateParsedTemplateMap.end()) + return false; + + LateTemplateParser(OpaqueParser, *LPTIter->second); + return FD->getBody() != nullptr; +} + bool Sema::IsInsideALocalClassWithinATemplateFunction() { DeclContext *DC = CurContext; diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index f1f97ca125f46..4ad1de0aad34f 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -7275,6 +7275,16 @@ void Sema::PerformPendingInstantiations(bool LocalOnly, bool AtEndOfTU) { // Instantiate function definitions if (FunctionDecl *Function = dyn_cast<FunctionDecl>(Inst.first)) { + if (Function->isLateTemplateParsed() && + (Function->getTemplatedKind() == FunctionDecl::TK_NonTemplate || + Function->getTemplateSpecializationKind() == + TSK_ExplicitSpecialization)) { + ParseLateFunctionDefinition(Function); + if (Function->isDefined()) + Function->setInstantiationIsPending(false); + continue; + } + bool DefinitionRequired = Function->getTemplateSpecializationKind() == TSK_ExplicitInstantiationDefinition; if (Function->isMultiVersion()) { diff --git a/clang/test/Parser/ParseFunctionsOndemand.cpp b/clang/test/Parser/ParseFunctionsOndemand.cpp new file mode 100644 index 0000000000000..f21d721a1488e --- /dev/null +++ b/clang/test/Parser/ParseFunctionsOndemand.cpp @@ -0,0 +1,602 @@ +// RUN: %clang_cc1 -fparse-functions-ondemand -fsyntax-only -verify -Wall -Wextra -Werror -ferror-limit 0 -std=c++11 %s +// RUN: not %clang_cc1 -fparse-functions-ondemand -fdelayed-template-parsing -fsyntax-only -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=MUTEX + +// MUTEX: error: invalid argument '-fparse-functions-ondemand' not allowed with '-fdelayed-template-parsing' + +// Internal-linkage bodies are parsed only when referenced; external ones eagerly. +namespace InternalFunctionBodies { +static void static_diagnoses_on_use() { + undeclared_static(); // expected-error {{use of undeclared identifier 'undeclared_static'}} +} + +static void prior_static(); +void prior_static() { + undeclared_prior_static(); // expected-error {{use of undeclared identifier 'undeclared_prior_static'}} +} + +static void unreferenced_prior_static(); +void unreferenced_prior_static() { + undeclared_prior_static(); // this should not give an error +} + +namespace { +void anon_namespace_function() { + undeclared_anon_namespace(); // expected-error {{use of undeclared identifier 'undeclared_anon_namespace'}} +} + +void unreferenced_anon_namespace_function() { + undeclared_anon_namespace(); // this should not give an error +} +} // namespace + +void external_function_diagnoses_eagerly() { + undeclared_external(); // expected-error {{use of undeclared identifier 'undeclared_external'}} +} + +static void unreferenced_static_body_is_delayed() { + undeclared_but_unreferenced(); // this should not give an error +} + +__attribute__((used)) static int attribute_used_body_is_parsed() { + return undeclared_attribute_used(); // expected-error {{use of undeclared identifier 'undeclared_attribute_used'}} +} + +void trigger_internal_function_bodies() { + static_diagnoses_on_use(); + prior_static(); + anon_namespace_function(); +} +} // namespace InternalFunctionBodies + +// A delayed body resolves overloads using only those declared before it. +namespace SourceOrderOverloads { +struct One {}; +struct Two {}; // expected-note 2 {{candidate constructor}} + +static One foo_hidden_later(int); +static void call_before_later_overload() { + Two value = foo_hidden_later(0.0); // expected-error {{no viable conversion from 'One' to 'Two'}} +} +static Two foo_hidden_later(double); + +static Two foo_forward_declared(double); +static void call_forward_declared_overload() { + Two _ = foo_forward_declared(0.0); +} +static Two foo_forward_declared(double); + +static One foo_both_before(int); +static Two foo_both_before(double); +static void call_both_overloads_before() { + Two _ = foo_both_before(0.0); +} + +void trigger_source_order_overloads() { + call_before_later_overload(); + call_forward_declared_overload(); + call_both_overloads_before(); +} +} // namespace SourceOrderOverloads + +// A delayed body sees namespace-scope names only if declared before it. +namespace NamespaceScopeSourceOrder { +static int later_variable_hidden() { + return later_variable; // expected-error {{use of undeclared identifier 'later_variable'}} +} +static int later_variable; + +static int later_function_hidden() { + return later_function(); // expected-error {{use of undeclared identifier 'later_function'}} +} +static int later_function(); + +static int builtin_lookup_still_works() { + return __builtin_abs(-1); +} + +void trigger_namespace_scope_source_order() { + later_variable_hidden(); + later_function_hidden(); + builtin_lookup_still_works(); +} +} // namespace NamespaceScopeSourceOrder + +// Member bodies are delayed too; when parsed they see the whole class but only +// prior namespace-scope names. +namespace ClassMethodLookup { +struct NormalClass { + void sees_later_method() { + later_method(); + } + void later_method(); + + int sees_later_static_member() { + return later_static_member; + } + static int later_static_member; +}; + +namespace { +struct InternalClass { + void unreferenced_body_is_delayed() { + undeclared_in_method(); // this should not give an error + } + + void sees_later_member() { + later_member(); + } + void later_member() {} + + void diagnoses_on_use() { + undeclared_in_referenced_method(); // expected-error {{use of undeclared identifier 'undeclared_in_referenced_method'}} + } + + void later_global_hidden() { + later_global_after_class; // expected-error {{use of undeclared identifier 'later_global_after_class'}} + } + + static int static_member_later_global_hidden() { + return later_global_after_class; // expected-error {{use of undeclared identifier 'later_global_after_class'}} + } + + static int static_member_sees_later_static_member() { + return later_static_member; + } + static int later_static_member; +}; +} // namespace + +int InternalClass::later_static_member = 10; +int later_global_after_class = 10; + +void trigger_class_method_lookup() { + InternalClass object; + object.sees_later_member(); + object.diagnoses_on_use(); + object.later_global_hidden(); + InternalClass::static_member_later_global_hidden(); + InternalClass::static_member_sees_later_static_member(); +} +} // namespace ClassMethodLookup + +// Out-of-line member bodies see namespace-scope names declared before the +// out-of-line definition. +namespace OutOfClassMethods { +namespace { +struct S { + int sees_prior_global(); + int hides_later_global(); +}; + +int prior_global; // expected-note {{'prior_global' declared here}} + +int S::sees_prior_global() { + return prior_global; +} + +int S::hides_later_global() { + return later_global; // expected-error {{use of undeclared identifier 'later_global'}} +} + +int later_global; // expected-note 2 {{'OutOfClassMethods::later_global' declared here}} +} // namespace + +void trigger_out_of_class_methods() { + S s; + s.sees_prior_global(); + s.hides_later_global(); +} +} // namespace OutOfClassMethods + +// Inline friend and friend-class member bodies follow the same prior-only +// namespace-scope visibility. +namespace FriendFunctionsAndClasses { +int prior_global; + +namespace { +struct FriendFunctionPrior { + friend int friend_function_prior(FriendFunctionPrior) { + return prior_global; + } +}; + +struct FriendFunctionLater { + friend int friend_function_later(FriendFunctionLater) { + return later_global; // expected-error {{use of undeclared identifier 'later_global'}} + } +}; + +struct Host { + friend struct FriendClass; +}; + +struct FriendClass { + static int sees_prior_global() { + return prior_global; + } + static int hides_later_global() { + return later_global; // expected-error {{use of undeclared identifier 'later_global'}} + } +}; +} // namespace + +int later_global; + +void trigger_friend_functions_and_classes() { + friend_function_prior(FriendFunctionPrior{}); + friend_function_later(FriendFunctionLater{}); + FriendClass::sees_prior_global(); + FriendClass::hides_later_global(); +} +} // namespace FriendFunctionsAndClasses + +// ADL from a delayed body finds only functions declared before it. +namespace ADLLookup { +struct One {}; +struct Two {}; + +namespace Hidden { +struct A {}; +} + +static void adl_later_function_hidden() { + Hidden::A a; + Two value = adl_target(a); // expected-error {{use of undeclared identifier 'adl_target'}} +} + +namespace Hidden { +One adl_target(A); +} + +namespace Visible { +struct A {}; +One adl_target(A); +} + +static void adl_prior_function_visible() { + Visible::A a; + One _ = adl_target(a); +} + +void trigger_adl_lookup() { + adl_later_function_hidden(); + adl_prior_function_visible(); +} +} // namespace ADLLookup + +// Qualified lookup from a delayed body finds only members declared before it. +namespace QualifiedLookup { +struct One {}; +struct Two {}; + +namespace Hidden {} + +static void qualified_later_function_hidden() { + Hidden::h(); // expected-error {{no member named 'h' in namespace 'QualifiedLookup::Hidden'}} +} + +namespace Hidden { +One h(); +} + +namespace Visible { +One h(); +} + +static void qualified_prior_function_visible() { + One _ = Visible::h(); +} + +void trigger_qualified_lookup() { + qualified_later_function_hidden(); + qualified_prior_function_visible(); +} +} // namespace QualifiedLookup + +// Default arguments, noexcept, and trailing-return are parsed eagerly (at the +// declaration), so they cannot see later names; delete/default bodies are fine. +namespace DeclarationParts { +static int default_argument(int value = default_argument_later_global) { // expected-error {{use of undeclared identifier 'default_argument_later_global'}} + return value; +} +int default_argument_later_global; + +static void noexcept_expr() noexcept(noexcept(noexcept_later_global)) {} // expected-error {{use of undeclared identifier 'noexcept_later_global'}} +int noexcept_later_global; + +static auto trailing_return() -> decltype(trailing_return_later_global) { // expected-error {{use of undeclared identifier 'trailing_return_later_global'}} + return 0; +} +int trailing_return_later_global; + +void deleted_function() = delete; + +struct DefaultedConstructor { + DefaultedConstructor() = default; +}; +} // namespace DeclarationParts + +// Local-class member bodies are parsed with their enclosing function's body. +namespace LocalClassCases { +void local_class_method_diagnoses_on_use() { + struct S { + void f() { + undeclared_in_local_class(); // expected-error {{use of undeclared identifier 'undeclared_in_local_class'}} + } + }; + + S s; + s.f(); +} + +void local_class_method_sees_local_typedef() { + typedef int T; + struct S { + T f() { return 0; } + }; + + S s; + s.f(); +} + +// A local-class member body can name a sibling local declaration, so it must be +// parsed with its enclosing function rather than deferred and re-parsed at the +// end of the TU (where the enclosing scope is gone). +void local_class_method_sees_sibling_local_class() { + struct Helper { + int value() { return 7; } + }; + struct User { + int use() { + Helper h; // must still resolve 'Helper' + return h.value(); + } + }; + + User u; + u.use(); +} + +// Same, but the referencing member is reached through the vtable of a locally +// constructed object. +void local_class_virtual_method_sees_sibling_local_class() { + struct Helper { + int value() { return 7; } + }; + struct Base { + virtual int get() { return 0; } + virtual ~Base() {} + }; + struct Derived : Base { + int get() override { + Helper h; // must still resolve 'Helper' + return h.value(); + } + }; + + Derived d; + (void)d; +} +} // namespace LocalClassCases + +// A hidden friend defined inline in a class template is late-parsed on demand. +// Its body is parsed in the complete-class context, so it must see members of +// the enclosing class regardless of source order -- including members declared +// after it, as in libstdc++'s forward_list iterator comparison operators. +namespace HiddenFriendMemberOrder { +template <class T> +struct Iter { + friend bool operator==(const Iter &a, const Iter &b) { + return a.node == b.node; // 'node' is declared later in the same class + } + friend bool operator!=(const Iter &a, const Iter &b) { + return a.node != b.node; + } + void *node; +}; + +bool trigger_hidden_friend_member_order() { + Iter<int> a{}, b{}; + return a == b || a != b; +} +} // namespace HiddenFriendMemberOrder + +namespace VirtualFunctions { +// Constructing an object of a polymorphic internal-linkage class emits its +// vtable, which references every virtual function. So all virtual function +// bodies are parsed when the object is used, while unreferenced non-virtual +// members stay unparsed. +namespace { +struct UsedObject { + virtual void first_virtual() { + undeclared_in_first_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_first_virtual'}} + } + virtual void second_virtual() { + undeclared_in_second_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_second_virtual'}} + } + void unreferenced_non_virtual() { + undeclared_in_non_virtual(); // this should not give an error + } +}; + +// No object is ever created, so none of the virtual bodies are parsed. +struct NeverInstantiated { + virtual void unused_first_virtual() { + undeclared_in_unused_first(); // this should not give an error + } + virtual void unused_second_virtual() { + undeclared_in_unused_second(); // this should not give an error + } +}; + +// Only a pointer is formed; without constructing an object the vtable is not +// emitted and the virtual bodies stay unparsed. +struct OnlyPointerUsed { + virtual void pointer_virtual() { + undeclared_in_pointer_virtual(); // this should not give an error + } +}; + +// Calling a single virtual function still emits the vtable, so every virtual +// body is parsed -- not just the one that was called. +struct CallOneVirtual { + virtual void called_virtual() { + undeclared_in_called_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_called_virtual'}} + } + virtual void other_virtual() { + undeclared_in_other_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_other_virtual'}} + } +}; + +// Using a derived object emits both the derived and base vtables, so virtual +// bodies from the whole hierarchy are parsed. +struct Base { + virtual void base_virtual() { + undeclared_in_base_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_base_virtual'}} + } +}; + +struct Derived : Base { + void base_virtual() override { + undeclared_in_override_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_override_virtual'}} + } + virtual void derived_virtual() { + undeclared_in_derived_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_derived_virtual'}} + } +}; +} // namespace + +void trigger_virtual_functions() { + UsedObject used; + (void)used; + + CallOneVirtual call; + call.called_virtual(); + + Derived derived; + (void)derived; + + OnlyPointerUsed *pointer = nullptr; + (void)pointer; +} +} // namespace VirtualFunctions + +// A vtable whose first use is inside an on-demand-deferred internal function is +// only marked used while that body is parsed at the end of the TU, after the +// initial vtable-defining pass. Its virtual bodies must still be parsed (here, +// diagnosed) rather than emitted unparsed. +namespace VTableUsedInDeferredFunction { +namespace { +struct Polymorphic { + virtual int poly_method() { + undeclared_in_poly_method(); // expected-error {{use of undeclared identifier 'undeclared_in_poly_method'}} + return 0; + } +}; +} + +// Internal linkage, so this body is deferred; constructing the object here is +// the first use of Polymorphic's vtable. +static Polymorphic &get_instance() { + static Polymorphic instance; + return instance; +} + +void trigger_vtable_used_in_deferred_function() { + get_instance(); +} +} // namespace VTableUsedInDeferredFunction + +// An explicit function-template specialization is not TK_NonTemplate, so the +// on-demand machinery cannot re-parse it. Even when it has internal linkage +// (its template argument is an anonymous-namespace type) it must be parsed +// eagerly rather than deferred; otherwise its body (here, diagnosed) would be +// left unparsed and emitted empty. +namespace ExplicitSpecialization { +namespace { +struct InternalField {}; +struct InternalField2 {}; +} +struct Parser { + template <class T> bool parse(T &field); +}; +template <> +bool Parser::parse(InternalField &field) { + (void)field; + undeclared_in_explicit_specialization(); // this should not error + return false; +} +template <> +bool Parser::parse(InternalField2 &field) { + (void)field; + undeclared_in_explicit_specialization(); // expected-error {{use of undeclared identifier 'undeclared_in_explicit_specialization'}} + return false; +} +} // namespace ExplicitSpecialization + +void trigger_explici_specialization() { + ExplicitSpecialization::Parser p; + ExplicitSpecialization::InternalField2 f; + p.parse(f); +} + +// A function template's body is deferred on demand (it is late-parsed), but its +// signature is still substituted at call sites. That substitution -- here SFINAE +// on decltype(Builder()) -- must not be subject to the late-parsed body's +// source-order visibility restriction, otherwise a capturing lambda's call +// operator (declared at the call site) is wrongly hidden and the call fails to +// resolve. (A non-capturing lambda avoids the restriction, so a capturing one is +// used here.) +namespace SFINAEWithCapturingLambda { +struct Diag { + int x; +}; +struct Emitter { + void emit(Diag &&) {} + template <class T> void emit(T Builder, decltype(Builder()) * = nullptr) { + emit(Builder()); + } +}; +void trigger_sfinae_with_capturing_lambda(Emitter &E) { + int local = 0; + E.emit([&]() { return Diag{local}; }); +} +} // namespace SFINAEWithCapturingLambda + +// Anonymous-namespace function templates are still parsed on instantiation, and +// dependent lookups in their signatures and bodies must resolve normally. +namespace AnonymousNamespaceTemplateLookup { +namespace { +template <typename T> void call_foo(T &t) { t.foo(); } + +template <typename T> typename T::result call_bar(T &t) { + return t.bar(); +} + +struct T { + using result = int; + + void foo() {} + + result bar() { return 0; } +}; +} // namespace + +void trigger_anonymous_namespace_template_lookup() { + T t; + call_foo(t); + (void)call_bar(t); +} +} // namespace AnonymousNamespaceTemplateLookup + +// Template bodies are still parsed on instantiation. +namespace TemplateCases { +template <class T> +void template_body_diagnoses_on_instantiation() { + undeclared_in_template(); // expected-error {{use of undeclared identifier 'undeclared_in_template'}} +} + +void trigger_template_cases() { + template_body_diagnoses_on_instantiation<int>(); +} +} // namespace TemplateCases _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
