Skip to content

[PATCH 1/4] [clang] Improve nested name specifier AST representation #147835

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

mizvekov
Copy link
Contributor

@mizvekov mizvekov commented Jul 9, 2025

This is a major change on how we represent nested name qualifications in the AST.

  • The nested name specifier itself and how it's stored is changed. The prefixes for types are handled within the type hierarchy, which makes canonicalization for them super cheap, no memory allocation required. Also translating a type into nested name specifier form becomes a no-op. An identifier is stored as a DependentNameType. The nested name specifier gains a lightweight handle class, to be used instead of passing around pointers, which is similar to what is implemented for TemplateName. There is still one free bit available, and this handle can be used within a PointerUnion and PointerIntPair, which should keep bit-packing aficionados happy.
  • The ElaboratedType node is removed, all type nodes in which it could previously apply to can now store the elaborated keyword and name qualifier, tail allocating when present.
  • TagTypes can now point to the exact declaration found when producing these, as opposed to the previous situation of there only existing one TagType per entity. This increases the amount of type sugar retained, and can have several applications, for example in tracking module ownership, and other tools which care about source file origins, such as IWYU. These TagTypes are lazily allocated, in order to limit the increase in AST size.

This patch offers a great performance benefit.

It greatly improves compilation time for stdexec. For one datapoint, for test_on2.cpp in that project, which is the slowest compiling test, this patch improves -c compilation time by about 7.2%, with the -fsyntax-only improvement being at ~12%.

This has great results on compile-time-tracker as well:
image

This patch also further enables other optimziations in the future, and will reduce the performance impact of template specialization resugaring when that lands.

It has some other miscelaneous drive-by fixes.

About the review: Yes the patch is huge, sorry about that. Part of the reason is that I started by the nested name specifier part, before the ElaboratedType part, but that had a huge performance downside, as ElaboratedType is a big performance hog. I didn't have the steam to go back and change the patch after the fact.

There is also a lot of internal API changes, and it made sense to remove ElaboratedType in one go, versus removing it from one type at a time, as that would present much more churn to the users. Also, the nested name specifier having a different API avoids missing changes related to how prefixes work now, which could make existing code compile but not work.

How to review: The important changes are all in clang/include/clang/AST and clang/lib/AST, with also important changes in clang/lib/Sema/TreeTransform.h.

The rest and bulk of the changes are mostly consequences of the changes in API.

PS: TagType::getDecl is renamed to getOriginalDecl in this patch, just for easier to rebasing. I plan to rename it back after this lands.

This is split into four interdependent patches:

  1. This one
  2. [PATCH 2/4] [clang] Improve nested name specifier AST representation #148012
  3. [PATCH 3/4] [clang] Improve nested name specifier AST representation #148014
  4. [PATCH 4/4] [clang] Improve nested name specifier AST representation #148015

Fixes #136624
Fixes #147000
Fixes #43179
Fixes #68670
Fixes #92757

@cor3ntin cor3ntin requested a review from Sirraide July 10, 2025 06:36
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there ANYTHING we can do to split this up? This is so huge that github is refusing to load half the changes.

@mizvekov mizvekov force-pushed the users/mizvekov/name-qualification-refactor branch from 69fb353 to 4730e9a Compare July 10, 2025 17:12
@mizvekov
Copy link
Contributor Author

Is there ANYTHING we can do to split this up? This is so huge that github is refusing to load half the changes.

I broke the commit down into others, now the first commit only has the AST and TreeTransform changes.

@erichkeane
Copy link
Collaborator

Is there ANYTHING we can do to split this up? This is so huge that github is refusing to load half the changes.

I broke the commit down into others, now the first commit only has the AST and TreeTransform changes.

Can we split them up in reviews/do reviews as a stack if necessary? reviewing each individual 'commit' in the same review is also really difficult.

@mizvekov
Copy link
Contributor Author

Can we split them up in reviews/do reviews as a stack if necessary? reviewing each individual 'commit' in the same review is also really difficult.

