diff --git a/clang/include/clang/InstallAPI/Context.h b/clang/include/clang/InstallAPI/Context.h index 7d105920734fd..3e2046642c7fe 100644 --- a/clang/include/clang/InstallAPI/Context.h +++ b/clang/include/clang/InstallAPI/Context.h @@ -9,6 +9,9 @@ #ifndef LLVM_CLANG_INSTALLAPI_CONTEXT_H #define LLVM_CLANG_INSTALLAPI_CONTEXT_H +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/InstallAPI/HeaderFile.h" #include "llvm/TextAPI/InterfaceFile.h" #include "llvm/TextAPI/RecordVisitor.h" #include "llvm/TextAPI/RecordsSlice.h" @@ -24,8 +27,23 @@ struct InstallAPIContext { /// Library attributes that are typically passed as linker inputs. llvm::MachO::RecordsSlice::BinaryAttrs BA; - /// Active target triple to parse. - llvm::Triple TargetTriple{}; + /// All headers that represent a library. + HeaderSeq InputHeaders; + + /// Active language mode to parse in. + Language LangMode = Language::ObjC; + + /// Active header access type. + HeaderType Type = HeaderType::Unknown; + + /// Active TargetSlice for symbol record collection. + std::shared_ptr Slice; + + /// FileManager for all I/O operations. + FileManager *FM = nullptr; + + /// DiagnosticsEngine for all error reporting. + DiagnosticsEngine *Diags = nullptr; /// File Path of output location. llvm::StringRef OutputLoc{}; diff --git a/clang/include/clang/InstallAPI/Frontend.h b/clang/include/clang/InstallAPI/Frontend.h new file mode 100644 index 0000000000000..7ee87ae028d07 --- /dev/null +++ b/clang/include/clang/InstallAPI/Frontend.h @@ -0,0 +1,48 @@ +//===- InstallAPI/Frontend.h -----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// Top level wrappers for InstallAPI frontend operations. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INSTALLAPI_FRONTEND_H +#define LLVM_CLANG_INSTALLAPI_FRONTEND_H + +#include "clang/AST/ASTConsumer.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/InstallAPI/Context.h" +#include "clang/InstallAPI/Visitor.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/MemoryBuffer.h" + +namespace clang { +namespace installapi { + +/// Create a buffer that contains all headers to scan +/// for global symbols with. +std::unique_ptr +createInputBuffer(const InstallAPIContext &Ctx); + +class InstallAPIAction : public ASTFrontendAction { +public: + explicit InstallAPIAction(llvm::MachO::RecordsSlice &Records) + : Records(Records) {} + + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + return std::make_unique(CI.getASTContext(), Records); + } + +private: + llvm::MachO::RecordsSlice &Records; +}; +} // namespace installapi +} // namespace clang + +#endif // LLVM_CLANG_INSTALLAPI_FRONTEND_H diff --git a/clang/include/clang/InstallAPI/HeaderFile.h b/clang/include/clang/InstallAPI/HeaderFile.h index fc64a43b3def5..70e83bbb3e76f 100644 --- a/clang/include/clang/InstallAPI/HeaderFile.h +++ b/clang/include/clang/InstallAPI/HeaderFile.h @@ -15,6 +15,7 @@ #include "clang/Basic/LangStandard.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Regex.h" #include #include @@ -32,6 +33,20 @@ enum class HeaderType { Project, }; +inline StringRef getName(const HeaderType T) { + switch (T) { + case HeaderType::Public: + return "Public"; + case HeaderType::Private: + return "Private"; + case HeaderType::Project: + return "Project"; + case HeaderType::Unknown: + return "Unknown"; + } + llvm_unreachable("unexpected header type"); +} + class HeaderFile { /// Full input path to header. std::string FullPath; @@ -52,6 +67,14 @@ class HeaderFile { static llvm::Regex getFrameworkIncludeRule(); + HeaderType getType() const { return Type; } + StringRef getIncludeName() const { return IncludeName; } + StringRef getPath() const { return FullPath; } + + bool useIncludeName() const { + return Type != HeaderType::Project && !IncludeName.empty(); + } + bool operator==(const HeaderFile &Other) const { return std::tie(Type, FullPath, IncludeName, Language) == std::tie(Other.Type, Other.FullPath, Other.IncludeName, diff --git a/clang/include/clang/InstallAPI/Visitor.h b/clang/include/clang/InstallAPI/Visitor.h new file mode 100644 index 0000000000000..95d669688e4f9 --- /dev/null +++ b/clang/include/clang/InstallAPI/Visitor.h @@ -0,0 +1,51 @@ +//===- InstallAPI/Visitor.h -----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// ASTVisitor Interface for InstallAPI frontend operations. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INSTALLAPI_VISITOR_H +#define LLVM_CLANG_INSTALLAPI_VISITOR_H + +#include "clang/AST/Mangle.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Frontend/FrontendActions.h" +#include "llvm/ADT/Twine.h" +#include "llvm/TextAPI/RecordsSlice.h" + +namespace clang { +namespace installapi { + +/// ASTVisitor for collecting declarations that represent global symbols. +class InstallAPIVisitor final : public ASTConsumer, + public RecursiveASTVisitor { +public: + InstallAPIVisitor(ASTContext &ASTCtx, llvm::MachO::RecordsSlice &Slice) + : Slice(Slice), + MC(ItaniumMangleContext::create(ASTCtx, ASTCtx.getDiagnostics())), + Layout(ASTCtx.getTargetInfo().getDataLayoutString()) {} + void HandleTranslationUnit(ASTContext &ASTCtx) override; + + /// Collect global variables. + bool VisitVarDecl(const VarDecl *D); + +private: + std::string getMangledName(const NamedDecl *D) const; + std::string getBackendMangledName(llvm::Twine Name) const; + + llvm::MachO::RecordsSlice &Slice; + std::unique_ptr MC; + StringRef Layout; +}; + +} // namespace installapi +} // namespace clang + +#endif // LLVM_CLANG_INSTALLAPI_VISITOR_H diff --git a/clang/lib/InstallAPI/CMakeLists.txt b/clang/lib/InstallAPI/CMakeLists.txt index fdc4f064f29e9..19fc4c3abde53 100644 --- a/clang/lib/InstallAPI/CMakeLists.txt +++ b/clang/lib/InstallAPI/CMakeLists.txt @@ -1,11 +1,14 @@ set(LLVM_LINK_COMPONENTS Support TextAPI + Core ) add_clang_library(clangInstallAPI FileList.cpp + Frontend.cpp HeaderFile.cpp + Visitor.cpp LINK_LIBS clangAST diff --git a/clang/lib/InstallAPI/Frontend.cpp b/clang/lib/InstallAPI/Frontend.cpp new file mode 100644 index 0000000000000..9f675ef7d1bd2 --- /dev/null +++ b/clang/lib/InstallAPI/Frontend.cpp @@ -0,0 +1,58 @@ +//===- Frontend.cpp ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/InstallAPI/Frontend.h" +#include "clang/AST/Availability.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" + +using namespace llvm; +using namespace llvm::MachO; + +namespace clang::installapi { + +static StringRef getFileExtension(clang::Language Lang) { + switch (Lang) { + default: + llvm_unreachable("Unexpected language option."); + case clang::Language::C: + return ".c"; + case clang::Language::CXX: + return ".cpp"; + case clang::Language::ObjC: + return ".m"; + case clang::Language::ObjCXX: + return ".mm"; + } +} + +std::unique_ptr createInputBuffer(const InstallAPIContext &Ctx) { + assert(Ctx.Type != HeaderType::Unknown && + "unexpected access level for parsing"); + SmallString<4096> Contents; + raw_svector_ostream OS(Contents); + for (const HeaderFile &H : Ctx.InputHeaders) { + if (H.getType() != Ctx.Type) + continue; + if (Ctx.LangMode == Language::C || Ctx.LangMode == Language::CXX) + OS << "#include "; + else + OS << "#import "; + if (H.useIncludeName()) + OS << "<" << H.getIncludeName() << ">"; + else + OS << "\"" << H.getPath() << "\""; + } + if (Contents.empty()) + return nullptr; + + return llvm::MemoryBuffer::getMemBufferCopy( + Contents, "installapi-includes" + getFileExtension(Ctx.LangMode)); +} + +} // namespace clang::installapi diff --git a/clang/lib/InstallAPI/Visitor.cpp b/clang/lib/InstallAPI/Visitor.cpp new file mode 100644 index 0000000000000..9b333a6142ae8 --- /dev/null +++ b/clang/lib/InstallAPI/Visitor.cpp @@ -0,0 +1,94 @@ +//===- Visitor.cpp ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/InstallAPI/Visitor.h" +#include "clang/AST/Availability.h" +#include "clang/Basic/Linkage.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/Mangler.h" + +using namespace llvm; +using namespace llvm::MachO; + +namespace clang::installapi { + +// Exported NamedDecl needs to have externally visibiliy linkage and +// default visibility from LinkageComputer. +static bool isExported(const NamedDecl *D) { + auto LV = D->getLinkageAndVisibility(); + return isExternallyVisible(LV.getLinkage()) && + (LV.getVisibility() == DefaultVisibility); +} + +static SymbolFlags getFlags(bool WeakDef, bool ThreadLocal) { + SymbolFlags Result = SymbolFlags::None; + if (WeakDef) + Result |= SymbolFlags::WeakDefined; + if (ThreadLocal) + Result |= SymbolFlags::ThreadLocalValue; + + return Result; +} + +void InstallAPIVisitor::HandleTranslationUnit(ASTContext &ASTCtx) { + if (ASTCtx.getDiagnostics().hasErrorOccurred()) + return; + + auto *D = ASTCtx.getTranslationUnitDecl(); + TraverseDecl(D); +} + +std::string InstallAPIVisitor::getMangledName(const NamedDecl *D) const { + SmallString<256> Name; + if (MC->shouldMangleDeclName(D)) { + raw_svector_ostream NStream(Name); + MC->mangleName(D, NStream); + } else + Name += D->getNameAsString(); + + return getBackendMangledName(Name); +} + +std::string InstallAPIVisitor::getBackendMangledName(Twine Name) const { + SmallString<256> FinalName; + Mangler::getNameWithPrefix(FinalName, Name, DataLayout(Layout)); + return std::string(FinalName); +} + +/// Collect all global variables. +bool InstallAPIVisitor::VisitVarDecl(const VarDecl *D) { + // Skip function parameters. + if (isa(D)) + return true; + + // Skip variables in records. They are handled seperately for C++. + if (D->getDeclContext()->isRecord()) + return true; + + // Skip anything inside functions or methods. + if (!D->isDefinedOutsideFunctionOrMethod()) + return true; + + // If this is a template but not specialization or instantiation, skip. + if (D->getASTContext().getTemplateOrSpecializationInfo(D) && + D->getTemplateSpecializationKind() == TSK_Undeclared) + return true; + + // TODO: Capture SourceLocation & Availability for Decls. + const RecordLinkage Linkage = + isExported(D) ? RecordLinkage::Exported : RecordLinkage::Internal; + const bool WeakDef = D->hasAttr(); + const bool ThreadLocal = D->getTLSKind() != VarDecl::TLS_None; + Slice.addGlobal(getMangledName(D), Linkage, GlobalRecord::Kind::Variable, + getFlags(WeakDef, ThreadLocal)); + return true; +} + +} // namespace clang::installapi diff --git a/clang/test/InstallAPI/basic.test b/clang/test/InstallAPI/basic.test index 22b04792ca2c3..5b41ccd517b0a 100644 --- a/clang/test/InstallAPI/basic.test +++ b/clang/test/InstallAPI/basic.test @@ -16,6 +16,11 @@ // CHECK-NOT: warning: //--- basic_inputs.json +{ + "headers": [ + ], + "version": "3" +} //--- expected.tbd { diff --git a/clang/test/InstallAPI/variables.test b/clang/test/InstallAPI/variables.test new file mode 100644 index 0000000000000..6272867911f18 --- /dev/null +++ b/clang/test/InstallAPI/variables.test @@ -0,0 +1,63 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s|SRC_DIR|%/t|g" %t/vars_inputs.json.in > %t/vars_inputs.json + +/// Check multiple targets are captured. +// RUN: clang-installapi -target arm64-apple-macos13.1 -target arm64e-apple-macos13.1 \ +// RUN: -fapplication-extension -install_name /usr/lib/vars.dylib \ +// RUN: %t/vars_inputs.json -o %t/vars.tbd 2>&1 | FileCheck %s --allow-empty +// RUN: llvm-readtapi -compare %t/vars.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- vars.h +extern int foo; + +//--- vars_inputs.json.in +{ + "headers": [ { + "path" : "SRC_DIR/vars.h", + "type" : "public" + }], + "version": "3" +} + +//--- expected.tbd +{ + "main_library": { + "compatibility_versions": [ + { + "version": "0" + }], + "current_versions": [ + { + "version": "0" + }], + "install_names": [ + { + "name": "/usr/lib/vars.dylib" + } + ], + "exported_symbols": [ + { + "data": { + "global": [ + "_foo" + ] + } + } + ], + "target_info": [ + { + "min_deployment": "13.1", + "target": "arm64-macos" + }, + { + "min_deployment": "13.1", + "target": "arm64e-macos" + } + ] + }, + "tapi_tbd_version": 5 +} diff --git a/clang/tools/clang-installapi/ClangInstallAPI.cpp b/clang/tools/clang-installapi/ClangInstallAPI.cpp index fc23ffd7ae6b9..43c9fca0a82ee 100644 --- a/clang/tools/clang-installapi/ClangInstallAPI.cpp +++ b/clang/tools/clang-installapi/ClangInstallAPI.cpp @@ -12,12 +12,14 @@ //===----------------------------------------------------------------------===// #include "Options.h" -#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticFrontend.h" #include "clang/Driver/Driver.h" #include "clang/Driver/DriverDiagnostic.h" -#include "clang/Frontend/CompilerInstance.h" +#include "clang/Driver/Tool.h" #include "clang/Frontend/TextDiagnosticPrinter.h" -#include "clang/InstallAPI/Context.h" +#include "clang/InstallAPI/Frontend.h" +#include "clang/Tooling/Tooling.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" @@ -27,7 +29,9 @@ #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/TargetParser/Host.h" +#include "llvm/TextAPI/RecordVisitor.h" #include "llvm/TextAPI/TextAPIWriter.h" +#include using namespace clang; using namespace clang::installapi; @@ -35,6 +39,36 @@ using namespace clang::driver::options; using namespace llvm::opt; using namespace llvm::MachO; +static bool runFrontend(StringRef ProgName, bool Verbose, + const InstallAPIContext &Ctx, + llvm::vfs::InMemoryFileSystem *FS, + const ArrayRef InitialArgs) { + + std::unique_ptr ProcessedInput = createInputBuffer(Ctx); + // Skip invoking cc1 when there are no header inputs. + if (!ProcessedInput) + return true; + + if (Verbose) + llvm::errs() << getName(Ctx.Type) << " Headers:\n" + << ProcessedInput->getBuffer() << "\n\n"; + + std::string InputFile = ProcessedInput->getBufferIdentifier().str(); + FS->addFile(InputFile, /*ModTime=*/0, std::move(ProcessedInput)); + // Reconstruct arguments with unique values like target triple or input + // headers. + std::vector Args = {ProgName.data(), "-target", + Ctx.Slice->getTriple().str().c_str()}; + llvm::copy(InitialArgs, std::back_inserter(Args)); + Args.push_back(InputFile); + + // Create & run invocation. + clang::tooling::ToolInvocation Invocation( + std::move(Args), std::make_unique(*Ctx.Slice), Ctx.FM); + + return Invocation.run(); +} + static bool run(ArrayRef Args, const char *ProgName) { // Setup Diagnostics engine. IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); @@ -48,9 +82,15 @@ static bool run(ArrayRef Args, const char *ProgName) { new clang::DiagnosticIDs(), DiagOpts.get(), new clang::TextDiagnosticPrinter(llvm::errs(), DiagOpts.get())); - // Create file manager for all file operations. + // Create file manager for all file operations and holding in-memory generated + // inputs. + llvm::IntrusiveRefCntPtr OverlayFileSystem( + new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); + llvm::IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + OverlayFileSystem->pushOverlay(InMemoryFileSystem); IntrusiveRefCntPtr FM( - new FileManager(clang::FileSystemOptions())); + new FileManager(clang::FileSystemOptions(), OverlayFileSystem)); // Set up driver to parse input arguments. auto DriverArgs = llvm::ArrayRef(Args).slice(1); @@ -71,7 +111,10 @@ static bool run(ArrayRef Args, const char *ProgName) { Options Opts(*Diag, FM.get(), ArgList); if (Diag->hasErrorOccurred()) return EXIT_FAILURE; + InstallAPIContext Ctx = Opts.createContext(); + if (Diag->hasErrorOccurred()) + return EXIT_FAILURE; // Set up compilation. std::unique_ptr CI(new CompilerInstance()); @@ -80,6 +123,21 @@ static bool run(ArrayRef Args, const char *ProgName) { if (!CI->hasDiagnostics()) return EXIT_FAILURE; + // Execute and gather AST results. + llvm::MachO::Records FrontendResults; + for (const auto &[Targ, Trip] : Opts.DriverOpts.Targets) { + for (const HeaderType Type : + {HeaderType::Public, HeaderType::Private, HeaderType::Project}) { + Ctx.Slice = std::make_shared(Trip); + Ctx.Type = Type; + if (!runFrontend(ProgName, Opts.DriverOpts.Verbose, Ctx, + InMemoryFileSystem.get(), Opts.getClangFrontendArgs())) + return EXIT_FAILURE; + FrontendResults.emplace_back(std::move(Ctx.Slice)); + } + } + + // After symbols have been collected, prepare to write output. auto Out = CI->createOutputFile(Ctx.OutputLoc, /*Binary=*/false, /*RemoveFileOnSignal=*/false, /*UseTemporary=*/false, @@ -88,7 +146,13 @@ static bool run(ArrayRef Args, const char *ProgName) { return EXIT_FAILURE; // Assign attributes for serialization. - InterfaceFile IF; + auto Symbols = std::make_unique(); + for (const auto &FR : FrontendResults) { + SymbolConverter Converter(Symbols.get(), FR->getTarget()); + FR->visit(Converter); + } + + InterfaceFile IF(std::move(Symbols)); for (const auto &TargetInfo : Opts.DriverOpts.Targets) { IF.addTarget(TargetInfo.first); IF.setFromBinaryAttrs(Ctx.BA, TargetInfo.first); diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp index 562a643edfcf4..7d45e999448d9 100644 --- a/clang/tools/clang-installapi/Options.cpp +++ b/clang/tools/clang-installapi/Options.cpp @@ -9,6 +9,7 @@ #include "Options.h" #include "clang/Driver/Driver.h" #include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/InstallAPI/FileList.h" #include "llvm/Support/Program.h" #include "llvm/TargetParser/Host.h" @@ -68,6 +69,8 @@ bool Options::processDriverOptions(InputArgList &Args) { } } + DriverOpts.Verbose = Args.hasArgNoClaim(OPT_v); + return true; } @@ -104,10 +107,21 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM, if (!processLinkerOptions(ArgList)) return; + + /// Any remaining arguments should be handled by invoking the clang frontend. + for (const Arg *A : ArgList) { + if (A->isClaimed()) + continue; + FrontendArgs.emplace_back(A->getAsString(ArgList)); + } + FrontendArgs.push_back("-fsyntax-only"); } InstallAPIContext Options::createContext() { InstallAPIContext Ctx; + Ctx.FM = FM; + Ctx.Diags = Diags; + // InstallAPI requires two level namespacing. Ctx.BA.TwoLevelNamespace = true; @@ -116,6 +130,21 @@ InstallAPIContext Options::createContext() { Ctx.BA.AppExtensionSafe = LinkerOpts.AppExtensionSafe; Ctx.FT = DriverOpts.OutFT; Ctx.OutputLoc = DriverOpts.OutputPath; + + // Process inputs. + for (const std::string &ListPath : DriverOpts.FileLists) { + auto Buffer = FM->getBufferForFile(ListPath); + if (auto Err = Buffer.getError()) { + Diags->Report(diag::err_cannot_open_file) << ListPath; + return Ctx; + } + if (auto Err = FileListReader::loadHeaders(std::move(Buffer.get()), + Ctx.InputHeaders)) { + Diags->Report(diag::err_cannot_open_file) << ListPath; + return Ctx; + } + } + return Ctx; } diff --git a/clang/tools/clang-installapi/Options.h b/clang/tools/clang-installapi/Options.h index 4a84166a6c91b..f68addf197288 100644 --- a/clang/tools/clang-installapi/Options.h +++ b/clang/tools/clang-installapi/Options.h @@ -43,6 +43,9 @@ struct DriverOptions { /// \brief File encoding to print. llvm::MachO::FileType OutFT = llvm::MachO::FileType::TBD_V5; + + /// \brief Print verbose output. + bool Verbose = false; }; struct LinkerOptions { @@ -78,9 +81,14 @@ class Options { Options(clang::DiagnosticsEngine &Diag, FileManager *FM, llvm::opt::InputArgList &Args); + /// \brief Get CC1 arguments after extracting out the irrelevant + /// ones. + std::vector &getClangFrontendArgs() { return FrontendArgs; } + private: DiagnosticsEngine *Diags; FileManager *FM; + std::vector FrontendArgs; }; } // namespace installapi