
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:  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 https://github.com/llvm/llvm-project/issues/43179 Fixes https://github.com/llvm/llvm-project/issues/68670 Fixes https://github.com/llvm/llvm-project/issues/92757
216 lines
7.4 KiB
C++
216 lines
7.4 KiB
C++
//===- ComparisonCategories.cpp - Three Way Comparison Data -----*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines the Comparison Category enum and data types, which
|
|
// store the types and expressions needed to support operator<=>
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/AST/ComparisonCategories.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include <optional>
|
|
|
|
using namespace clang;
|
|
|
|
std::optional<ComparisonCategoryType>
|
|
clang::getComparisonCategoryForBuiltinCmp(QualType T) {
|
|
using CCT = ComparisonCategoryType;
|
|
|
|
if (T->isIntegralOrEnumerationType())
|
|
return CCT::StrongOrdering;
|
|
|
|
if (T->isRealFloatingType())
|
|
return CCT::PartialOrdering;
|
|
|
|
// C++2a [expr.spaceship]p8: If the composite pointer type is an object
|
|
// pointer type, p <=> q is of type std::strong_ordering.
|
|
// Note: this assumes neither operand is a null pointer constant.
|
|
if (T->isObjectPointerType())
|
|
return CCT::StrongOrdering;
|
|
|
|
// TODO: Extend support for operator<=> to ObjC types.
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool ComparisonCategoryInfo::ValueInfo::hasValidIntValue() const {
|
|
assert(VD && "must have var decl");
|
|
if (!VD->isUsableInConstantExpressions(VD->getASTContext()))
|
|
return false;
|
|
|
|
// Before we attempt to get the value of the first field, ensure that we
|
|
// actually have one (and only one) field.
|
|
const auto *Record = VD->getType()->getAsCXXRecordDecl();
|
|
if (std::distance(Record->field_begin(), Record->field_end()) != 1 ||
|
|
!Record->field_begin()->getType()->isIntegralOrEnumerationType())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Attempt to determine the integer value used to represent the comparison
|
|
/// category result by evaluating the initializer for the specified VarDecl as
|
|
/// a constant expression and retrieving the value of the class's first
|
|
/// (and only) field.
|
|
///
|
|
/// Note: The STL types are expected to have the form:
|
|
/// struct X { T value; };
|
|
/// where T is an integral or enumeration type.
|
|
llvm::APSInt ComparisonCategoryInfo::ValueInfo::getIntValue() const {
|
|
assert(hasValidIntValue() && "must have a valid value");
|
|
return VD->evaluateValue()->getStructField(0).getInt();
|
|
}
|
|
|
|
ComparisonCategoryInfo::ValueInfo *ComparisonCategoryInfo::lookupValueInfo(
|
|
ComparisonCategoryResult ValueKind) const {
|
|
// Check if we already have a cache entry for this value.
|
|
auto It = llvm::find_if(
|
|
Objects, [&](ValueInfo const &Info) { return Info.Kind == ValueKind; });
|
|
if (It != Objects.end())
|
|
return &(*It);
|
|
|
|
// We don't have a cached result. Lookup the variable declaration and create
|
|
// a new entry representing it.
|
|
DeclContextLookupResult Lookup = Record->getCanonicalDecl()->lookup(
|
|
&Ctx.Idents.get(ComparisonCategories::getResultString(ValueKind)));
|
|
if (Lookup.empty() || !isa<VarDecl>(Lookup.front()))
|
|
return nullptr;
|
|
Objects.emplace_back(ValueKind, cast<VarDecl>(Lookup.front()));
|
|
return &Objects.back();
|
|
}
|
|
|
|
static const NamespaceDecl *lookupStdNamespace(const ASTContext &Ctx,
|
|
NamespaceDecl *&StdNS) {
|
|
if (!StdNS) {
|
|
DeclContextLookupResult Lookup =
|
|
Ctx.getTranslationUnitDecl()->lookup(&Ctx.Idents.get("std"));
|
|
if (!Lookup.empty())
|
|
StdNS = dyn_cast<NamespaceDecl>(Lookup.front());
|
|
}
|
|
return StdNS;
|
|
}
|
|
|
|
static const CXXRecordDecl *lookupCXXRecordDecl(const ASTContext &Ctx,
|
|
const NamespaceDecl *StdNS,
|
|
ComparisonCategoryType Kind) {
|
|
StringRef Name = ComparisonCategories::getCategoryString(Kind);
|
|
DeclContextLookupResult Lookup = StdNS->lookup(&Ctx.Idents.get(Name));
|
|
if (!Lookup.empty())
|
|
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(Lookup.front()))
|
|
return RD;
|
|
return nullptr;
|
|
}
|
|
|
|
const ComparisonCategoryInfo *
|
|
ComparisonCategories::lookupInfo(ComparisonCategoryType Kind) const {
|
|
auto It = Data.find(static_cast<char>(Kind));
|
|
if (It != Data.end())
|
|
return &It->second;
|
|
|
|
if (const NamespaceDecl *NS = lookupStdNamespace(Ctx, StdNS))
|
|
if (const CXXRecordDecl *RD = lookupCXXRecordDecl(Ctx, NS, Kind))
|
|
return &Data.try_emplace((char)Kind, Ctx, RD, Kind).first->second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const ComparisonCategoryInfo *
|
|
ComparisonCategories::lookupInfoForType(QualType Ty) const {
|
|
assert(!Ty.isNull() && "type must be non-null");
|
|
using CCT = ComparisonCategoryType;
|
|
const auto *RD = Ty->getAsCXXRecordDecl();
|
|
if (!RD)
|
|
return nullptr;
|
|
|
|
// Check to see if we have information for the specified type cached.
|
|
const auto *CanonRD = RD->getCanonicalDecl();
|
|
for (const auto &KV : Data) {
|
|
const ComparisonCategoryInfo &Info = KV.second;
|
|
if (CanonRD == Info.Record->getCanonicalDecl())
|
|
return &Info;
|
|
}
|
|
|
|
if (!RD->getEnclosingNamespaceContext()->isStdNamespace())
|
|
return nullptr;
|
|
|
|
// If not, check to see if the decl names a type in namespace std with a name
|
|
// matching one of the comparison category types.
|
|
for (unsigned I = static_cast<unsigned>(CCT::First),
|
|
End = static_cast<unsigned>(CCT::Last);
|
|
I <= End; ++I) {
|
|
CCT Kind = static_cast<CCT>(I);
|
|
|
|
// We've found the comparison category type. Build a new cache entry for
|
|
// it.
|
|
if (getCategoryString(Kind) == RD->getName())
|
|
return &Data.try_emplace((char)Kind, Ctx, RD, Kind).first->second;
|
|
}
|
|
|
|
// We've found nothing. This isn't a comparison category type.
|
|
return nullptr;
|
|
}
|
|
|
|
const ComparisonCategoryInfo &ComparisonCategories::getInfoForType(QualType Ty) const {
|
|
const ComparisonCategoryInfo *Info = lookupInfoForType(Ty);
|
|
assert(Info && "info for comparison category not found");
|
|
return *Info;
|
|
}
|
|
|
|
QualType ComparisonCategoryInfo::getType() const {
|
|
assert(Record);
|
|
return Record->getASTContext().getCanonicalTagType(Record);
|
|
}
|
|
|
|
StringRef ComparisonCategories::getCategoryString(ComparisonCategoryType Kind) {
|
|
using CCKT = ComparisonCategoryType;
|
|
switch (Kind) {
|
|
case CCKT::PartialOrdering:
|
|
return "partial_ordering";
|
|
case CCKT::WeakOrdering:
|
|
return "weak_ordering";
|
|
case CCKT::StrongOrdering:
|
|
return "strong_ordering";
|
|
}
|
|
llvm_unreachable("unhandled cases in switch");
|
|
}
|
|
|
|
StringRef ComparisonCategories::getResultString(ComparisonCategoryResult Kind) {
|
|
using CCVT = ComparisonCategoryResult;
|
|
switch (Kind) {
|
|
case CCVT::Equal:
|
|
return "equal";
|
|
case CCVT::Equivalent:
|
|
return "equivalent";
|
|
case CCVT::Less:
|
|
return "less";
|
|
case CCVT::Greater:
|
|
return "greater";
|
|
case CCVT::Unordered:
|
|
return "unordered";
|
|
}
|
|
llvm_unreachable("unhandled case in switch");
|
|
}
|
|
|
|
std::vector<ComparisonCategoryResult>
|
|
ComparisonCategories::getPossibleResultsForType(ComparisonCategoryType Type) {
|
|
using CCT = ComparisonCategoryType;
|
|
using CCR = ComparisonCategoryResult;
|
|
std::vector<CCR> Values;
|
|
Values.reserve(4);
|
|
bool IsStrong = Type == CCT::StrongOrdering;
|
|
Values.push_back(IsStrong ? CCR::Equal : CCR::Equivalent);
|
|
Values.push_back(CCR::Less);
|
|
Values.push_back(CCR::Greater);
|
|
if (Type == CCT::PartialOrdering)
|
|
Values.push_back(CCR::Unordered);
|
|
return Values;
|
|
}
|