Possible, although it would be difficult to split this in a way where each commit works individually.
If we accept that we will have to rely on the top of the stack being rebased in order to get testing feedback, and that this will have to be committed all in one go, I can do it.

@erichkeane
Copy link
Collaborator

Can we split them up in reviews/do reviews as a stack if necessary? reviewing each individual 'commit' in the same review is also really difficult.

Possible, although it would be difficult to split this in a way where each commit works individually. If we accept that we will have to rely on the top of the stack being rebased in order to get testing feedback, and that this will have to be committed all in one go, I can do it.

I'm definitely OK with accepting all of that, and would very much appreciate it. Note that Graphite is a tool others have used to manage patch-stacks that can do a lot of that for you, if it makes it easier.

@mizvekov mizvekov force-pushed the users/mizvekov/name-qualification-refactor branch from 4730e9a to 374aefc Compare July 10, 2025 17:50
@mizvekov mizvekov changed the title [clang] Improve nested name specifier AST representation [PATCH 1/4] [clang] Improve nested name specifier AST representation Jul 10, 2025
@mizvekov
Copy link
Contributor Author

I'm definitely OK with accepting all of that, and would very much appreciate it.

Done.

@rjmccall
Copy link
Contributor

rjmccall commented Jul 10, 2025

You should at minimum be able to layer the NestedNameSpecifier change on top of the TagType change. I can't see why that wouldn't work.

  1. Add the structure for sugary TagTypes.
  2. Replace ElaboratedType with sugary TagTypes.
  3. Change the representation of NNS.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For obvious reasons, I wasn't able to make even a reasonably thorough review here, but I came across a few things that seemed like good ideas. All 4 of the patches are STILL huge.

IMO, unless this can be more bite-sized changes, I don't know how to proceed here.

return QualType(PrevDecl->TypeForDecl, 0);
}
QualType getTypeDeclType(const TypeDecl *Decl) const;
QualType getTypeDeclType(const TagDecl *) const = delete;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do some sort of comments to explain why these are deleted, so that when someone runs into them, they can see what they should be using instead?


/// Microsoft's '__super' specifier, stored as a CXXRecordDecl* of
/// the class it appeared in.
Super,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"While we are here" :) It would be REALLY nice if instead of Super we spelled this as something more obvious what it is. Something likeMicrosoftSuper? Super hits too many other language-keyword-type things for me, and it isn't particularly clear when reading other stuff in this patch that it was what this is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this rename would be straight forward.

