This post describes how to compile a single C++ source file to anobject file with the Clang API. Here is the code. It behaves like asimplified clang executable that handles -cand -S.
auto invoc = std::make_unique<CompilerInvocation>(); CompilerInvocation::CreateFromArgs(*invoc, ccArgs, *diags); auto ci = std::make_unique<CompilerInstance>(); ci->setInvocation(std::move(invoc)); ci->createDiagnostics(*fs, &dc, false); // Disable CompilerInstance::printDiagnosticStats, which might display "2 warnings generated." ci->getDiagnostics().getDiagnosticOptions().ShowCarets = false; ci->createFileManager(fs); ci->createSourceManager(ci->getFileManager());
// Clang calls BuryPointer on the internal AST and CodeGen-related elements like TargetMachine. // This will cause memory leaks if `compile` is executed many times. ci->getCodeGenOpts().DisableFree = false; ci->getFrontendOpts().DisableFree = false;
We need an LLVM and Clang installation that provides bothlib/cmake/llvm/LLVMConfig.cmake andlib/cmake/clang/ClangConfig.cmake. You can grab these fromsystem packages (dev versions may be required) or build LLVMyourself-I'll skip the detailed steps here. For a DIY build, use:
1 2 3
# cmake ... -DLLVM_ENABLE_PROJECTS='clang'
ninja -C out/stable clang-cmake-exports clang
No install step is needed. Next, create a builddirectory with the CMake configuration above:
I've set a prebuilt Clang as CMAKE_CXX_COMPILER-just ahabit of mine. llvm-project isn't guaranteed to build warning-free withGCC, since GCC -Wall -Wextra has many false positives andLLVM developers avoid cluttering the codebase.
1 2 3 4 5 6 7 8 9
% echo 'void f() {}' > a.cc % out/debug/cc -S a.cc && head -n 5 a.s .file "a.cc" .text .globl _Z1fv # -- Begin function _Z1fv .p2align 4 .type _Z1fv,@function % out/debug/cc -c a.cc && ls a.o a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Anonymous files
The input source file and the output ELF file are stored in thefilesystem. We could create a temporary file and delete it with a RAIIclass llvm::FileRemover:
LLVMX86AsmParser: llvm/lib/Target/X86/AsmParser(depends on LLVMX86Info and LLVMX86Desc)
LLVMX86CodeGen: llvm/lib/Target/X86/ (depends onLLVMX86Info and LLVMX86Desc)
EmitAssembly andEmitObj
The code supports two frontend actions, EmitAssembly(-S) and EmitObj (-c).
You could also utilize the API inclang/include/clang/FrontendTool/Utils.h, but that wouldpull in another library clangFrontendTool (different fromclangFrontend).
Diagnostics
The diagnostics system is quite complex. We haveDiagnosticConsumer, DiagnosticsEngine, andDiagnosticOptions.
We define a simple DiagnosticConsumer that handlesnotes, warnings, errors, and fatal errors. When macro expansion comesinto play, we report two key locations:
The physical location (fileLoc), where the expandedtoken triggers an issue-matching Clang's error line, and
The spelling location within the macro's replacement list(sm.getSpellingLoc(loc)).
Although Clang also highlights intermediate locations for chainedexpansions, our simple approach offers a solid approximation.
% cat a.h #define FOO(x) x + 1 % cat a.cc #include "a.h" #define BAR FOO void f() { int y = BAR("abc"); } % out/debug/cc -c -Wall a.cc a.cc:4:11: warning: adding 'int' to a string does not append to the string ./a.h:1:18: note: expanded from macro a.cc:4:11: note: use array indexing to silence this warning ./a.h:1:18: note: expanded from macro a.cc:4:7: error: cannot initialize a variable of type 'int' with an rvalue of type 'const char *' % clang -c -Wall a.cc a.cc:4:11: warning: adding 'int' to a string does not append to the string [-Wstring-plus-int] 4 | int y = BAR("abc"); | ^~~~~~~~~~ a.cc:2:13: note: expanded from macro 'BAR' 2 | #define BAR FOO | ^ ./a.h:1:18: note: expanded from macro 'FOO' 1 | #define FOO(x) x + 1 | ~~^~~ a.cc:4:11: note: use array indexing to silence this warning a.cc:2:13: note: expanded from macro 'BAR' 2 | #define BAR FOO | ^ ./a.h:1:18: note: expanded from macro 'FOO' 1 | #define FOO(x) x + 1 | ^ a.cc:4:7: error: cannot initialize a variable of type 'int' with an rvalue of type 'const char *' 4 | int y = BAR("abc"); | ^ ~~~~~~~~~~ 1 warning and 1 error generated.
We call a convenience functionCompilerInstance::ExecuteAction, which wraps lower-levelAPI like BeginSource, Execute, andEndSource. However, it will print1 warning and 1 error generated. unless we setShowCarets to false.
clang::createInvocation
clang::createInvocation, renamed from createInvocationFromCommandLinein 2022, combines clang::Driver::BuildCompilation andclang::CompilerInvocation::CreateFromArgs. While it saves afew lines for certain tasks, it lacks the flexibility we need for ourspecific use cases.