diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 22261621df092..052ee8cd42bf0 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -5941,6 +5941,22 @@ def save_temps : Flag<["-", "--"], "save-temps">, Flags<[NoXarchOption]>, Visibility<[ClangOption, FlangOption, FC1Option]>, Alias, AliasArgs<["cwd"]>, HelpText<"Alias for --save-temps=cwd">; +def summaries_dir_EQ : Joined<["-", "--"], "summaries-dir=">, Flags<[NoXarchOption]>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Read summaries about different functions from this directory">, + MarshallingInfoString>; +def emit_summaries_EQ : Joined<["-", "--"], "emit-summaries=">, Flags<[NoXarchOption]>, + Visibility<[ClangOption]>, + HelpText<"Save summaries about the different functions. can be set to 'cwd' for " + "current working directory, or 'obj' which will save temporary files in the " + "same directory as the final output file">; +def emit_summaries : Flag<["-", "--"], "emit-summaries">, Flags<[NoXarchOption]>, + Visibility<[ClangOption]>, + Alias, AliasArgs<["cwd"]>, + HelpText<"Alias for --emit-summaries=cwd">; +def summary_format_EQ : Joined<["-", "--"], "summary-format=">, Flags<[NoXarchOption]>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"The format of the emitted summaries. Can be set to 'json' (default), 'yaml', or 'binary'">; def save_stats_EQ : Joined<["-", "--"], "save-stats=">, Flags<[NoXarchOption]>, HelpText<"Save llvm statistics.">; def save_stats : Flag<["-", "--"], "save-stats">, Flags<[NoXarchOption]>, @@ -8148,6 +8164,9 @@ defm emit_llvm_uselists : BoolOption<"", "emit-llvm-uselists", NegFlag, BothFlags<[], [ClangOption], " order of LLVM use-lists when serializing">>; +def emit_summary_dir : Joined<["-"], "emit-summary-dir=">, + HelpText<"Directory to write summaries about function definitions to">, + MarshallingInfoString>; def print_stats : Flag<["-"], "print-stats">, HelpText<"Print performance metrics and statistics">, MarshallingInfoFlag>; diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h index 5f25a932c5052..4208fb2ae797c 100644 --- a/clang/include/clang/Frontend/CompilerInstance.h +++ b/clang/include/clang/Frontend/CompilerInstance.h @@ -19,6 +19,7 @@ #include "clang/Lex/DependencyDirectivesScanner.h" #include "clang/Lex/HeaderSearchOptions.h" #include "clang/Lex/ModuleLoader.h" +#include "clang/Summary/SummaryContext.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" @@ -48,6 +49,8 @@ class ModuleFile; } class CodeCompleteConsumer; +class SummaryContext; +class SummaryConsumer; class DiagnosticsEngine; class DiagnosticConsumer; class FileManager; @@ -121,6 +124,18 @@ class CompilerInstance : public ModuleLoader { /// The code completion consumer. std::unique_ptr CompletionConsumer; + /// The summary serializer. + std::unique_ptr TheSummarySerializer; + + /// The summary consumer. + std::unique_ptr TheSummaryConsumer; + + /// The summary context. + std::unique_ptr SummaryCtx; + + /// The summary output file. + std::unique_ptr SummaryOS; + /// The semantic analysis object. std::unique_ptr TheSema; @@ -611,6 +626,34 @@ class CompilerInstance : public ModuleLoader { /// the compiler instance takes ownership of \p Value. void setCodeCompletionConsumer(CodeCompleteConsumer *Value); + /// @} + /// @name Summary + /// @{ + + bool hasSummaryContext() { return (bool)SummaryCtx; } + + SummaryContext *getSummaryContext() { return SummaryCtx.get(); } + + void createSummaryContext() { SummaryCtx.reset(new SummaryContext()); } + + bool hasSummaryConsumer() const { return (bool)TheSummaryConsumer; } + + SummaryConsumer *getSummaryConsumer() const { + return TheSummaryConsumer.get(); + } + + void createSummaryConsumer(FrontendInputFile Input); + + bool hasSummarySerializer() const { return (bool)TheSummarySerializer; } + + SummarySerializer &getSummarySerializer() const { + assert(TheSummarySerializer && + "Compiler instance has no code summary serializer!"); + return *TheSummarySerializer; + } + + void createSummarySerializer(); + /// @} /// @name Frontend timer /// @{ diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index c919a53ae089e..0e06c13000188 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -534,6 +534,15 @@ class FrontendOptions { /// minimization hints. std::string DumpMinimizationHintsPath; + /// The directory used to write summary files to. + std::string EmitSummaryDir; + + /// The directory used to load summary files from. + std::string ReadSummaryDir; + + /// The format of the summary files. + std::string SummaryFormat; + public: FrontendOptions() : DisableFree(false), RelocatablePCH(false), ShowHelp(false), diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index fe93df94438cb..34e3dc7778df2 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -125,6 +125,8 @@ class CXXBasePath; class CXXBasePaths; class CXXFieldCollector; class CodeCompleteConsumer; +class SummaryContext; +class SummaryConsumer; enum class ComparisonCategoryType : unsigned char; class ConstraintSatisfaction; class DarwinSDKInfo; @@ -883,7 +885,9 @@ class Sema final : public SemaBase { public: Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, TranslationUnitKind TUKind = TU_Complete, - CodeCompleteConsumer *CompletionConsumer = nullptr); + CodeCompleteConsumer *CompletionConsumer = nullptr, + SummaryContext *SummaryCtx = nullptr, + SummaryConsumer *SummaryConsumer = nullptr); ~Sema(); /// Perform initialization that occurs after the parser has been @@ -1260,6 +1264,8 @@ class Sema final : public SemaBase { DiagnosticsEngine &Diags; SourceManager &SourceMgr; api_notes::APINotesManager APINotes; + SummaryContext *SummaryCtx; + SummaryConsumer *TheSummaryConsumer; /// A RAII object to enter scope of a compound statement. class CompoundScopeRAII { diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index b8a4dcbc727a6..ac010e424da40 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -21,19 +21,20 @@ #include "clang/Analysis/DomainSpecific/ObjCNoReturn.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/LLVM.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" #include "clang/StaticAnalyzer/Core/PathSensitive/WorkList.h" +#include "clang/Summary/SummaryContext.h" #include "llvm/ADT/ArrayRef.h" #include #include @@ -178,10 +179,13 @@ class ExprEngine { /// The flag, which specifies the mode of inlining for the engine. InliningModes HowToInline; + const SummaryContext *SummaryCtx; + public: ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, AnalysisManager &mgr, - SetOfConstDecls *VisitedCalleesIn, - FunctionSummariesTy *FS, InliningModes HowToInlineIn); + SetOfConstDecls *VisitedCalleesIn, FunctionSummariesTy *FS, + InliningModes HowToInlineIn, + const SummaryContext *SummaryCtx = nullptr); virtual ~ExprEngine() = default; @@ -214,6 +218,8 @@ class ExprEngine { return &CTU; } + const SummaryContext *getSummaryCtx() { return SummaryCtx; } + const NodeBuilderContext &getBuilderContext() { assert(currBldrCtx); return *currBldrCtx; diff --git a/clang/include/clang/Summary/SummaryAttribute.h b/clang/include/clang/Summary/SummaryAttribute.h new file mode 100644 index 0000000000000..5ac889e6b0d4d --- /dev/null +++ b/clang/include/clang/Summary/SummaryAttribute.h @@ -0,0 +1,52 @@ +#ifndef LLVM_CLANG_SUMMARY_SUMMARYATTRIBUTE_H +#define LLVM_CLANG_SUMMARY_SUMMARYATTRIBUTE_H + +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +enum SummaryAttrKind { NO_WRITE_GLOBAL, NO_WRITE_PTR_PARAMETER }; + +class FunctionSummary; +class SummaryContext; + +class SummaryAttr { + const SummaryAttrKind Kind; + const char *Spelling; + +protected: + SummaryAttr(SummaryAttrKind Kind, const char *Spelling) + : Kind(Kind), Spelling(Spelling) {}; + +public: + virtual ~SummaryAttr() = default; + + SummaryAttrKind getKind() const { return Kind; } + const char *getSpelling() const { return Spelling; } + + virtual bool infer(const FunctionDecl *FD) const = 0; + virtual bool merge(const FunctionSummary &Caller, + const FunctionSummary *Callee) const = 0; + + virtual std::string serialize() const { return std::string(Spelling); }; + virtual bool parse(std::string_view input) const { + return input == Spelling; + }; +}; + +class NoWriteGlobalAttr : public SummaryAttr { + NoWriteGlobalAttr() : SummaryAttr(NO_WRITE_GLOBAL, "no_write_global") {} + +public: + bool infer(const FunctionDecl *FD) const override final; + bool merge(const FunctionSummary &Caller, + const FunctionSummary *Callee) const override final; + + static bool classof(const SummaryAttr *A) { + return A->getKind() == NO_WRITE_GLOBAL; + } + friend class SummaryContext; +}; +} // namespace clang + +#endif // LLVM_CLANG_SUMMARY_SUMMARYATTRIBUTEH diff --git a/clang/include/clang/Summary/SummaryConsumer.h b/clang/include/clang/Summary/SummaryConsumer.h new file mode 100644 index 0000000000000..b1421c960da28 --- /dev/null +++ b/clang/include/clang/Summary/SummaryConsumer.h @@ -0,0 +1,36 @@ +#ifndef LLVM_CLANG_SUMMARY_SUMMARYCONSUMER_H +#define LLVM_CLANG_SUMMARY_SUMMARYCONSUMER_H + +#include "llvm/Support/raw_ostream.h" +namespace clang { +class FunctionSummary; +class SummaryContext; +class SummarySerializer; + +class SummaryConsumer { +protected: + const SummaryContext *SummaryCtx; + +public: + SummaryConsumer(const SummaryContext &SummaryCtx) : SummaryCtx(&SummaryCtx) {} + virtual ~SummaryConsumer() = default; + + virtual void ProcessStartOfSourceFile() {}; + virtual void ProcessFunctionSummary(const FunctionSummary &) {}; + virtual void ProcessEndOfSourceFile() {}; +}; + +class SerializingSummaryConsumer : public SummaryConsumer { + llvm::raw_ostream &OS; + SummarySerializer *Serializer; + +public: + SerializingSummaryConsumer(SummarySerializer &Serializer, + llvm::raw_ostream &OS); + + void ProcessEndOfSourceFile() override; +}; + +} // namespace clang + +#endif // LLVM_CLANG_SUMMARY_SUMMARYCONSUMER_H diff --git a/clang/include/clang/Summary/SummaryContext.h b/clang/include/clang/Summary/SummaryContext.h new file mode 100644 index 0000000000000..a54b4fb8a0d09 --- /dev/null +++ b/clang/include/clang/Summary/SummaryContext.h @@ -0,0 +1,75 @@ +#ifndef LLVM_CLANG_SUMMARY_SUMMARYCONTEXT_H +#define LLVM_CLANG_SUMMARY_SUMMARYCONTEXT_H + +#include "clang/Summary/SummaryAttribute.h" +#include "clang/Summary/SummaryConsumer.h" +#include "llvm/Support/YAMLTraits.h" +#include + +namespace clang { +class FunctionSummary { + size_t ID; + std::set Attrs; + std::set Calls; + bool CallsOpaque; + +public: + FunctionSummary(size_t ID, std::set Attrs, + std::set Calls, bool CallsOpaque); + + size_t getID() const { return ID; } + const std::set &getAttributes() const { return Attrs; } + const std::set &getCalls() const { return Calls; } + bool callsOpaqueObject() const { return CallsOpaque; } + + template bool hasAttribute() const { + for (auto &&attr : Attrs) { + if (llvm::isa(attr)) + return true; + } + + return false; + } + + void replaceAttributes(std::set Attrs) { + this->Attrs = std::move(Attrs); + } +}; + +class SummaryContext { + std::map IdentifierToID; + std::vector Identifiers; + + std::map IDToSummary; + std::vector> FunctionSummaries; + + std::map KindToAttribute; + std::vector> Attributes; + + template void RegisterAttr(); + +public: + SummaryContext(); + + size_t GetOrInsertStoredIdentifierIdx(StringRef ID); + std::optional GetStoredIdentifierIdx(StringRef ID) const; + + void CreateSummary(size_t ID, std::set Attrs, + std::set Calls, bool CallsOpaque); + bool ReduceFunctionSummary(FunctionSummary &FunctionSummary); + + const std::vector> &GetSummaries() const { + return FunctionSummaries; + }; + const std::vector> &GetAttributes() const { + return Attributes; + }; + const std::vector &GetIdentifiers() const { return Identifiers; }; + + const FunctionSummary *GetSummary(const FunctionDecl *FD) const; + void SummarizeFunctionBody(const FunctionDecl *FD); + void ReduceSummaries(); +}; +} // namespace clang + +#endif // LLVM_CLANG_SUMMARY_SUMMARYCONTEXTH diff --git a/clang/include/clang/Summary/SummarySerialization.h b/clang/include/clang/Summary/SummarySerialization.h new file mode 100644 index 0000000000000..ca22be3b8da83 --- /dev/null +++ b/clang/include/clang/Summary/SummarySerialization.h @@ -0,0 +1,94 @@ +#ifndef LLVM_CLANG_SUMMARY_SUMMARYSERIALIZATION_H +#define LLVM_CLANG_SUMMARY_SUMMARYSERIALIZATION_H + +#include "clang/Summary/SummaryContext.h" +#include "llvm/Bitstream/BitstreamReader.h" +#include "llvm/Bitstream/BitstreamWriter.h" + +namespace clang { +class SummarySerializer { +protected: + SummaryContext *SummaryCtx; + +public: + SummaryContext *getSummaryCtx() const { return SummaryCtx; } + + SummarySerializer(SummaryContext &SummaryCtx) : SummaryCtx(&SummaryCtx) {}; + virtual ~SummarySerializer() = default; + + virtual void serialize(raw_ostream &OS) = 0; + virtual void parse(StringRef) = 0; +}; + +class JSONSummarySerializer : public SummarySerializer { +public: + JSONSummarySerializer(SummaryContext &SummaryCtx) + : SummarySerializer(SummaryCtx) {}; + + void serialize(raw_ostream &OS) override; + void parse(StringRef) override; +}; + +class YAMLSummarySerializer : public SummarySerializer { +public: + YAMLSummarySerializer(SummaryContext &SummaryCtx) + : SummarySerializer(SummaryCtx) {}; + + void serialize(raw_ostream &OS) override; + void parse(StringRef) override; +}; + +class BinarySummarySerializer : public SummarySerializer { + enum BlockIDs { + IDENTIFIER_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID, + ATTRIBUTE_BLOCK_ID, + SUMMARY_BLOCK_ID + }; + + enum AttributeRecordTypes { + ATTR = 1, + }; + + enum IdentifierRecordTypes { + IDENTIFIER = 1, + }; + + enum SummaryRecordTypes { FUNCTION = 1 }; + + std::map LocalToContextID; + std::map AttrIDToPtr; + + llvm::SmallVector Buffer; + llvm::BitstreamWriter Stream; + + void PopulateBlockInfo(); + void EmitAttributeBlock(); + void EmitIdentifierBlock(); + void EmitSummaryBlock(); + + void EmitBlock(unsigned ID, const char *Name); + void EmitRecord(unsigned ID, const char *Name); + + llvm::Error handleBlockStartCommon(unsigned ID, + llvm::BitstreamCursor &Stream); + llvm::Error handleBlockRecordsCommon( + llvm::BitstreamCursor &Stream, + llvm::function_ref &)>); + + llvm::Error parseAttributeBlock(llvm::BitstreamCursor &Stream); + llvm::Error parseIdentifierBlock(llvm::BitstreamCursor &Stream); + llvm::Error parseSummaryBlock(llvm::BitstreamCursor &Stream); + llvm::Error parseBlock(unsigned ID, llvm::BitstreamCursor &Stream); + llvm::Error parseImpl(StringRef Buffer); + +public: + BinarySummarySerializer(SummaryContext &SummaryCtx) + : SummarySerializer(SummaryCtx), Stream(Buffer) {}; + + void serialize(raw_ostream &OS) override; + void parse(StringRef) override; +}; + +} // namespace clang + +#endif // LLVM_CLANG_SUMMARY_SUMMARYSERIALIZATION_H diff --git a/clang/lib/CMakeLists.txt b/clang/lib/CMakeLists.txt index 4f2218b583e41..96f0ccd7d1c88 100644 --- a/clang/lib/CMakeLists.txt +++ b/clang/lib/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(CrossTU) add_subdirectory(Sema) add_subdirectory(CodeGen) add_subdirectory(Analysis) +add_subdirectory(Summary) add_subdirectory(Edit) add_subdirectory(ExtractAPI) add_subdirectory(Rewrite) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 2fb6cf8ea2bdc..cc9a019b40345 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -5470,6 +5470,32 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, if (Args.getLastArg(options::OPT_save_temps_EQ)) Args.AddLastArg(CmdArgs, options::OPT_save_temps_EQ); + if (Args.getLastArg(options::OPT_summaries_dir_EQ)) + Args.AddLastArg(CmdArgs, options::OPT_summaries_dir_EQ); + + if (Args.getLastArg(options::OPT_summary_format_EQ)) + Args.AddLastArg(CmdArgs, options::OPT_summary_format_EQ); + + if (const Arg *A = Args.getLastArg(options::OPT_emit_summaries_EQ)) { + std::string EmitSummaryDir = "."; + + if (Arg *FinalOutput = C.getArgs().getLastArg(options::OPT_o); + A->containsValue("obj") && FinalOutput) { + StringRef ObjDir = llvm::sys::path::parent_path(FinalOutput->getValue()); + if (!ObjDir.empty()) + EmitSummaryDir = ObjDir; + } + + if(A->containsValue("src") && Input.isFilename()) { + StringRef BasePath = llvm::sys::path::parent_path(Input.getFilename()); + if(!BasePath.empty()) + EmitSummaryDir = BasePath.str(); + } + + CmdArgs.push_back( + Args.MakeArgString(Twine("-emit-summary-dir=") + EmitSummaryDir)); + } + auto *MemProfArg = Args.getLastArg(options::OPT_fmemory_profile, options::OPT_fmemory_profile_EQ, options::OPT_fno_memory_profile); diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 503d36467653e..b57dbd78336dc 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -41,6 +41,8 @@ #include "clang/Serialization/GlobalModuleIndex.h" #include "clang/Serialization/InMemoryModuleCache.h" #include "clang/Serialization/ModuleCache.h" +#include "clang/Summary/SummaryConsumer.h" +#include "clang/Summary/SummarySerialization.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" @@ -741,10 +743,56 @@ CompilerInstance::createCodeCompletionConsumer(Preprocessor &PP, return new PrintingCodeCompleteConsumer(Opts, OS); } +void CompilerInstance::createSummaryConsumer(FrontendInputFile Input) { + StringRef EmitSummaryDir = getFrontendOpts().EmitSummaryDir; + if (EmitSummaryDir.empty() || !Input.isFile()) + return; + + llvm::SmallString<32> SummaryFile = EmitSummaryDir; + llvm::sys::path::append(SummaryFile, llvm::sys::path::filename(Input.getFile())); + + StringRef Format = getFrontendOpts().SummaryFormat; + llvm::sys::path::replace_extension(SummaryFile, + Format == "binary" ? "summary" : Format); + + std::error_code EC; + SummaryOS.reset(new llvm::raw_fd_ostream(SummaryFile, EC, + llvm::sys::fs::CD_CreateAlways)); + + if (EC) { + SummaryOS = nullptr; + return; + } + + if (!hasSummarySerializer()) + createSummarySerializer(); + + TheSummaryConsumer.reset( + new SerializingSummaryConsumer(getSummarySerializer(), *SummaryOS)); +} + +void CompilerInstance::createSummarySerializer() { + StringRef Format = getFrontendOpts().SummaryFormat; + SummarySerializer *Serializer; + + if (!hasSummaryContext()) + createSummaryContext(); + + if (Format == "yaml") + Serializer = new YAMLSummarySerializer(*getSummaryContext()); + else if (Format == "binary") + Serializer = new BinarySummarySerializer(*getSummaryContext()); + else + Serializer = new JSONSummarySerializer(*getSummaryContext()); + + TheSummarySerializer.reset(Serializer); +} + void CompilerInstance::createSema(TranslationUnitKind TUKind, CodeCompleteConsumer *CompletionConsumer) { TheSema.reset(new Sema(getPreprocessor(), getASTContext(), getASTConsumer(), - TUKind, CompletionConsumer)); + TUKind, CompletionConsumer, getSummaryContext(), + getSummaryConsumer())); // Set up API notes. TheSema->APINotes.setSwiftVersion(getAPINotesOpts().SwiftVersion); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 3c23073fc6a8c..1d3006505d110 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2985,6 +2985,9 @@ static void GenerateFrontendArgs(const FrontendOptions &Opts, Lang + HeaderUnit + Header + ModuleMap + Preprocessed); } + if (!Opts.EmitSummaryDir.empty() || !Opts.ReadSummaryDir.empty()) + GenerateArg(Consumer, OPT_summary_format_EQ, Opts.SummaryFormat); + // OPT_INPUT has a unique class, generate it directly. for (const auto &Input : Opts.Inputs) Consumer(Input.getFile()); @@ -3263,6 +3266,15 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args, Opts.Inputs.emplace_back(std::move(Inputs[i]), IK, IsSystem); } + Opts.SummaryFormat = "json"; + if (const Arg *A = Args.getLastArg(OPT_summary_format_EQ)) { + StringRef Format = A->getValue(); + + // FIXME: don't hardcode these values + if (Format == "yaml" || Format == "binary") + Opts.SummaryFormat = Format; + }; + Opts.DashX = DashX; return Diags.getNumErrors() == NumErrorsBefore; diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp index 54a2e3eb297f5..44adc71b7a0df 100644 --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/clang/lib/Frontend/FrontendAction.cpp @@ -38,6 +38,8 @@ #include "clang/Serialization/ASTDeserializationListener.h" #include "clang/Serialization/ASTReader.h" #include "clang/Serialization/GlobalModuleIndex.h" +#include "clang/Summary/SummaryContext.h" +#include "clang/Summary/SummarySerialization.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/BuryPointer.h" @@ -46,7 +48,9 @@ #include "llvm/Support/Path.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include #include +#include #include using namespace clang; @@ -962,6 +966,76 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI, } } + bool EmitSummaries = !CI.getFrontendOpts().EmitSummaryDir.empty(); + bool ReadSummaries = !CI.getFrontendOpts().ReadSummaryDir.empty(); + + if (EmitSummaries || ReadSummaries) { + if (!CI.hasSummaryContext()) + CI.createSummaryContext(); + + if (!CI.hasSummarySerializer()) + CI.createSummarySerializer(); + } + + if (EmitSummaries) + CI.createSummaryConsumer(getCurrentInput()); + + if (ReadSummaries) { + // FIXME: this is a quick shortcut so large summaries are only evaluated + // once, we should think about implementing it in a reasonable way... + static const char *reducedCacheName = + "reduced-summary-so-that-we-do-not-have-to-evaluate-it-every-time"; + const std::string summaryExtension = + '.' + (CI.getFrontendOpts().SummaryFormat == "binary" + ? "summary" + : CI.getFrontendOpts().SummaryFormat); + + FileManager &FileMgr = CI.getFileManager(); + StringRef SummaryDirPath = CI.getFrontendOpts().ReadSummaryDir; + if (auto SummaryDir = FileMgr.getOptionalDirectoryRef(SummaryDirPath)) { + std::error_code EC; + SmallString<128> DirNative; + llvm::sys::path::native(SummaryDir->getName(), DirNative); + + llvm::vfs::FileSystem &FS = FileMgr.getVirtualFileSystem(); + std::string cacheFile = + DirNative.str().str() + '/' + reducedCacheName + summaryExtension; + + std::vector paths; + + if (FS.exists(cacheFile)) { + paths.emplace_back(cacheFile); + } else { + for (llvm::vfs::directory_iterator Dir = FS.dir_begin(DirNative, EC), + DirEnd; + Dir != DirEnd && !EC; Dir.increment(EC)) { + if (llvm::sys::path::extension(Dir->path()) != summaryExtension) + continue; + + paths.emplace_back(Dir->path().str()); + } + } + + llvm::sort(paths); + + for (auto &&path : paths) { + std::ifstream t(path); + std::stringstream buffer; + buffer << t.rdbuf(); + + CI.getSummarySerializer().parse(buffer.str()); + } + + CI.getSummaryContext()->ReduceSummaries(); + + if (!FS.exists(cacheFile)) { + // FIXME: very quick printing of the summary to the cache file + llvm::raw_fd_ostream fd(cacheFile, EC, llvm::sys::fs::CD_CreateAlways); + CI.getSummarySerializer().serialize(fd); + } + } + } + // Set up the preprocessor if needed. When parsing model files the // preprocessor of the original source is reused. if (!isModelParsingAction()) diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 4b87004e4b8ea..940456e15f9f8 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -114,4 +114,5 @@ add_clang_library(clangSema clangEdit clangLex clangSupport + clangSummary ) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 1901d19b14dfc..fd29145d4d773 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -71,6 +71,7 @@ #include "clang/Sema/TemplateDeduction.h" #include "clang/Sema/TemplateInstCallback.h" #include "clang/Sema/TypoCorrection.h" +#include "clang/Summary/SummaryContext.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" @@ -248,11 +249,13 @@ const unsigned Sema::MaxAlignmentExponent; const uint64_t Sema::MaximumAlignment; Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, - TranslationUnitKind TUKind, CodeCompleteConsumer *CodeCompleter) + TranslationUnitKind TUKind, CodeCompleteConsumer *CodeCompleter, + SummaryContext *SummaryCtx, SummaryConsumer *SummaryConsumer) : SemaBase(*this), CollectStats(false), TUKind(TUKind), CurFPFeatures(pp.getLangOpts()), LangOpts(pp.getLangOpts()), PP(pp), Context(ctxt), Consumer(consumer), Diags(PP.getDiagnostics()), SourceMgr(PP.getSourceManager()), APINotes(SourceMgr, LangOpts), + SummaryCtx(SummaryCtx), TheSummaryConsumer(SummaryConsumer), AnalysisWarnings(*this), ThreadSafetyDeclCache(nullptr), LateTemplateParser(nullptr), LateTemplateParserCleanup(nullptr), OpaqueParser(nullptr), CurContext(nullptr), ExternalSource(nullptr), @@ -304,6 +307,8 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, AccessCheckingSFINAE(false), CurrentInstantiationScope(nullptr), InNonInstantiationSFINAEContext(false), NonInstantiationEntries(0), ArgPackSubstIndex(std::nullopt), SatisfactionCache(Context) { + assert((!SummaryConsumer || SummaryCtx) && + "summary consumer without a summary context"); assert(pp.TUKind == TUKind); TUScope = nullptr; @@ -1142,6 +1147,9 @@ void Sema::ActOnStartOfTranslationUnit() { if (getLangOpts().CPlusPlusModules && getLangOpts().getCompilingModule() == LangOptions::CMK_HeaderUnit) HandleStartOfHeaderUnit(); + + if (TheSummaryConsumer) + TheSummaryConsumer->ProcessStartOfSourceFile(); } void Sema::ActOnEndOfTranslationUnitFragment(TUFragmentKind Kind) { @@ -1217,6 +1225,9 @@ void Sema::ActOnEndOfTranslationUnit() { assert(DelayedDiagnostics.getCurrentPool() == nullptr && "reached end of translation unit with a pool attached?"); + if (TheSummaryConsumer) + TheSummaryConsumer->ProcessEndOfSourceFile(); + // If code completion is enabled, don't perform any end-of-translation-unit // work. if (PP.isCodeCompletionEnabled()) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 814f81cb64cae..86892920dd479 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -58,6 +58,7 @@ #include "clang/Sema/SemaSwift.h" #include "clang/Sema/SemaWasm.h" #include "clang/Sema/Template.h" +#include "clang/Summary/SummaryContext.h" #include "llvm/ADT/STLForwardCompat.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" @@ -16694,6 +16695,12 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, if (FD && !FD->isDeleted()) checkTypeSupport(FD->getType(), FD->getLocation(), FD); + if (TheSummaryConsumer && !LateTemplateParser && FD && + !SourceMgr.isInSystemHeader(FD->getLocation()) && !FD->getBuiltinID()) { + SummaryCtx->SummarizeFunctionBody(FD); + TheSummaryConsumer->ProcessFunctionSummary(*SummaryCtx->GetSummary(FD)); + } + return dcl; } diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 583315f4f3a90..4b9e852983f48 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -233,12 +233,20 @@ static void findPtrToConstParams(llvm::SmallSet &PreserveArgs, ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, ProgramStateRef Orig) const { + const FunctionSummary *Summary = nullptr; ProgramStateRef Result = (Orig ? Orig : getState()); - // Don't invalidate anything if the callee is marked pure/const. - if (const Decl *callee = getDecl()) + if (const Decl *callee = getDecl()) { + const SummaryContext *SummaryCtx = + State->getStateManager().getOwningEngine().getSummaryCtx(); + const FunctionDecl *FD = llvm::dyn_cast(callee); + if (SummaryCtx && FD) + Summary = SummaryCtx->GetSummary(FD); + + // Don't invalidate anything if the callee is marked pure/const. if (callee->hasAttr() || callee->hasAttr()) return Result; + } SmallVector ValuesToInvalidate; RegionAndSymbolInvalidationTraits ETraits; @@ -277,13 +285,16 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, ValuesToInvalidate.push_back(loc::MemRegionVal(TVR)); } + bool ShouldPreserveGlobals = + Summary && Summary->hasAttribute(); + // Invalidate designated regions using the batch invalidation API. // NOTE: Even if RegionsToInvalidate is empty, we may still invalidate // global variables. - return Result->invalidateRegions(ValuesToInvalidate, getCFGElementRef(), - BlockCount, getLocationContext(), - /*CausedByPointerEscape*/ true, - /*Symbols=*/nullptr, this, &ETraits); + return Result->invalidateRegions( + ValuesToInvalidate, getCFGElementRef(), BlockCount, getLocationContext(), + /*CausedByPointerEscape*/ true, + /*Symbols=*/nullptr, ShouldPreserveGlobals ? nullptr : this, &ETraits); } ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 1afd4b52eb354..c4836fb9d0aac 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -223,7 +223,8 @@ static const char* TagProviderName = "ExprEngine"; ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, AnalysisManager &mgr, SetOfConstDecls *VisitedCalleesIn, - FunctionSummariesTy *FS, InliningModes HowToInlineIn) + FunctionSummariesTy *FS, InliningModes HowToInlineIn, + const SummaryContext *SummaryCtx) : CTU(CTU), IsCTUEnabled(mgr.getAnalyzerOptions().IsNaiveCTUEnabled), AMgr(mgr), AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()), Engine(*this, FS, mgr.getAnalyzerOptions()), G(Engine.getGraph()), @@ -232,7 +233,7 @@ ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, SymMgr(StateMgr.getSymbolManager()), MRMgr(StateMgr.getRegionManager()), svalBuilder(StateMgr.getSValBuilder()), ObjCNoRet(mgr.getASTContext()), BR(mgr, *this), VisitedCallees(VisitedCalleesIn), - HowToInline(HowToInlineIn) { + HowToInline(HowToInlineIn), SummaryCtx(SummaryCtx) { unsigned TrimInterval = mgr.options.GraphTrimInterval; if (TrimInterval != 0) { // Enable eager node reclamation when constructing the ExplodedGraph. diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 491aa93c96e49..b5b95c2ab7ae1 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -93,6 +93,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, ArrayRef Plugins; std::unique_ptr Injector; cross_tu::CrossTranslationUnitContext CTU; + const SummaryContext *SummaryCtx; /// Stores the declarations from the local translation unit. /// Note, we pre-compute the local declarations at parse time as an @@ -128,7 +129,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), PP(CI.getPreprocessor()), OutDir(outdir), Opts(opts), Plugins(plugins), Injector(std::move(injector)), CTU(CI), - MacroExpansions(CI.getLangOpts()) { + SummaryCtx(CI.getSummaryContext()), MacroExpansions(CI.getLangOpts()) { EntryPointStat::lockRegistry(); DigestAnalyzerOptions(); @@ -761,7 +762,8 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, if (!Mgr->getAnalysisDeclContext(D)->getAnalysis()) return; - ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode); + ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode, + SummaryCtx); // Execute the worklist algorithm. llvm::TimeRecord ExprEngineStartTime; diff --git a/clang/lib/Summary/CMakeLists.txt b/clang/lib/Summary/CMakeLists.txt new file mode 100644 index 0000000000000..a0079e3bd2b3d --- /dev/null +++ b/clang/lib/Summary/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS + Core + Support + ) + +add_clang_library(clangSummary + SummaryAttribute.cpp + SummaryConsumer.cpp + SummaryContext.cpp + SummarySerialization.cpp + + LINK_LIBS + clangAST + clangSupport + clangIndex + ) diff --git a/clang/lib/Summary/SummaryAttribute.cpp b/clang/lib/Summary/SummaryAttribute.cpp new file mode 100644 index 0000000000000..92a83d5342d05 --- /dev/null +++ b/clang/lib/Summary/SummaryAttribute.cpp @@ -0,0 +1,39 @@ +#include "clang/Summary/SummaryAttribute.h" +#include "clang/Summary/SummaryContext.h" + +namespace clang { +bool NoWriteGlobalAttr::infer(const FunctionDecl *FD) const { + using namespace ast_matchers; + MatchFinder Finder; + + class Callback : public ast_matchers::MatchFinder::MatchCallback { + public: + bool WriteGlobal = false; + + void + run(const ast_matchers::MatchFinder::MatchResult &Result) override final { + const auto *Assignment = + Result.Nodes.getNodeAs("assignment"); + if (!Assignment) + return; + + WriteGlobal = true; + } + } CB; + + Finder.addMatcher( + functionDecl(forEachDescendant( + binaryOperator(isAssignmentOperator(), + hasLHS(declRefExpr(to(varDecl(hasGlobalStorage()))))) + .bind("assignment"))), + &CB); + Finder.match(*FD, FD->getASTContext()); + return !CB.WriteGlobal; +} + +bool NoWriteGlobalAttr::merge(const FunctionSummary &Caller, + const FunctionSummary *Callee) const { + return !Caller.callsOpaqueObject() && Caller.getAttributes().count(this) && + Callee && Callee->getAttributes().count(this); +} +} // namespace clang diff --git a/clang/lib/Summary/SummaryConsumer.cpp b/clang/lib/Summary/SummaryConsumer.cpp new file mode 100644 index 0000000000000..3c7a08cbcd91b --- /dev/null +++ b/clang/lib/Summary/SummaryConsumer.cpp @@ -0,0 +1,14 @@ +#include "clang/Summary/SummaryConsumer.h" +#include "clang/Summary/SummaryContext.h" +#include "clang/Summary/SummarySerialization.h" + +namespace clang { +SerializingSummaryConsumer::SerializingSummaryConsumer( + SummarySerializer &Serializer, llvm::raw_ostream &OS) + : SummaryConsumer(*Serializer.getSummaryCtx()), OS(OS), + Serializer(&Serializer) {} + +void SerializingSummaryConsumer::ProcessEndOfSourceFile() { + Serializer->serialize(OS); +} +} // namespace clang diff --git a/clang/lib/Summary/SummaryContext.cpp b/clang/lib/Summary/SummaryContext.cpp new file mode 100644 index 0000000000000..9a25261df3fa2 --- /dev/null +++ b/clang/lib/Summary/SummaryContext.cpp @@ -0,0 +1,164 @@ +#include "clang/Summary/SummaryContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Summary/SummaryAttribute.h" +#include "clang/Summary/SummaryConsumer.h" +#include + +namespace clang { +namespace { +std::string GetUSR(const FunctionDecl *FD) { + SmallString<32> USR; + index::generateUSRForDecl(FD, USR); + return USR.str().str(); +} + +class CallCollector : public ast_matchers::MatchFinder::MatchCallback { + SummaryContext *Context; + std::set Calls; + bool callsOpaqueSymbol = false; + + virtual void + run(const ast_matchers::MatchFinder::MatchResult &Result) override { + const auto *Call = Result.Nodes.getNodeAs("call"); + if (!Call) + return; + + const auto *Callee = + llvm::dyn_cast_or_null(Call->getCalleeDecl()); + if (!Callee) { + callsOpaqueSymbol = true; + return; + } + + if (Result.SourceManager->isInSystemHeader(Callee->getLocation()) || + Callee->getBuiltinID()) + return; + + if (const auto *MD = llvm::dyn_cast(Callee); + MD && MD->isVirtual()) { + callsOpaqueSymbol = true; + return; + } + + Calls.emplace(Context->GetOrInsertStoredIdentifierIdx(GetUSR(Callee))); + } + +public: + CallCollector(SummaryContext &Context) : Context(&Context) {} + + std::pair, bool> collect(const FunctionDecl *FD) { + using namespace ast_matchers; + MatchFinder Finder; + + Finder.addMatcher(functionDecl(forEachDescendant(callExpr().bind("call"))), + this); + Finder.match(*FD, FD->getASTContext()); + + return {Calls, callsOpaqueSymbol}; + } +}; +} // namespace + +FunctionSummary::FunctionSummary(size_t ID, + std::set FunctionAttrs, + std::set Calls, bool CallsOpaque) + : ID(ID), Attrs(std::move(FunctionAttrs)), Calls(std::move(Calls)), + CallsOpaque(CallsOpaque) {} + +template void SummaryContext::RegisterAttr() { + std::unique_ptr attr(new T()); + SummaryAttrKind Kind = attr->getKind(); + + if (KindToAttribute.count(Kind)) + return; + + if (!Attributes.empty()) + assert(Attributes.back()->getKind() == Kind - 1 && + "attributes are not stored continously"); + + KindToAttribute[Kind] = Attributes.emplace_back(std::move(attr)).get(); +} + +SummaryContext::SummaryContext() { RegisterAttr(); } + +size_t SummaryContext::GetOrInsertStoredIdentifierIdx(StringRef ID) { + auto &&[Element, Inserted] = + IdentifierToID.try_emplace(ID.str(), IdentifierToID.size()); + if (Inserted) + Identifiers.emplace_back(Element->first); + + return Element->second; +} + +std::optional +SummaryContext::GetStoredIdentifierIdx(StringRef ID) const { + if (IdentifierToID.count(ID.str())) + return IdentifierToID.at(ID.str()); + + return std::nullopt; +} + +void SummaryContext::CreateSummary(size_t ID, + std::set Attrs, + std::set Calls, bool CallsOpaque) { + if (IDToSummary.count(ID)) + return; + + auto Summary = std::make_unique( + ID, std::move(Attrs), std::move(Calls), CallsOpaque); + auto *SummaryPtr = FunctionSummaries.emplace_back(std::move(Summary)).get(); + IDToSummary[SummaryPtr->getID()] = SummaryPtr; +} + +const FunctionSummary * +SummaryContext::GetSummary(const FunctionDecl *FD) const { + std::optional ID = GetStoredIdentifierIdx(GetUSR(FD)); + return ID ? IDToSummary.at(*ID) : nullptr; +} + +void SummaryContext::SummarizeFunctionBody(const FunctionDecl *FD) { + std::set Attrs; + + for (auto &&Attr : Attributes) { + if (Attr->infer(FD)) + Attrs.emplace(Attr.get()); + } + + auto [CollectedCalls, Opaque] = CallCollector(*this).collect(FD); + CreateSummary(GetOrInsertStoredIdentifierIdx(GetUSR(FD)), std::move(Attrs), + std::move(CollectedCalls), Opaque); +} + +bool SummaryContext::ReduceFunctionSummary(FunctionSummary &Function) { + bool changed = false; + + for (auto &&call : Function.getCalls()) { + std::set reducedAttrs; + + const FunctionSummary *callSummary = + IDToSummary.count(call) ? IDToSummary[call] : nullptr; + for (auto &&Attr : Attributes) { + if (Attr->merge(Function, callSummary)) + reducedAttrs.emplace(Attr.get()); + } + + if (reducedAttrs != Function.getAttributes()) { + Function.replaceAttributes(std::move(reducedAttrs)); + changed = true; + } + } + + return changed; +} + +void SummaryContext::ReduceSummaries() { + bool changed = true; + while (changed) { + changed = false; + + for (auto &&Function : FunctionSummaries) + changed |= ReduceFunctionSummary(*Function); + } +} +} // namespace clang diff --git a/clang/lib/Summary/SummarySerialization.cpp b/clang/lib/Summary/SummarySerialization.cpp new file mode 100644 index 0000000000000..3afad28def498 --- /dev/null +++ b/clang/lib/Summary/SummarySerialization.cpp @@ -0,0 +1,580 @@ +#include "clang/Summary/SummarySerialization.h" +#include "llvm/Bitstream/BitstreamReader.h" +#include "llvm/Support/JSON.h" + +namespace llvm { +namespace yaml { +struct FunctionSummaryProxy { + size_t ID; + std::vector Attrs; + std::vector Calls; + bool CallsOpaque; + + FunctionSummaryProxy() = default; + FunctionSummaryProxy(const clang::FunctionSummary &Summary) + : ID(Summary.getID()), CallsOpaque(Summary.callsOpaqueObject()) { + Attrs.reserve(Summary.getAttributes().size()); + for (auto &&Attr : Summary.getAttributes()) + Attrs.emplace_back(Attr->getKind()); + + Calls.reserve(Summary.getCalls().size()); + for (auto &&Call : Summary.getCalls()) + Calls.emplace_back(Call); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, FunctionSummaryProxy &FS) { + io.mapRequired("id", FS.ID); + io.mapRequired("fn_attrs", FS.Attrs); + io.mapRequired("opaque_calls", FS.CallsOpaque); + io.mapRequired("calls", FS.Calls); + } +}; + +template <> struct SequenceTraits> { + static size_t size(IO &io, std::vector &seq) { + return seq.size(); + } + + static FunctionSummaryProxy & + element(IO &io, std::vector &seq, size_t index) { + if (index >= seq.size()) + seq.emplace_back(); + + return seq[index]; + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, clang::SummaryContext &Ctx) { + if (io.outputting()) { + std::vector Identifiers = Ctx.GetIdentifiers(); + io.mapRequired("identifiers", Identifiers); + + std::vector Attributes; + Attributes.reserve(Ctx.GetAttributes().size()); + for (auto &&Attr : Ctx.GetAttributes()) + Attributes.emplace_back(Attr->serialize()); + io.mapRequired("attributes", Attributes); + + std::vector SummaryProxies; + SummaryProxies.reserve(Ctx.GetSummaries().size()); + for (auto &&Summary : Ctx.GetSummaries()) + SummaryProxies.emplace_back(*Summary); + io.mapRequired("summaries", SummaryProxies); + + return; + } + + std::vector Identifiers; + io.mapRequired("identifiers", Identifiers); + std::map LocalToContextID; + for (auto &&ID : Identifiers) { + LocalToContextID[LocalToContextID.size()] = + Ctx.GetOrInsertStoredIdentifierIdx(ID); + } + + std::vector Attributes; + io.mapRequired("attributes", Attributes); + std::map AttrIDToPtr; + std::set Seen; + for (auto &&Attribute : Attributes) { + for (auto &&Attr : Ctx.GetAttributes()) + if (Attr->parse(Attribute)) { + if (!Seen.emplace(Attr.get()).second) + break; + + AttrIDToPtr[AttrIDToPtr.size()] = Attr.get(); + break; + } + } + + std::vector SummaryProxies; + io.mapRequired("summaries", SummaryProxies); + for (auto &&Proxy : SummaryProxies) { + if (Proxy.ID >= LocalToContextID.size()) + continue; + + std::set Attrs; + for (auto &&Attr : Proxy.Attrs) { + if (Attr >= AttrIDToPtr.size()) + continue; + + Attrs.emplace(AttrIDToPtr[Attr]); + } + + std::set Calls; + for (auto &&Call : Proxy.Calls) { + if (Call >= LocalToContextID.size()) + continue; + + Calls.emplace(LocalToContextID[Call]); + } + + Ctx.CreateSummary(LocalToContextID[Proxy.ID], std::move(Attrs), + std::move(Calls), Proxy.CallsOpaque); + } + } +}; +} // namespace yaml +} // namespace llvm + +namespace clang { +void JSONSummarySerializer::serialize(raw_ostream &OS) { + llvm::json::OStream JOS(OS, 2); + JOS.objectBegin(); + + JOS.attributeArray("identifiers", [&] { + for (auto &&Identifier : SummaryCtx->GetIdentifiers()) + JOS.value(Identifier); + }); + + JOS.attributeArray("attributes", [&] { + for (auto &&Attribute : SummaryCtx->GetAttributes()) + JOS.value(Attribute->serialize()); + }); + + JOS.attributeArray("summaries", [&] { + for (auto &&Summary : SummaryCtx->GetSummaries()) { + JOS.object([&] { + JOS.attribute("id", llvm::json::Value(Summary->getID())); + JOS.attributeArray("fn_attrs", [&] { + for (auto &&Attr : Summary->getAttributes()) { + JOS.value(llvm::json::Value(static_cast(Attr->getKind()))); + } + }); + JOS.attribute("opaque_calls", + llvm::json::Value(Summary->callsOpaqueObject())); + JOS.attributeArray("calls", [&] { + for (auto &&Call : Summary->getCalls()) { + JOS.value(llvm::json::Value(Call)); + } + }); + }); + } + }); + + JOS.objectEnd(); + JOS.flush(); +} + +void JSONSummarySerializer::parse(StringRef Buffer) { + auto JSON = llvm::json::parse(Buffer); + if (!JSON) { + llvm::handleAllErrors(JSON.takeError(), [](const llvm::ErrorInfoBase &EI) { + std::ignore = EI.message(); + }); + return; + } + + auto *JSONObject = JSON->getAsObject(); + if (!JSONObject) + return; + + auto *JSONIdentifiers = JSONObject->getArray("identifiers"); + if (!JSONIdentifiers) + return; + + std::map LocalToContextID; + for (auto &&Identifier : *JSONIdentifiers) { + auto IdentifierStr = Identifier.getAsString(); + if (!IdentifierStr) + return; + + LocalToContextID[LocalToContextID.size()] = + SummaryCtx->GetOrInsertStoredIdentifierIdx(*IdentifierStr); + } + + auto *JSONAttributes = JSONObject->getArray("attributes"); + if (!JSONAttributes) + return; + + std::map AttrIDToPtr; + std::set Seen; + for (auto &&Attribute : *JSONAttributes) { + auto AttributeStr = Attribute.getAsString(); + + for (auto &&Attr : SummaryCtx->GetAttributes()) + if (Attr->parse(*AttributeStr)) { + if (!Seen.emplace(Attr.get()).second) + return; + + AttrIDToPtr[AttrIDToPtr.size()] = Attr.get(); + break; + } + } + + auto *JSONSummaries = JSONObject->getArray("summaries"); + if (!JSONSummaries) + return; + + for (auto &&JSONSummary : *JSONSummaries) { + auto *JSONSummaryObject = JSONSummary.getAsObject(); + if (!JSONSummaryObject) + continue; + + std::optional ID = JSONSummaryObject->getInteger("id"); + if (!ID || *ID >= LocalToContextID.size()) + continue; + + auto *JSONAttributes = JSONSummaryObject->getArray("fn_attrs"); + if (!JSONAttributes) + continue; + + std::set FunctionAttrs; + for (auto &&JSONAttr : *JSONAttributes) { + std::optional AttrID = JSONAttr.getAsUINT64(); + if (!AttrID || *AttrID >= AttrIDToPtr.size()) + return; + + FunctionAttrs.emplace(AttrIDToPtr[*AttrID]); + } + + std::optional CallsOpaue = + *JSONSummaryObject->getBoolean("opaque_calls"); + if (!CallsOpaue) + continue; + + std::set Calls; + auto *JSONCallEntries = JSONSummaryObject->getArray("calls"); + if (!JSONCallEntries) + continue; + + for (auto &&JSONCall : *JSONCallEntries) { + std::optional CallID = JSONCall.getAsUINT64(); + if (!CallID || *CallID >= LocalToContextID.size()) + continue; + + Calls.emplace(LocalToContextID[*CallID]); + } + + SummaryCtx->CreateSummary(LocalToContextID[*ID], std::move(FunctionAttrs), + std::move(Calls), *CallsOpaue); + } +} + +void YAMLSummarySerializer::serialize(raw_ostream &OS) { + llvm::yaml::Output YOUT(OS); + YOUT << *SummaryCtx; + OS.flush(); +} + +void YAMLSummarySerializer::parse(StringRef Buffer) { + llvm::yaml::Input YIN(Buffer); + YIN >> *SummaryCtx; +} + +void BinarySummarySerializer::PopulateBlockInfo() { + Stream.EnterBlockInfoBlock(); + EmitBlock(ATTRIBUTE_BLOCK_ID, "ATTRIBUTES"); + EmitRecord(ATTR, "ATTR"); + EmitBlock(IDENTIFIER_BLOCK_ID, "IDENTIFIERS"); + EmitRecord(IDENTIFIER, "IDENTIFIER"); + EmitBlock(SUMMARY_BLOCK_ID, "SUMMARIES"); + EmitRecord(FUNCTION, "FUNCTION"); + Stream.ExitBlock(); +} + +void BinarySummarySerializer::EmitIdentifierBlock() { + Stream.EnterSubblock(IDENTIFIER_BLOCK_ID, 3); + + auto Abv = std::make_shared(); + Abv->Add(llvm::BitCodeAbbrevOp(IDENTIFIER)); + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Array)); + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, 8)); + unsigned Abbrev = Stream.EmitAbbrev(std::move(Abv)); + + uint64_t Record[] = {IDENTIFIER}; + for (auto &&Identifier : SummaryCtx->GetIdentifiers()) + Stream.EmitRecordWithArray(Abbrev, Record, Identifier); + + Stream.ExitBlock(); +} + +void BinarySummarySerializer::EmitAttributeBlock() { + Stream.EnterSubblock(ATTRIBUTE_BLOCK_ID, 3); + + auto Abv = std::make_shared(); + Abv->Add(llvm::BitCodeAbbrevOp(ATTR)); + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Array)); + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, 8)); + unsigned Abbrev = Stream.EmitAbbrev(std::move(Abv)); + + uint64_t Record[] = {ATTR}; + for (auto &&Attr : SummaryCtx->GetAttributes()) + Stream.EmitRecordWithArray(Abbrev, Record, Attr->serialize()); + + Stream.ExitBlock(); +} + +void BinarySummarySerializer::EmitSummaryBlock() { + Stream.EnterSubblock(SUMMARY_BLOCK_ID, 3); + + auto Abv = std::make_shared(); + Abv->Add(llvm::BitCodeAbbrevOp(FUNCTION)); + // The number of attributes. + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, 7)); + // The number of callees. + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, 32)); + // Whether there are opaque callees or not. + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, 1)); + // An array of the following form: [ID, Attr0...AttrN, Callee0...CalleeN] + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Array)); + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, 32)); + unsigned Abbrev = Stream.EmitAbbrev(std::move(Abv)); + + for (auto &&Summary : SummaryCtx->GetSummaries()) { + SmallVector Record; + + Record.push_back(Summary->getAttributes().size()); + Record.push_back(Summary->getCalls().size()); + Record.push_back(Summary->callsOpaqueObject()); + + Record.push_back(1 + Summary->getAttributes().size() + + Summary->getCalls().size()); + Record.push_back(Summary->getID()); + for (auto &&Attr : Summary->getAttributes()) + Record.push_back(Attr->getKind()); + for (auto &&Call : Summary->getCalls()) + Record.push_back(Call); + + Stream.EmitRecord(FUNCTION, Record, Abbrev); + } + + Stream.ExitBlock(); +} + +void BinarySummarySerializer::EmitBlock(unsigned ID, const char *Name) { + SmallVector Buffer; + Buffer.push_back(ID); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, Buffer); + + Buffer.clear(); + while (*Name) + Buffer.push_back(*Name++); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_BLOCKNAME, Buffer); +} + +void BinarySummarySerializer::EmitRecord(unsigned ID, const char *Name) { + SmallVector Buffer; + Buffer.push_back(ID); + while (*Name) + Buffer.push_back(*Name++); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, Buffer); +} + +void BinarySummarySerializer::serialize(raw_ostream &OS) { + Stream.Emit((unsigned)'C', 8); + Stream.Emit((unsigned)'T', 8); + Stream.Emit((unsigned)'U', 8); + Stream.Emit((unsigned)'S', 8); + + PopulateBlockInfo(); + EmitIdentifierBlock(); + EmitAttributeBlock(); + EmitSummaryBlock(); + + Stream.FlushToWord(); + OS << Buffer; + OS.flush(); +} + +llvm::Error +BinarySummarySerializer::handleBlockStartCommon(unsigned ID, + llvm::BitstreamCursor &Stream) { + unsigned NumWords = 0; + if (llvm::Error Err = Stream.EnterSubBlock(ID, &NumWords)) + return Err; + + llvm::BitstreamEntry Entry; + if (llvm::Error E = + Stream.advance(llvm::BitstreamCursor::AF_DontAutoprocessAbbrevs) + .moveInto(Entry)) + return E; + + if (Entry.Kind != llvm::BitstreamEntry::Record && + Entry.ID != llvm::bitc::DEFINE_ABBREV) + return llvm::createStringError("expected abbrev"); + + if (llvm::Error Err = Stream.ReadAbbrevRecord()) + return Err; + + return llvm::Error::success(); +} + +llvm::Error BinarySummarySerializer::handleBlockRecordsCommon( + llvm::BitstreamCursor &Stream, + llvm::function_ref &)> Callback) { + while (true) { + llvm::BitstreamEntry Entry; + if (llvm::Error E = + Stream.advance(llvm::BitstreamCursor::AF_DontAutoprocessAbbrevs) + .moveInto(Entry)) + return E; + + if (Entry.Kind == llvm::BitstreamEntry::EndBlock) + return llvm::Error::success(); + + if (Entry.Kind != llvm::BitstreamEntry::Record) + return llvm::createStringError("expected record"); + + SmallVector Record; + unsigned Code; + if (llvm::Error E = Stream.readRecord(Entry.ID, Record).moveInto(Code)) + return E; + + Callback(Record); + } +} + +llvm::Error +BinarySummarySerializer::parseIdentifierBlock(llvm::BitstreamCursor &Stream) { + if (llvm::Error Err = handleBlockStartCommon(IDENTIFIER_BLOCK_ID, Stream)) + return Err; + + if (llvm::Error Err = handleBlockRecordsCommon(Stream, [&](auto &&Record) { + llvm::SmallString<64> IdentifierStr(Record.begin(), Record.end()); + LocalToContextID[LocalToContextID.size()] = + SummaryCtx->GetOrInsertStoredIdentifierIdx(IdentifierStr); + })) + return Err; + + return llvm::Error::success(); +} + +llvm::Error +BinarySummarySerializer::parseAttributeBlock(llvm::BitstreamCursor &Stream) { + if (llvm::Error Err = handleBlockStartCommon(ATTRIBUTE_BLOCK_ID, Stream)) + return Err; + + if (llvm::Error Err = handleBlockRecordsCommon(Stream, [&](auto &&Record) { + std::set Seen; + + for (auto &&Attr : SummaryCtx->GetAttributes()) { + llvm::SmallString<64> AttributeStr(Record.begin(), Record.end()); + + if (Attr->parse(AttributeStr.str())) { + if (!Seen.emplace(Attr.get()).second) + break; + + AttrIDToPtr[AttrIDToPtr.size()] = Attr.get(); + break; + } + } + })) + return Err; + + return llvm::Error::success(); +} + +llvm::Error +BinarySummarySerializer::parseSummaryBlock(llvm::BitstreamCursor &Stream) { + if (llvm::Error Err = handleBlockStartCommon(SUMMARY_BLOCK_ID, Stream)) + return Err; + + if (llvm::Error Err = handleBlockRecordsCommon(Stream, [&](auto &&Record) { + int AttrCnt = Record[0]; + int CallCnt = Record[1]; + bool Opaque = Record[2]; + size_t ID = Record[4]; + int I = 0; + + if (ID >= LocalToContextID.size()) + return; + + std::set Attrs; + while (AttrCnt) { + size_t AttrID = Record[5 + I]; + if (AttrID >= AttrIDToPtr.size()) + return; + + Attrs.emplace(AttrIDToPtr[AttrID]); + ++I; + --AttrCnt; + } + + std::set Calls; + while (CallCnt) { + size_t CallID = Record[5 + I]; + if (CallID >= LocalToContextID.size()) + return; + + Calls.emplace(LocalToContextID[CallID]); + ++I; + --CallCnt; + } + + SummaryCtx->CreateSummary(LocalToContextID[ID], std::move(Attrs), + std::move(Calls), Opaque); + })) + return Err; + + return llvm::Error::success(); +} + +llvm::Error BinarySummarySerializer::parseBlock(unsigned ID, + llvm::BitstreamCursor &Stream) { + if (ID == llvm::bitc::BLOCKINFO_BLOCK_ID) { + std::optional NewBlockInfo; + if (llvm::Error Err = Stream.ReadBlockInfoBlock().moveInto(NewBlockInfo)) + return Err; + if (!NewBlockInfo) + return llvm::createStringError("expected block info"); + + return llvm::Error::success(); + } + + if (ID == IDENTIFIER_BLOCK_ID) + return parseIdentifierBlock(Stream); + + if (ID == ATTRIBUTE_BLOCK_ID) + return parseAttributeBlock(Stream); + + if (ID == SUMMARY_BLOCK_ID) + return parseSummaryBlock(Stream); + + return llvm::createStringError("unexpected block"); +} + +llvm::Error BinarySummarySerializer::parseImpl(StringRef Buffer) { + llvm::BitstreamCursor Stream(Buffer); + LocalToContextID.clear(); + AttrIDToPtr.clear(); + + llvm::SimpleBitstreamCursor::word_t Magic[4] = {0}; + unsigned char ExpectedMagic[] = {'C', 'T', 'U', 'S'}; + for (int i = 0; i < 4; ++i) { + if (llvm::Error Err = Stream.Read(8).moveInto(Magic[i])) + return Err; + + if (Magic[i] != ExpectedMagic[i]) + return llvm::createStringError("invalid magic number"); + } + + while (!Stream.AtEndOfStream()) { + Expected MaybeCode = Stream.ReadCode(); + if (!MaybeCode) + return MaybeCode.takeError(); + if (MaybeCode.get() != llvm::bitc::ENTER_SUBBLOCK) + return llvm::createStringError("expected record"); + + Expected MaybeBlockID = Stream.ReadSubBlockID(); + if (!MaybeBlockID) + return MaybeBlockID.takeError(); + + if (llvm::Error Err = parseBlock(MaybeBlockID.get(), Stream)) + return Err; + } + + return llvm::Error::success(); +} + +void BinarySummarySerializer::parse(StringRef Buffer) { + if (llvm::Error Err = parseImpl(Buffer)) { + handleAllErrors(std::move(Err), [&](const llvm::ErrorInfoBase &EI) { + std::ignore = EI.message(); + }); + } +} +} // namespace clang