Keeping pace with LLVM: compatibility strategies
2024-11-10 16:0:0 Author: maskray.me(查看原文) 阅读量:10 收藏

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
2
3
4
5
6
#if LLVM_VERSION_MAJOR < 8

namespace llvm {
namespace vfs = clang::vfs;
}
#endif

In Mar 2019, https://reviews.llvm.org/D59377 removed the member variable VirtualFileSystem and removed setVirtualFileSystem. I added

1
2
3
4
5
6
#if LLVM_VERSION_MAJOR >= 9 
Clang->createFileManager(FS);
#else
Clang->setVirtualFileSystem(FS);
Clang->createFileManager();
#endif

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
2
3
4
5
#if LLVM_VERSION_MAJOR < 12 
driver::Driver d(args[0], llvm::sys::getDefaultTargetTriple(), *diags, vfs);
#else
driver::Driver d(args[0], llvm::sys::getDefaultTargetTriple(), *diags, "ccls", vfs);
#endif

In November 2020, https://reviews.llvm.org/D90890 changed an argument of ComputePreambleBounds from const llvm::MemoryBuffer *Buffer to const llvm::MemoryBufferRef &Buffer.

1
2
3
4
5
6
7
std::unique_ptr<llvm::MemoryBuffer> buf =
llvm::MemoryBuffer::getMemBuffer(content);
#if LLVM_VERSION_MAJOR >= 12
auto bounds = ComputePreambleBounds(*ci.getLangOpts(), *buf, 0);
#else
auto bounds = ComputePreambleBounds(*ci.getLangOpts(), buf.get(), 0);
#endif

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
2
3
4
5
6
7
8
9
10
11
12
  void ProcessOverloadCandidates(Sema &s, unsigned currentArg,
OverloadCandidate *candidates,
unsigned numCandidates
#if LLVM_VERSION_MAJOR >= 8
,
SourceLocation openParLoc
#endif
#if LLVM_VERSION_MAJOR >= 14
,
bool braced
#endif
) override {

In late 2022 and early 2023, there were many changes to migrate from llvm::Optional to std::optional.

1
2
3
4
5
6
7
8
#if LLVM_VERSION_MAJOR >= 16 
std::array<std::optional<StringRef>, 3>
#else
std::array<Optional<StringRef>, 3>
#endif
redir{StringRef(stdinPath), StringRef(path), StringRef()}; 0 ref
std::vector<StringRef> args{g_config->compilationDatabaseCommand, root}; 0 ref
if (sys::ExecuteAndWait(args[0], args, {}, redir, 0, 0, &err_msg) < 0) {

In Dec 2022, https://reviews.llvm.org/D137838 added a new LLVM library LLVMTargetParser. I adjusted ccls's CMakeLists.txt:

1
2
3
4
target_link_libraries(ccls PRIVATE LLVMOption LLVMSupport)
if(LLVM_VERSION_MAJOR GREATER_EQUAL 16)
target_link_libraries(ccls PRIVATE LLVMTargetParser)
endif()

In Sep 2023, https://github.com/llvm/llvm-project/pull/65647 changed CompilerInvocationRefBase to CompilerInvocationBase. I duplicated the code with ..

1
2
3
4
5
6
7
8
9
10
11
#if LLVM_VERSION_MAJOR >= 18
ci->getLangOpts().SpellChecking = false;
ci->getLangOpts().RecoveryAST = true;
ci->getLangOpts().RecoveryASTType = true;
#else
ci->getLangOpts()->SpellChecking = false;
#if LLVM_VERSION_MAJOR >= 11
ci->getLangOpts()->RecoveryAST = true;
ci->getLangOpts()->RecoveryASTType = true;
#endif
#endif

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
2
3
4
#if LLVM_VERSION_MAJOR >= 19
#define startswith starts_with
#define endswith ends_with
#endif

It's important to note that the converse approach

1
2
#define starts_with startswith
#define ends_with endswith

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:


文章来源: https://maskray.me/blog/2024-11-10-keeping-pace-with-llvm-compatibility-strategies
如有侵权请联系:admin#unsafe.sh