Search code examples
c++clangllvmllvm-irllvm-c++-api

Compiling C++ on the fly: clang/libtooling fails to set Triple for LLVM IR


Let's say I want to compile a C++ string on the fly:

llvm::LLVMContext context;

std::unique_ptr<clang::CodeGenAction> action = std::make_unique<clang::EmitLLVMOnlyAction>(&context);
clang::tooling::runToolOnCode/*WithArgs*/(action.get(), "int foo(int x){ return ++x;}");

std::unique_ptr<llvm::Module> module = action->takeModule();

Unfortunately, it seems that when LLVM tries to transform the IR, there is an exception saying that the Triple is not set (https://clang.llvm.org/docs/CrossCompilation.html#target-triple).

Is it possible to use libtooling or libclang for this purpose?


Solution

  • Unfortunately, it's difficult to use these interfaces to create a proper LLVM module. The only way is even to create a file and compile the file, setting all the include paths:

    First there are lots of includes to add:

    #include <clang/AST/ASTContext.h>
    #include <clang/AST/ASTConsumer.h>
    #include <clang/Basic/DiagnosticOptions.h>
    #include <clang/Basic/Diagnostic.h>
    #include <clang/Basic/FileManager.h>
    #include <clang/Basic/FileSystemOptions.h>
    #include <clang/Basic/LangOptions.h>
    #include <clang/Basic/MemoryBufferCache.h>
    #include <clang/Basic/SourceManager.h>
    #include <clang/Basic/TargetInfo.h>
    #include <clang/CodeGen/CodeGenAction.h>
    #include <clang/Frontend/CompilerInstance.h>
    #include <clang/Frontend/CompilerInvocation.h>
    #include <clang/Frontend/TextDiagnosticPrinter.h>
    #include <clang/Lex/HeaderSearch.h>
    #include <clang/Lex/HeaderSearchOptions.h>
    #include <clang/Lex/Preprocessor.h>
    #include <clang/Lex/PreprocessorOptions.h>
    #include <clang/Parse/ParseAST.h>
    #include <clang/Sema/Sema.h>
    

    Then we need to set up all the engines around the compiler instance:

    clang::DiagnosticOptions diagnosticOptions;
    std::unique_ptr<clang::TextDiagnosticPrinter> textDiagnosticPrinter =
      std::make_unique<clang::TextDiagnosticPrinter>(llvm::outs(),
                                                     &diagnosticOptions);
    llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagIDs;
    
    std::unique_ptr<clang::DiagnosticsEngine> diagnosticsEngine =
      std::make_unique<clang::DiagnosticsEngine>(diagIDs, &diagnosticOptions, textDiagnosticPrinter.get());
    
    clang::CompilerInstance compilerInstance;
    auto& compilerInvocation = compilerInstance.getInvocation();
    

    This is where we can set the triple and also the type of langage we want:

    std::stringstream ss;
    ss << "-triple=" << llvm::sys::getDefaultTargetTriple();
    ss << " -x c++"; // to activate C++
    ss << " -fcxx-exceptions";
    ss << " -std=c++17";
    
    std::istream_iterator<std::string> begin(ss);
    std::istream_iterator<std::string> end;
    std::istream_iterator<std::string> i = begin;
    std::vector<const char*> itemcstrs;
    std::vector<std::string> itemstrs;
    while (i != end) {
      itemstrs.push_back(*i);
      ++i;
    }
    
    for (unsigned idx = 0; idx < itemstrs.size(); idx++) {
      // note: if itemstrs is modified after this, itemcstrs will be full
      // of invalid pointers! Could make copies, but would have to clean up then...
      itemcstrs.push_back(itemstrs[idx].c_str());
    }
    
    clang::CompilerInvocation::CreateFromArgs(compilerInvocation, itemcstrs.data(), itemcstrs.data() + itemcstrs.size(),
     *diagnosticsEngine.release());
    

    We can check then options that were set (changing options just here is not enough) and add verbosity:

    auto* languageOptions = compilerInvocation.getLangOpts();
    auto& preprocessorOptions = compilerInvocation.getPreprocessorOpts();
    auto& targetOptions = compilerInvocation.getTargetOpts();
    auto& frontEndOptions = compilerInvocation.getFrontendOpts();
    #ifdef DEBUG
    frontEndOptions.ShowStats = true;
    #endif
    auto& headerSearchOptions = compilerInvocation.getHeaderSearchOpts();
    

    Let's add all include header path:

    constexpr std::string_view paths[] = {"/usr/include/c++/8",
      "/usr/include/x86_64-linux-gnu/c++/8",
      "/usr/include/c++/8/backward",
      "/usr/include/clang/6.0.0/include",
      "/usr/local/include",
      "/usr/include/x86_64-linux-gnu",
      "/usr/include"};
    
    for(auto path: paths)
    {
        headerSearchOptions.AddPath(std::string(path), clang::frontend::IncludeDirGroup::Angled, false, false);
    }
    
    #ifdef DEBUG
    headerSearchOptions.Verbose = true;
    #endif
    auto& codeGenOptions = compilerInvocation.getCodeGenOpts();
    

    There should be here a way of setting a file-like string (not using FrontendInputFile), but unfortunately in LLVM 7, there is a check ensuring it's a real file...

    frontEndOptions.Inputs.clear();
    frontEndOptions.Inputs.push_back(clang::FrontendInputFile(filename, clang::InputKind::CXX));
    
    targetOptions.Triple = llvm::sys::getDefaultTargetTriple();
    compilerInstance.createDiagnostics(textDiagnosticPrinter.get(), false);
    
    LLVM::Context context;
    

    Create now the code generator action and make the compiler instance execute the action:

    std::unique_ptr<clang::CodeGenAction> action = std::make_unique<clang::EmitLLVMOnlyAction>(&context);
    
    if (!compilerInstance.ExecuteAction(*action))
    {
        // Failed to compile, and should display on cout the result of the compilation
    }