@@ -3413,7 +3417,10 @@ static void encodeTypeForFunctionPointerAuth(const ASTContext &Ctx,
// type, or an unsigned integer type.
//
// So we have to treat enum types as integers.
QualType UnderlyingType = cast<EnumType>(T)->getDecl()->getIntegerType();
QualType UnderlyingType = cast<EnumType>(T)
->getOriginalDecl()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has shown up a few times so far... could this use a helper of some sort (getOriginalDecl->getDefinitionOrSelf)?

@@ -326,7 +326,7 @@ Record *Program::getOrCreateRecord(const RecordDecl *RD) {
const auto *RT = Spec.getType()->getAs<RecordType>();
if (!RT)
return nullptr;
const RecordDecl *BD = RT->getDecl();
const RecordDecl *BD = RT->getOriginalDecl()->getDefinitionOrSelf();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty convinced by now, getOriginalDefinitionOrSelf would pay HUGE dividends in this patch :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the problem is that it would be really annoying to provide an inline definition of this helper, since it has to be defined with Type.h, but the definition depends on Decl bits which in turn depend on Type.h.

Not making them inline has a noticeable perf hit.

I had the same problem with providing inline implementations of a bunch of NestedNameSpecifier members.
What I did there, was to split this into NestedNameSpecifierBase.h and NestedNameSpecifier.h, the latter containing only inline definitions, and the former being included in places which would otherwise introduce a circular dependency.

I think making this change in Type.h for this patch would have been much larger though, so I'd propose doing this in a follow up.

Another possibility is providing these as inline free-function helpers, defined in Decl.h, but I think this looks uglier (even though we use this pattern elsewhere).

@mizvekov
Copy link
Contributor Author

You should at minimum be able to layer the NestedNameSpecifier change on top of the TagType change. I can't see why that wouldn't work.

  1. Add the structure for sugary TagTypes.
  2. Replace ElaboratedType with sugary TagTypes.
  3. Change the representation of NNS.

Well it's not just TagTypes which were changed in order to not need ElaboratedType, everything else too (TypedefTypes, TemplateSpecializationTypes, etc).

It would be really annoying to remove ElaboratedType from just TagType, leave it on everything else, while making every test pass, due for example how complicated some AST matchers would become in the intermediary state.

If you meant splitting the ElaboratedType removal from the NestedNameSpecifier representation change, that's easier to do, but still its a lot of work to make it in a way which every test passes.

If you would be happy to have dependent patches which must still be tested together, I can certainly split this patch (part 1) into NestedNameSpecifier changes and ElaboratedType changes, if that helps.

@mizvekov
Copy link
Contributor Author

For obvious reasons, I wasn't able to make even a reasonably thorough review here, but I came across a few things that seemed like good ideas. All 4 of the patches are STILL huge.

IMO, unless this can be more bite-sized changes, I don't know how to proceed here.

Yes this can be more bite-sized changes, as long as we keep the patches not individually testable.

I will follow the suggestion above.

@rjmccall
Copy link
Contributor

You should at minimum be able to layer the NestedNameSpecifier change on top of the TagType change. I can't see why that wouldn't work.

  1. Add the structure for sugary TagTypes.
  2. Replace ElaboratedType with sugary TagTypes.
  3. Change the representation of NNS.

Well it's not just TagTypes which were changed in order to not need ElaboratedType, everything else too (TypedefTypes, TemplateSpecializationTypes, etc).

It would be really annoying to remove ElaboratedType from just TagType, leave it on everything else, while making every test pass, due for example how complicated some AST matchers would become in the intermediary state.

I actually don't see why. AST matching becomes heinously complicated in the end state of your change if you don't have methods to ask questions like "was this written as an elaborated type" or whatever else the client code in Sema is looking for. So presumably you do have those predicates, and the end state uses them. You can certainly implement those predicates in terms of ElaboratedType while it still exists. That should be patch 1.

@mizvekov
Copy link
Contributor Author

I actually don't see why. AST matching becomes heinously complicated in the end state of your change if you don't have methods to ask questions like "was this written as an elaborated type" or whatever else the client code in Sema is looking for. So presumably you do have those predicates, and the end state uses them. You can certainly implement those predicates in terms of ElaboratedType while it still exists. That should be patch 1.

Yes, that's possible, maybe this is not as hard as I originally made it seem to be, but any kind of splitting this up in which every test has to pass in intermediary states would be a huge time sink to me in my current condition.

I made this patch while I was working 100% on clang, and then continued while I was unemployed while looking for a new thing. Now I have a new project with another employer, and it's not dedicated to clang upstream anymore, so I am not sure when I will have that kind of time again.

@mizvekov mizvekov force-pushed the users/mizvekov/name-qualification-refactor branch from 374aefc to 8da8c53 Compare July 10, 2025 20:14
@mizvekov mizvekov force-pushed the users/mizvekov/name-qualification-refactor branch from 8da8c53 to 7b74782 Compare July 11, 2025 00:05
@zyn0217
Copy link
Contributor

zyn0217 commented Jul 11, 2025

Given the scale of the patch, it's not surprising if it undergoes some revert-reapplies cycles after the initial commit.

To reduce the churn, would it be possible to ask google (and other major downstream clients) to test it internally before we merge? @AaronBallman

@AaronBallman
Copy link
Collaborator

Given the scale of the patch, it's not surprising if it undergoes some revert-reapplies cycles after the initial commit.

To reduce the churn, would it be possible to ask google (and other major downstream clients) to test it internally before we merge? @AaronBallman

We can certainly ask; it's in their best interests to avoid that amount of churn if there are problems. But I'm not certain we have a way to really do that aside from poke individuals and see whether they're willing to try it out internally or not.

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments, but I will say that I haven’t nearly reviewed all of this because it’s rather massive (and I’m also not too familiar w/ everything around NNSs).

Super,
};

inline Kind getKind() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason a lot of these declarations are inline? Because from what I know that doesn’t really do anything on declarations that aren’t definitions, does it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, an inline definition can't be provided here due to circular dependencies, but it is provided in NestedNameSpecifier.h. This is a way to get around the interdependence between Decl.h and Type.h.

This is a major change on how we represent nested name qualifications in the AST.

* The nested name specifier itself and how it's stored is changed. The prefixes for types are handled within the type hierarchy, which makes canonicalization for them super cheap, no memory allocation required. Also translating a type into nested name specifier form becomes a no-op. An identifier is stored as a DependentNameType. The nested name specifier gains a lightweight handle class, to be used instead of passing around pointers, which is similar to what is implemented for TemplateName. There is still one free bit available, and this handle can be used within a PointerUnion and PointerIntPair, which should keep bit-packing aficionados happy.
* The ElaboratedType node is removed, all type nodes in which it could previously apply to can now store the elaborated keyword and name qualifier, tail allocating when present.
* TagTypes can now point to the exact declaration found when producing these, as opposed to the previous situation of there only existing one TagType per entity. This increases the amount of type sugar retained, and can have several applications, for example in tracking module ownership, and other tools which care about source file origins, such as IWYU. These TagTypes are lazily allocated, in order to limit the increase in AST size.

This patch offers a great performance benefit.

It greatly improves compilation time for [stdexec](https://github.com/NVIDIA/stdexec). For one datapoint, for `test_on2.cpp` in that project, which is the slowest compiling test, this patch improves `-c` compilation time by about 7.2%, with the `-fsyntax-only` improvement being at ~12%.

This has great results on compile-time-tracker as well:
![image](https://github.com/user-attachments/assets/700dce98-2cab-4aa8-97d1-b038c0bee831)

This patch also further enables other optimziations in the future, and will reduce the performance impact of template specialization resugaring when that lands.

It has some other miscelaneous drive-by fixes.

About the review: Yes the patch is huge, sorry about that. Part of the reason is that I started by the nested name specifier part, before the ElaboratedType part, but that had a huge performance downside, as ElaboratedType is a big performance hog. I didn't have the steam to go back and change the patch after the fact.

There is also a lot of internal API changes, and it made sense to remove ElaboratedType in one go, versus removing it from one type at a time, as that would present much more churn to the users. Also, the nested name specifier having a different API avoids missing changes related to how prefixes work now, which could make existing code compile but not work.

How to review: The important changes are all in `clang/include/clang/AST` and `clang/lib/AST`, with also important changes in `clang/lib/Sema/TreeTransform.h`.

The rest and bulk of the changes are mostly consequences of the changes in API.

PS: TagType::getDecl is renamed to `getOriginalDecl` in this patch, just for easier to rebasing. I plan to rename it back after this lands.

Fixes #136624
Fixes #147000
@mizvekov mizvekov force-pushed the users/mizvekov/name-qualification-refactor branch from 7b74782 to cfe89c1 Compare July 12, 2025 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:AArch64 backend:AMDGPU backend:ARC backend:ARM backend:CSKY backend:Hexagon backend:Lanai backend:loongarch backend:MIPS backend:PowerPC backend:RISC-V backend:Sparc backend:SystemZ backend:WebAssembly backend:X86 clang:analysis clang:as-a-library libclang and C++ API clang:bytecode Issues for the clang bytecode constexpr interpreter clang:codegen IR generation bugs: mangling, exceptions, etc. clang:dataflow Clang Dataflow Analysis framework - https://clang.llvm.org/docs/DataFlowAnalysisIntro.html clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:openmp OpenMP related changes to Clang clang:static analyzer clang Clang issues not falling into any other category clang-tidy clang-tools-extra clangd coroutines C++20 coroutines debuginfo HLSL HLSL Language Support
Projects
None yet
8 participants