LLVM's C++ API doesn't offer a stability guarantee. This means function signatures can change or be removed between versions, forcing projects to adapt.
On the other hand, LLVM has an extensive API surface. When a library
like llvm/lib/Y
relies functionality from another library,
the API is often exported in header files under
llvm/include/llvm/X/
, even if it is not intended to be
user-facing.
To be compatible with multiple LLVM versions, many projects rely on
#if
directives based on the LLVM_VERSION_MAJOR
macro. This post explores the specific techniques used by ccls to ensure
compatibility with LLVM versions 7 to 19.
Given the tight coupling between LLVM and Clang, the
LLVM_VERSION_MAJOR
macro can be used for both version
detection. There's no need to check
CLANG_VERSION_MAJOR
.
This post explores the specific techniques used by ccls to maintain compatibility with LLVM versions 7 to 19. For the latest release, support for LLVM versions 7 to 9 has been discontinued.
In Oct 2018, https://reviews.llvm.org/D52783 moved the namespace
clang::vfs
to llvm::vfs
. To remain
compatibility, I renamed clang::vfs
uses and added a
namespace alias:
1 | #if LLVM_VERSION_MAJOR < 8 |
In Mar 2019, https://reviews.llvm.org/D59377 removed the member
variable VirtualFileSystem
and removed
setVirtualFileSystem
. I added
1 | #if LLVM_VERSION_MAJOR >= 9 |
In April 2020, the LLVM monorepo integrated a new subproject: flang.
flang developers made many changes to clangDriver to reuse it for flang.
https://reviews.llvm.org/D86089 changed the constructor
clang::driver::Driver
. I added
1 | #if LLVM_VERSION_MAJOR < 12 |
In November 2020, https://reviews.llvm.org/D90890 changed an argument of
ComputePreambleBounds
from
const llvm::MemoryBuffer *Buffer
to
const llvm::MemoryBufferRef &Buffer
.
1 | std::unique_ptr<llvm::MemoryBuffer> buf = |
https://reviews.llvm.org/D91297 made a similar change and I adapted it similarly.
In Jan 2022, https://reviews.llvm.org/D116317 added a new parameter
bool Braced
to
CodeCompleteConsumer::ProcessOverloadCandidates
.
1 | void ProcessOverloadCandidates(Sema &s, unsigned currentArg, |
In late 2022 and early 2023, there were many changes to migrate from
llvm::Optional
to std::optional
.
1 | #if LLVM_VERSION_MAJOR >= 16 |
In Dec 2022, https://reviews.llvm.org/D137838 added a new LLVM
library LLVMTargetParser. I adjusted ccls's CMakeLists.txt
:
1 | target_link_libraries(ccls PRIVATE LLVMOption LLVMSupport) |
In Sep 2023, https://github.com/llvm/llvm-project/pull/65647 changed
CompilerInvocationRefBase
to
CompilerInvocationBase
. I duplicated the code with
.
.
1 | #if LLVM_VERSION_MAJOR >= 18 |
In April 2024, https://github.com/llvm/llvm-project/pull/89548/ removed
llvm::StringRef::startswith
in favor of
starts_with
. starts_with
has been available since Oct 2022 and
startswith
had been deprecated. I added the following
snippet:
1 | #if LLVM_VERSION_MAJOR >= 19 |
It's important to note that the converse approach
1 | #define starts_with startswith |
could break code that calls
std::string_view::starts_with
.
LLVM C API
While LLVM offers a C API with an effort made towards compatibility, its capabilities often fall short.
Clang provides a C API called libclang. While highly stable, libclang's limited functionality makes it unsuitable for many tasks.
In 2018, when creating ccls (a fork of cquery), I encountered multiple limitations in libclang's ability to handle code completion and indexing. This led to rewriting the relevant code to leverage the Clang C++ API for a more comprehensive solution. The following commits offer insights into how the C API and the mostly equivalent but better C++ API works: