diff --git a/include/Compiler/Compiler.h b/include/Compiler/Compiler.h index 04692cb7..dad9fb1f 100644 --- a/include/Compiler/Compiler.h +++ b/include/Compiler/Compiler.h @@ -2,6 +2,7 @@ #include #include +#include namespace clice { @@ -81,10 +82,118 @@ private: private: std::string filepath; std::string content; - std::unique_ptr action; + std::unique_ptr action; std::unique_ptr instance; std::unique_ptr buffer; std::unique_ptr m_Resolver; }; +/// All information about AST. +struct ASTInfo { + std::unique_ptr action; + std::unique_ptr instance; + std::unique_ptr tokBuf; + + ASTInfo(std::unique_ptr action, + std::unique_ptr instance, + std::unique_ptr tokBuf) : + action(std::move(action)), instance(std::move(instance)), tokBuf(std::move(tokBuf)) {} + + ASTInfo(const ASTInfo&) = delete; + + ASTInfo(ASTInfo&&) = default; + + ~ASTInfo() { + if(action) { + action->EndSourceFile(); + } + } + + clang::Sema& sema() { + return instance->getSema(); + } + + clang::ASTContext& context() { + return instance->getASTContext(); + } + + clang::SourceManager& srcMgr() { + return instance->getSourceManager(); + } + + clang::Preprocessor& pp() { + return instance->getPreprocessor(); + } + + clang::TranslationUnitDecl* tu() { + return instance->getASTContext().getTranslationUnitDecl(); + } +}; + +struct PCHInfo : ASTInfo { + /// PCM file path. + std::string path; + /// Source file path. + std::string mainpath; + /// The content of source file used to build this PCM. + std::string preamble; + /// Files involved in building this PCM. + std::vector deps; + + PCHInfo(ASTInfo info, + llvm::StringRef path, + llvm::StringRef content, + llvm::StringRef mainpath, + clang::PreambleBounds bounds) : + ASTInfo(std::move(info)), path(std::move(path)), mainpath(mainpath) { + + preamble = content.substr(0, bounds.Size).str(); + if(bounds.PreambleEndsAtStartOfLine) { + preamble.append("@"); + } + } + + clang::PreambleBounds bounds() const { + /// We use '@' to mark the end of the preamble. + bool endAtStart = preamble.ends_with('@'); + unsigned int size = preamble.size() - endAtStart; + return {size, endAtStart}; + } +}; + +struct PCMInfo : ASTInfo {}; + +/// Information about reuse PCH or PCM. This should be placed in stack. +struct Preamble { + /// Information about reuse PCH. + std::string pch; + clang::PreambleBounds bounds = {0, false}; + + /// Information about reuse PCM(name, path). + llvm::SmallVector> pcms; + + void addPCH(const PCHInfo& info) { + pch = info.path; + bounds = info.bounds(); + } +}; + +/// Build AST from given file path and content. If pch or pcm provided, apply them to the compiler. +/// Note this function will not check whether we need to update the PCH or PCM, caller should check +/// their reusability and update in time. +llvm::Expected buildAST(llvm::StringRef path, + llvm::StringRef content, + llvm::ArrayRef args, + Preamble* preamble = nullptr); + +llvm::Expected buildPCH(llvm::StringRef path, + llvm::StringRef content, + llvm::StringRef outpath, + llvm::ArrayRef args); + +llvm::Expected buildPCM(llvm::StringRef path, + llvm::StringRef content, + llvm::StringRef outpath, + llvm::ArrayRef args); + } // namespace clice diff --git a/include/Support/Format.h b/include/Support/Format.h index f7bac512..9c490690 100644 --- a/include/Support/Format.h +++ b/include/Support/Format.h @@ -4,6 +4,7 @@ #include "llvm/ADT/StringRef.h" #include "Error.h" +#include "JSON.h" template <> struct std::formatter : std::formatter { @@ -38,3 +39,21 @@ struct std::formatter : std::formatter { } }; +template <> +struct std::formatter : std::formatter { + using Base = std::formatter; + + template + constexpr auto parse(ParseContext& ctx) { + return Base::parse(ctx); + } + + template + auto format(const clice::json::Value& value, FormatContext& ctx) const { + llvm::SmallString<128> buffer; + llvm::raw_svector_ostream os(buffer); + os << value; + return Base::format(buffer, ctx); + } +}; + diff --git a/src/Compiler/Compiler.cpp b/src/Compiler/Compiler.cpp index c7826cfe..a16be973 100644 --- a/src/Compiler/Compiler.cpp +++ b/src/Compiler/Compiler.cpp @@ -130,18 +130,11 @@ void Compiler::ExecuteAction() { std::terminate(); } - llvm::outs() << instance->getLangOpts().Modules << "\n"; - llvm::outs() << instance->getLangOpts().CPlusPlusModules << "\n"; - if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { llvm::errs() << "Failed to begin source file\n"; std::terminate(); } - /// llvm::outs() << instance->getPreprocessorOpts().ImplicitPCHInclude << "\n"; - - /// instance->getASTContext().setExternalSource(nullptr); - auto& preproc = instance->getPreprocessor(); // FIXME: add PPCallbacks to collect information. @@ -167,4 +160,133 @@ Compiler::~Compiler() { } } +static auto createInstance(llvm::ArrayRef args) { + auto instance = std::make_unique(); + + /// TODO: Figure out `CreateInvocationOptions`. + clang::CreateInvocationOptions options = {}; + instance->setInvocation(clang::createInvocation(args, options)); + + /// TODO: use a thread safe filesystem and our customized `DiagnosticConsumer`. + instance->createDiagnostics( + *llvm::vfs::getRealFileSystem(), + new clang::TextDiagnosticPrinter(llvm::outs(), new clang::DiagnosticOptions()), + true); + + adjustInvocation(instance->getInvocation()); + + return instance; +} + +static llvm::Expected ExecuteAction(std::unique_ptr instance, + clang::frontend::ActionKind kind) { + std::unique_ptr action; + if(kind == clang::frontend::ActionKind::ParseSyntaxOnly) { + action = std::make_unique(); + } else if(kind == clang::frontend::ActionKind::GeneratePCH) { + action = std::make_unique(); + } else if(kind == clang::frontend::ActionKind::GenerateReducedModuleInterface) { + action = std::make_unique(); + } else { + llvm::errs() << "Unsupported action kind\n"; + std::terminate(); + } + + if(!instance->createTarget()) { + return error("Failed to create target"); + } + + if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { + return error("Failed to begin source file"); + } + + // FIXME: clang-tidy, include-fixer, etc? + + // `BeginSourceFile` may create new preprocessor, so all operations related to preprocessor + // should be done after `BeginSourceFile`. + auto& PP = instance->getPreprocessor(); + clang::syntax::TokenCollector collector{PP}; + + if(auto error = action->Execute()) { + return clice::error("Failed to execute action, because {} ", error); + } + + auto tokBuf = std::make_unique(std::move(collector).consume()); + tokBuf->indexExpandedTokens(); + + return ASTInfo(std::move(action), std::move(instance), std::move(tokBuf)); +} + +llvm::Expected buildAST(llvm::StringRef path, + llvm::StringRef content, + llvm::ArrayRef args, + Preamble* preamble) { + auto instance = createInstance(args); + + auto& PPOpts = instance->getPreprocessorOpts(); + + auto buffer = llvm::MemoryBuffer::getMemBufferCopy(content); + /// FIXME: Check PPOpts.RetainRemappedFileBuffers. + PPOpts.addRemappedFile(path, buffer.release()); + + if(preamble) { + auto& [pch, bounds, pcms] = *preamble; + if(bounds.Size != 0) { + PPOpts.UsePredefines = false; + PPOpts.ImplicitPCHInclude = std::move(pch); + PPOpts.PrecompiledPreambleBytes.first = bounds.Size; + PPOpts.PrecompiledPreambleBytes.second = bounds.PreambleEndsAtStartOfLine; + PPOpts.DisablePCHOrModuleValidation = clang::DisableValidationForModuleKind::PCH; + } + + for(auto& [name, path]: pcms) { + auto& HSOpts = instance->getHeaderSearchOpts(); + HSOpts.PrebuiltModuleFiles.try_emplace(std::move(name), std::move(path)); + } + } + + return ExecuteAction(std::move(instance), clang::frontend::ActionKind::ParseSyntaxOnly); +} + +llvm::Expected buildPCH(llvm::StringRef path, + llvm::StringRef content, + llvm::StringRef outpath, + llvm::StringRef mainpath, + std::size_t index, + llvm::ArrayRef args) { + auto instance = createInstance(args); + + clang::PreambleBounds bounds = {0, false}; + if(mainpath == path) { + /// If mainpath is equal to path, just tokenize the content to get preamble bounds. + bounds = clang::Lexer::ComputePreamble(content, {}, false); + } else { + /// FIXME: if the mainpath is not equal to path, we need to preprocess the mainpath to get + /// the preamble bounds. + std::terminate(); + } + + instance->getFrontendOpts().OutputFile = outpath; + instance->getFrontendOpts().ProgramAction = clang::frontend::GeneratePCH; + instance->getPreprocessorOpts().PrecompiledPreambleBytes = {0, false}; + instance->getPreprocessorOpts().GeneratePreamble = true; + instance->getLangOpts().CompilingPCH = true; + + auto buffer = llvm::MemoryBuffer::getMemBufferCopy(content.substr(0, bounds.Size)); + instance->getPreprocessorOpts().addRemappedFile(path, buffer.release()); + + if(auto info = ExecuteAction(std::move(instance), clang::frontend::ActionKind::GeneratePCH)) { + return PCHInfo(std::move(*info), outpath, mainpath, content, bounds); + } else { + return info.takeError(); + } +} + +llvm::Expected buildPCH(llvm::StringRef path, + llvm::StringRef content, + llvm::StringRef outpath, + llvm::ArrayRef args) { + return buildPCH(path, content, outpath, path, 0, args); +} + } // namespace clice diff --git a/unittests/Compiler/Compiler.cpp b/unittests/Compiler/Compiler.cpp new file mode 100644 index 00000000..0c6be0bd --- /dev/null +++ b/unittests/Compiler/Compiler.cpp @@ -0,0 +1,70 @@ +#include "../Test.h" +#include "Compiler/Compiler.h" +#include + +namespace { + +using namespace clice; + +TEST(Compiler, buildAST) { + const char* code = R"cpp( +#include + +int main(){ + printf("Hello world"); + return 0; +} +)cpp"; + + llvm::SmallVector compileArgs = { + "clang++", + "-std=c++20", + "main.cpp", + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + + auto info = buildAST("main.cpp", code, compileArgs); + ASSERT_TRUE(bool(info)); +} + +TEST(Compiler, buildPCH) { + const char* code = R"cpp( +#include + +int main(){ + printf("Hello world"); + return 0; +} +)cpp"; + + llvm::SmallVector compileArgs = { + "clang++", + "-std=c++20", + "main.cpp", + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + + llvm::SmallString<128> outpath; + if(auto error = llvm::sys::fs::createTemporaryFile("main", "pch", outpath)) { + llvm::errs() << error.message() << "\n"; + return; + } + + if(auto error = fs::remove(outpath)) { + llvm::errs() << error.message() << "\n"; + return; + } + + auto pch = clice::buildPCH("main.cpp", code, outpath, compileArgs); + ASSERT_TRUE(bool(pch)); + + Preamble preamble; + preamble.addPCH(*pch); + + auto ast = buildAST("main.cpp", code, compileArgs, &preamble); + ASSERT_TRUE(bool(ast)); +} + +} // namespace