即时编译 C++:clang/libtooling 无法为 LLVM IR 设置 Triple
Compiling C++ on the fly: clang/libtooling fails to set Triple for LLVM IR
假设我想即时编译一个 C++ 字符串:
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();
不幸的是,似乎当 LLVM 尝试转换 IR 时,有一个异常说 Triple
未设置 (https://clang.llvm.org/docs/CrossCompilation.html#target-triple)。
是否可以使用 libtooling
或 libclang
来达到这个目的?
不幸的是,很难使用这些接口来创建合适的 LLVM 模块。唯一的方法是创建一个文件并编译文件,设置所有包含路径:
首先要添加很多内容:
#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>
然后我们需要围绕编译器实例设置所有引擎:
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();
在这里我们可以设置三元组以及我们想要的语言类型:
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());
我们可以检查已设置的选项(仅在此处更改选项是不够的)并添加详细信息:
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();
让我们添加所有包含 header 路径:
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();
这里应该有一种设置 file-like 字符串的方法(不使用 FrontendInputFile
),但不幸的是在 LLVM 7 中,有一个检查确保它是一个真实的文件...
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;
现在创建代码生成器操作并使编译器实例执行操作:
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
}
假设我想即时编译一个 C++ 字符串:
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();
不幸的是,似乎当 LLVM 尝试转换 IR 时,有一个异常说 Triple
未设置 (https://clang.llvm.org/docs/CrossCompilation.html#target-triple)。
是否可以使用 libtooling
或 libclang
来达到这个目的?
不幸的是,很难使用这些接口来创建合适的 LLVM 模块。唯一的方法是创建一个文件并编译文件,设置所有包含路径:
首先要添加很多内容:
#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>
然后我们需要围绕编译器实例设置所有引擎:
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();
在这里我们可以设置三元组以及我们想要的语言类型:
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());
我们可以检查已设置的选项(仅在此处更改选项是不够的)并添加详细信息:
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();
让我们添加所有包含 header 路径:
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();
这里应该有一种设置 file-like 字符串的方法(不使用 FrontendInputFile
),但不幸的是在 LLVM 7 中,有一个检查确保它是一个真实的文件...
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;
现在创建代码生成器操作并使编译器实例执行操作:
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
}