We've talked about adding the title to the filename several times over the years and it seems really valuable. This requires us to compute a "slug" for the title spelling that can be part of the filename. Beyond that, we crossed 7000 recently, and so it seems likely that we will need to add digits sooner rather than later here, so this goes ahead and moves us to 6 digits so we don't have to adjust again for a reasonable length of time. To implement this and ensure we can sustain it going forward this adds a tool to our pre-commit that validates (and corrects if needed) the filename. In order to update everything and keep links working, there are a _lot_ of changes, but the most interesting for direct review are in `proposals/scripts`. Assisted-by: Antigravity with Gemini --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk>
8.2 KiB
Clang API usage survey
Table of contents
Overview
Clang performs the equivalent of Carbon's lower progressively, interleaved
with parsing/semantic analysis. This is in conflict with Carbon's phase-based
approach and leads to bugs in missing functionality in Clang's generated IR
during Carbon/C++ interop.
We analyze different uses of Clang's APIs to better understand the tradeoffs between them, and propose a direction for Carbon.
Different uses of Clang's APIs
There are several different users of Clang's APIs that take different approaches to address their needs (needs more or less similar to Carbon's), below is a survey of those approaches:
Clang
clang::CodeGeneratorImpl
is registered during clang’s parsing/semantic analysis, receiving callbacks
during that process rather than in a batch phase afterwards. Specifically, the
CodeGeneratorImpl has several virtual function callbacks that handle various
features. I tested clang by disabling or asserting in the various callbacks to
identify tests/examples that rely on the callback. I was then able to verify
that Carbon had missing functionality due to not implementing the callback using
a test like this:
test.cpp
// some code
test.carbon
import Cpp inline '''
#include "test.cpp"
''';
$ diff <(clang++ test.cpp -c && nm test.o) \
<(carbon compile interop.carbon --optimize=none && nm interop.o)
Any difference should be a bug in the Carbon compiler's interop support. Here
are some (non-exhaustive) examples I found, based on different callbacks in the
ASTConsumer API:
HandleCXXStaticMemberVarInstantiationhandles instantiating C++ static member variables in template contexts like this:
template<typename T>struct t3 {
static int i;
};
template<typename T>int t3<T>::i;
void f1() {
// Without the callback, t3<int>::i is not emitted. t3<int>::i = 42;
}
HandleTopLevelDeclthis is the main callback that handles each top level (nested only within namespaces - not within another class or function) declarations for code generationEmitDeferredDecls+HandleInlineFunctionDefinitionfor emitting inline function definitions in certain situations, like this:
struct t2 {
// Without the callback, `func`'s definition is not emitted.
__attribute__((used)) void func() {}
};
HandleTagDeclDefinitionupdates types in the IR when a definition is provided later (not relevant to Carbon or Swift since they only generate the IR once the AST is complete anyway), eg:
struct S;
extern S a[10];
S(*b)[10] = &a;
struct S {
int x;
};
// Without the callback, this code still compiles,
// but uses a gep over a raw byte array, whereas
// with the callback it uses a gep over the `struct S` type.
int f() { return a[3].x; }
HandleTagDeclRequiredDefinitionseems to be just for Microsoft debug info.HandleTranslationUnithandles finishing things up after the translation unit - Carbon can call this & get the same behavior.AssignInheritanceModelrelated to the Microsoft inheritance attribute for.CompleteTentativeDefinitionseems to be only relevant to C code, not C++.CompleteExternalDeclarationseems to be only relevant to the BPF target.HandleVTableemits vtables as needed, eg:
struct t1 {
virtual void f1();
};
// Without the callback, the vtable is not emitted despite
// the appearance of this key function definition.
void t1::f1() { }
Swift
- Swift supports C++ -> Swift interop
by generating a Swift library with a matched
MyLib-Swift.hheader file, unmodified Clang can then parse that header for calling into the Swift library - Swift->C++ can’t do template instantiation, only interacting with class templates already instantiated in C++ with C++ parameters - so nothing like Carbon’s closer interop (that already allows new instantiations from Carbon using C++ types as parameters, and will allow instantiating a C++ type with a Carbon type as a parameter)
- Swift constructs the
clang::CodeGeneratoritself (rather than by way ofclang-the-compiler-like use) inswift::IRGenModule- Dealing with the inline function problem, Swift uses
IRGenModule::emitClangDeclwhenever it needs a decl from Clang for a call from Swift. - It recurses through decls in the decl that swift requires searching for
other decls that might need to be emitted - this search is all done in
Swift’s
IRGenModule::emitClangDecl. - Ultimately any decls found by way of this recursion are passed to
clang::CodeGenerator::HandleTopLevelDecl
- Dealing with the inline function problem, Swift uses
LLDB
clang::ParseASTwith theclang::CodeGeneratoralready registered- looks basically like Clang, doesn’t need to separate parsing from IRGen, so it doesn’t have the problems Carbon and Swift do
Carbon previously
checkused a Clang Tooling based API,clang::tooling::buildASTFromCodeWithArgscheckdoes things to the AST, trigger template instantiation, etclowerusesclang::CreateLLVMCodeGento create a code generator for the AST- Carbon handles passing ASTs to this
clang::CodeGenerator - Limitations have been partially addressed by
PR6237
- Clang’s Sema does at least keep a list of top level decls that need to
be visited by the code generator, so this solves the inline-calls-inline
situation by essentially replaying the
HandleTopLevelDeclcallback. - Expected that the clang<>carbon divergence is still an outstanding risk.
- This work laid more of a foundation for doing something like PR5543 (keeping the clang::CodeGenerator attached through Sema/SemIR) but without the need for multithreading, because it’s effectively inlined the FrontendAction execution into Clang, which is relatively little code/risk of divergence. This ended up landing in PR6569
- Clang’s Sema does at least keep a list of top level decls that need to
be visited by the code generator, so this solves the inline-calls-inline
situation by essentially replaying the
Carbon approach
Since PR5543, several changes (especially PR6237) have been made to Clang for related but incremental reasons. This has resulted in what was indivisible work that motivated PR5543's multithreading to be inlined into Carbon.
With that code inlined, we're now able to address the underlying desire - have a
clang::CodeGenerator attached to Clang's Sema throughout
Carbons checkphase - allowing Clang to lower as it does in the nativeclang`
compilation. This avoids the divergence without the multithreading complexity.
The main cost is the inherent difference between Clang's continuous lowering and Carbon's phase based lowering, though that seems to be an acceptable cost to avoid friction trying to otherwise wedge Clang into Carbon's phase based approach.