From 4ccc577f5706fb90957e9b167eb76a77e4837f1b Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 7 Dec 2024 23:44:35 +0800 Subject: [PATCH] Implement `computeBounds`. --- include/Compiler/Compiler.h | 13 ++-- src/Compiler/Compiler.cpp | 101 +++++++++++++++++++++++++++++++- unittests/Compiler/Compiler.cpp | 73 ++++++++++++++++++++--- 3 files changed, 174 insertions(+), 13 deletions(-) diff --git a/include/Compiler/Compiler.h b/include/Compiler/Compiler.h index b6bec12d..69db1482 100644 --- a/include/Compiler/Compiler.h +++ b/include/Compiler/Compiler.h @@ -141,12 +141,19 @@ struct CompliationParams { /// `#include` directive that includes the header to speed up the parsing. std::optional bounds; - /// Output file path. - llvm::SmallString<128> outPath; + /// Computes the preamble bounds for the given content. + /// If the bounds are not provided explicitly, they will be calculated based on the content. + /// + /// - If the header is empty, the bounds can be determined by lexing the source file. + /// - If the header is not empty, the preprocessor must be executed to compute the bounds. + void computeBounds(llvm::StringRef header = ""); /// Command line arguments. llvm::ArrayRef args; + /// Output file path. + llvm::SmallString<128> outPath; + llvm::IntrusiveRefCntPtr vfs = new ThreadSafeFS(); /// Information about reuse PCH. @@ -161,8 +168,6 @@ struct CompliationParams { uint32_t line = 0; uint32_t column = 0; - void computeBounds(); - void addPCH(const PCHInfo& info) { pch = info.path; pchBounds = info.bounds(); diff --git a/src/Compiler/Compiler.cpp b/src/Compiler/Compiler.cpp index b33bcef7..2dbb99b9 100644 --- a/src/Compiler/Compiler.cpp +++ b/src/Compiler/Compiler.cpp @@ -46,11 +46,14 @@ auto createInstance(CompliationParams& params) { adjustInvocation(instance->getInvocation()); - /// FIXME: figure out whether we need to retain remapped file buffers. /// Add remapped files, if bounds is provided, cut off the content. std::size_t size = params.bounds.has_value() ? params.bounds.value().Size : params.content.size(); - auto buffer = llvm::MemoryBuffer::getMemBufferCopy(params.content.substr(0, size)); + + assert(!instance->getPreprocessorOpts().RetainRemappedFileBuffers && + "RetainRemappedFileBuffers should be false"); + auto buffer = + llvm::MemoryBuffer::getMemBufferCopy(params.content.substr(0, size), params.srcPath); instance->getPreprocessorOpts().addRemappedFile(params.srcPath, buffer.release()); return instance; @@ -132,6 +135,100 @@ llvm::Expected ExecuteAction(std::unique_ptr i } // namespace +void CompliationParams::computeBounds(llvm::StringRef header) { + assert(!bounds.has_value() && "Bounds is already computed"); + assert(!content.empty() && "Source content is required to compute bounds"); + + if(header.empty()) { + auto invocation = clang::createInvocation(args, {}); + bounds = clang::Lexer::ComputePreamble(content, invocation->getLangOpts()); + return; + } + + auto instance = createInstance(*this); + + instance->getFrontendOpts().ProgramAction = clang::frontend::RunPreprocessorOnly; + + struct SearchBoundary : public clang::PPCallbacks { + llvm::StringRef header; + clang::SourceLocation& hashLoc; + + SearchBoundary(llvm::StringRef header, clang::SourceLocation& hashLoc) : + header(header), hashLoc(hashLoc) {} + + void InclusionDirective(clang::SourceLocation hashLoc, + const clang::Token& includeTok, + llvm::StringRef filename, + bool isAngled, + clang::CharSourceRange filenameRange, + clang::OptionalFileEntryRef file, + llvm::StringRef searchPath, + llvm::StringRef relativePath, + const clang::Module* suggestedModule, + bool moduleImported, + clang::SrcMgr::CharacteristicKind fileType) override { + llvm::SmallString<128> path; + if(searchPath != ".") { + path::append(path, searchPath); + } + path::append(path, relativePath); + if(path == header) { + this->hashLoc = hashLoc; + } + } + }; + + /// The hash location of the include directive that includes the header. + clang::SourceLocation hashLoc; + + clang::PreprocessOnlyAction action; + + /// FIXME: merge the logic to `ExecuteAction`. + if(!instance->createTarget()) { + llvm::errs() << "Failed to create target\n"; + std::terminate(); + } + + if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { + llvm::errs() << "Failed to begin source file\n"; + std::terminate(); + } + + instance->getPreprocessor().addPPCallbacks(std::make_unique(header, hashLoc)); + + if(auto error = action.Execute()) { + llvm::errs() << "Failed to execute action, because " << error << "\n"; + std::terminate(); + } + + if(hashLoc.isInvalid()) { + llvm::errs() << "Failed to find the boundary\n"; + std::terminate(); + } + + /// Find the top level file. + auto& srcMgr = instance->getSourceManager(); + while(srcMgr.getIncludeLoc(srcMgr.getFileID(hashLoc)).isValid()) { + hashLoc = srcMgr.getIncludeLoc(srcMgr.getFileID(hashLoc)); + } + auto offset = srcMgr.getFileOffset(hashLoc); + + action.EndSourceFile(); + + /// We need to move to next line to get the correct bounds. + for(auto i = offset; i < content.size(); ++i) { + if(content[i] == '\n') { + bounds = {i + 2, true}; + break; + } + } + + if(!bounds.has_value()) { + llvm::errs() << "Failed to compute bounds\n"; + std::terminate(); + } +} + llvm::Expected compile(CompliationParams& params) { auto instance = createInstance(params); diff --git a/unittests/Compiler/Compiler.cpp b/unittests/Compiler/Compiler.cpp index 6bea7b99..7d9cbfd0 100644 --- a/unittests/Compiler/Compiler.cpp +++ b/unittests/Compiler/Compiler.cpp @@ -33,6 +33,69 @@ int main(){ ASSERT_TRUE(bool(info)); } +TEST(Compiler, ComputeBounds) { + + llvm::SmallVector compileArgs = { + "clang++", + "-std=c++20", + "main.cpp", + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + + const char* code = R"cpp( +#include +int main(){ + printf("Hello world"); + return 0; +})cpp"; + + /// Test in no header file. + CompliationParams params; + params.srcPath = "main.cpp"; + params.args = compileArgs; + params.content = code; + params.computeBounds(); + + ASSERT_TRUE(params.bounds.has_value()); + ASSERT_EQ(params.bounds->Size, 19); + + params.bounds.reset(); + + compileArgs = {"clang++", "-std=c++20", "main.cpp"}; + + std::unique_ptr vfs(new vfs::InMemoryFileSystem); + const char* header = R"cpp( +#include "target.h" +)cpp"; + + vfs->addFile("header.h", 0, llvm::MemoryBuffer::getMemBuffer(header)); + vfs->addFile("header2.h", 0, llvm::MemoryBuffer::getMemBuffer("")); + vfs->addFile("target.h", 0, llvm::MemoryBuffer::getMemBuffer("")); + + code = R"cpp( +#include "header2.h" +#include "header.h" +int main(){ + return 0; +})cpp"; + + compileArgs = { + "clang++", + "-std=c++20", + "main.cpp", + }; + + params.srcPath = "main.cpp"; + params.args = compileArgs; + params.content = code; + params.vfs = std::move(vfs); + params.computeBounds("target.h"); + + ASSERT_TRUE(params.bounds.has_value()); + ASSERT_EQ(params.bounds->Size, 43); +} + TEST(Compiler, buildPCH) { const char* code = R"cpp( #include @@ -63,14 +126,16 @@ int main(){ } CompliationParams params; - params.srcPath = "main.cpp"; params.content = code; + params.srcPath = "main.cpp"; params.outPath = outpath; params.args = compileArgs; + params.computeBounds(); PCHInfo pch; ASSERT_TRUE(bool(clice::compile(params, pch))); + params.bounds.reset(); params.addPCH(pch); auto ast = compile(params); @@ -122,12 +187,6 @@ int main(){ } )cpp"; - compileArgs = { - "clang++", - "-std=c++20", - "main.cpp", - }; - params.srcPath = "main.cpp"; params.content = code2; params.args = compileArgs;