Skip to content

Commit a6788c3

Browse files
committed
[Clang] Add builtin templates to deduplicate and sort types in template arguments
The two new additions are: - `__builtin_dedup_types` allows to efficiently remove duplicate types from template arguments. - `__builtin_sort_types` allows to sort a list of types by their mangled names. They are multivalued templates that can only be used directly in the template argument lists and get immediately expanded as soon as results of the computation are available. It allows easily combining them, e.g.: ```cpp std::tuple< __builtin_sort_types< __builtin_dedup_types<int, double, T...>>> tuple_; ``` Compiler will warn when those builtin templates are used outside of template arguments: ```cpp // These are disallowed. __builtin_sort_types<int, int> var; template<class T = __builtin_sort_types<int, int>> struct Type {}; template<template<class...> T = __builtin_sort_types> struct Type {}; ``` Another helper `__builtin_expand_types` just returns the list of its arguments unchanged and is useful to simplify the implementation of other multivalued builtin templates as a holder for the results of that computation. We have observed that deduplication code is quite common in our internal codebase and this builtin allows to have significant savings (up to 25% of compile time on targets that already take 3 minutes to compile). The same builtin is also used widely enough in the codebase that we expect a savings from a long tail of uses, too, although it is hard to put an estimate on this number in advance.
1 parent 89e7f4d commit a6788c3

File tree

13 files changed

+382
-1
lines changed

13 files changed

+382
-1
lines changed

clang-tools-extra/clangd/unittests/FindTargetTests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,12 @@ TEST_F(TargetDeclTest, BuiltinTemplates) {
731731
using type_pack_element = [[__type_pack_element]]<N, Pack...>;
732732
)cpp";
733733
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
734+
735+
Code = R"cpp(
736+
template <template <class...> class Templ, class... Types>
737+
using dedup_types = [[__builtin_dedup_types]]<Templ, Types...>;
738+
)cpp";
739+
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
734740
}
735741

736742
TEST_F(TargetDeclTest, MemberOfTemplate) {

clang/include/clang/AST/DeclTemplate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,6 +1796,9 @@ class BuiltinTemplateDecl : public TemplateDecl {
17961796
BuiltinTemplateKind getBuiltinTemplateKind() const { return BTK; }
17971797
};
17981798

1799+
bool isMultivaluedBuiltinTemplate(TemplateDecl *D);
1800+
bool isMultivaluedBuiltinTemplateName(TemplateName N);
1801+
17991802
/// Provides information about an explicit instantiation of a variable or class
18001803
/// template.
18011804
struct ExplicitInstantiationInfo {

clang/include/clang/Basic/BuiltinTemplates.td

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,17 @@ def __builtin_common_type : BuiltinTemplate<
5050
Template<[Class<"TypeMember">], "HasTypeMember">,
5151
Class<"HasNoTypeMember">,
5252
Class<"Ts", /*is_variadic=*/1>]>;
53+
54+
55+
// template <class ...Args>
56+
def __builtin_expand_types : BuiltinTemplate<
57+
[Class<"Args", /*is_variadic=*/1>]>;
58+
59+
// template <class ...Args>
60+
def __builtin_dedup_types : BuiltinTemplate<
61+
[Class<"Args", /*is_variadic=*/1>]>;
62+
63+
// template <class ...Args>
64+
def __builtin_sort_types : BuiltinTemplate<
65+
[Class<"Args", /*is_variadic=*/1>]>;
66+

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3147,6 +3147,10 @@ def err_integer_sequence_integral_element_type : Error<
31473147
def err_type_pack_element_out_of_bounds : Error<
31483148
"a parameter pack may not be accessed at an out of bounds index">;
31493149

3150+
// Multivalued builtin templates, e.g. __builtin_dedup_types and __builtin_sort_types
3151+
def err_multivalued_builtin_template_outside_template_args : Error<
3152+
"%0 can only be used directly inside a template argument list and cannot have type qualifiers">;
3153+
31503154
// Objective-C++
31513155
def err_objc_decls_may_only_appear_in_global_scope : Error<
31523156
"Objective-C declarations may only appear in global scope">;

clang/lib/AST/DeclTemplate.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,20 @@ void *allocateDefaultArgStorageChain(const ASTContext &C) {
273273
return new (C) char[sizeof(void*) * 2];
274274
}
275275

276+
bool isMultivaluedBuiltinTemplate(TemplateDecl *D) {
277+
auto *BD = llvm::dyn_cast<BuiltinTemplateDecl>(D);
278+
return BD &&
279+
(BD->getBuiltinTemplateKind() == clang::BTK__builtin_sort_types ||
280+
BD->getBuiltinTemplateKind() == clang::BTK__builtin_expand_types ||
281+
BD->getBuiltinTemplateKind() == clang::BTK__builtin_dedup_types);
282+
}
283+
284+
bool isMultivaluedBuiltinTemplateName(TemplateName N) {
285+
if (N.getKind() == TemplateName::DeducedTemplate)
286+
return false;
287+
auto *T = N.getAsTemplateDecl();
288+
return T && isMultivaluedBuiltinTemplate(T);
289+
}
276290
} // namespace clang
277291

278292
//===----------------------------------------------------------------------===//

clang/lib/Parse/ParseTemplate.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@
1313
#include "clang/AST/ASTContext.h"
1414
#include "clang/AST/DeclTemplate.h"
1515
#include "clang/AST/ExprCXX.h"
16+
#include "clang/Basic/Builtins.h"
1617
#include "clang/Basic/DiagnosticParse.h"
18+
#include "clang/Basic/DiagnosticSema.h"
1719
#include "clang/Parse/Parser.h"
1820
#include "clang/Parse/RAIIObjectsForParser.h"
1921
#include "clang/Sema/DeclSpec.h"
2022
#include "clang/Sema/EnterExpressionEvaluationContext.h"
2123
#include "clang/Sema/ParsedTemplate.h"
2224
#include "clang/Sema/Scope.h"
2325
#include "clang/Sema/SemaDiagnostic.h"
26+
#include "llvm/Support/Casting.h"
2427
#include "llvm/Support/TimeProfiler.h"
2528
using namespace clang;
2629

@@ -1469,6 +1472,17 @@ ParsedTemplateArgument Parser::ParseTemplateTemplateArgument() {
14691472
}
14701473
}
14711474

1475+
// We do not allow to reference builtin templates that produce multiple
1476+
// values, they would not have a well-defined semantics outside template
1477+
// arguments.
1478+
if (auto *T = !Result.isInvalid()
1479+
? Result.getAsTemplate().get().getAsTemplateDecl()
1480+
: nullptr;
1481+
T && isMultivaluedBuiltinTemplate(T)) {
1482+
Actions.diagnoseMissingTemplateArguments(Result.getAsTemplate().get(),
1483+
Result.getLocation());
1484+
}
1485+
14721486
// If this is a pack expansion, build it as such.
14731487
if (EllipsisLoc.isValid() && !Result.isInvalid())
14741488
Result = Actions.ActOnPackExpansion(Result, EllipsisLoc);

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
#include "clang/AST/DynamicRecursiveASTVisitor.h"
1818
#include "clang/AST/Expr.h"
1919
#include "clang/AST/ExprCXX.h"
20+
#include "clang/AST/Mangle.h"
21+
#include "clang/AST/RecursiveASTVisitor.h"
22+
#include "clang/AST/TemplateBase.h"
2023
#include "clang/AST/TemplateName.h"
24+
#include "clang/AST/Type.h"
25+
#include "clang/AST/TypeOrdering.h"
2126
#include "clang/AST/TypeVisitor.h"
2227
#include "clang/Basic/Builtins.h"
2328
#include "clang/Basic/DiagnosticSema.h"
@@ -36,9 +41,16 @@
3641
#include "clang/Sema/SemaInternal.h"
3742
#include "clang/Sema/Template.h"
3843
#include "clang/Sema/TemplateDeduction.h"
44+
#include "llvm/ADT/ArrayRef.h"
45+
#include "llvm/ADT/BitVector.h"
46+
#include "llvm/ADT/DenseSet.h"
47+
#include "llvm/ADT/STLExtras.h"
3948
#include "llvm/ADT/SmallBitVector.h"
49+
#include "llvm/ADT/SmallString.h"
50+
#include "llvm/ADT/SmallVector.h"
4051
#include "llvm/ADT/StringExtras.h"
4152
#include "llvm/Support/SaveAndRestore.h"
53+
#include "llvm/Support/raw_ostream.h"
4254

4355
#include <optional>
4456
using namespace clang;
@@ -3320,6 +3332,78 @@ checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
33203332
}
33213333
return HasNoTypeMember;
33223334
}
3335+
case BTK__builtin_dedup_types: {
3336+
assert(Converted.size() == 1 && "__builtin_dedup_types should be given "
3337+
"a parameter pack");
3338+
TemplateArgument Ts = Converted[0];
3339+
// Delay the computation until we can compute the final result. We choose
3340+
// not to remove the duplicates upfront before substitution to keep the code
3341+
// simple.
3342+
if (Ts.isDependent())
3343+
return Context.getCanonicalTemplateSpecializationType(TemplateName(BTD),
3344+
Converted);
3345+
assert(Ts.getKind() == clang::TemplateArgument::Pack);
3346+
llvm::SmallVector<TemplateArgument> OutArgs;
3347+
llvm::SmallDenseSet<QualType> Seen;
3348+
// Synthesize a new template argument list, removing duplicates.
3349+
for (auto T : Ts.getPackAsArray()) {
3350+
assert(T.getKind() == clang::TemplateArgument::Type);
3351+
if (!Seen.insert(T.getAsType().getCanonicalType()).second)
3352+
continue;
3353+
OutArgs.push_back(T);
3354+
}
3355+
// Return __builtin_expand_types, it will handle the final expansion.
3356+
return Context.getCanonicalTemplateSpecializationType(
3357+
TemplateName(Context.get__builtin_expand_typesDecl()), OutArgs);
3358+
}
3359+
case BTK__builtin_sort_types: {
3360+
assert(Converted.size() == 1);
3361+
assert(Converted[0].getKind() == TemplateArgument::Pack);
3362+
// Delay if we have any dependencies, the mangled names may change after
3363+
// subsistution or may not be well-defined for dependent types.
3364+
if (Converted[0].isDependent())
3365+
return Context.getCanonicalTemplateSpecializationType(TemplateName(BTD),
3366+
Converted);
3367+
3368+
auto InputArgs = Converted[0].getPackAsArray();
3369+
std::unique_ptr<MangleContext> Mangler(
3370+
SemaRef.getASTContext().createMangleContext());
3371+
3372+
// Prepare our sort keys, i.e. the mangled names.
3373+
llvm::SmallVector<std::string> MangledNames(InputArgs.size());
3374+
for (unsigned I = 0; I < InputArgs.size(); ++I) {
3375+
llvm::raw_string_ostream OS(MangledNames[I]);
3376+
Mangler->mangleCanonicalTypeName(
3377+
InputArgs[I].getAsType().getCanonicalType(), OS);
3378+
}
3379+
3380+
// Sort array of indices into the InputArgs/MangledNames.
3381+
llvm::SmallVector<unsigned> Indexes(InputArgs.size());
3382+
for (unsigned I = 0; I < InputArgs.size(); ++I) {
3383+
Indexes[I] = I;
3384+
}
3385+
llvm::stable_sort(Indexes, [&](unsigned L, unsigned R) {
3386+
return MangledNames[L] < MangledNames[R];
3387+
});
3388+
3389+
llvm::SmallVector<TemplateArgument> SortedArguments;
3390+
SortedArguments.reserve(InputArgs.size());
3391+
for (unsigned I : Indexes)
3392+
SortedArguments.push_back(InputArgs[I]);
3393+
3394+
// Use __builtin_expand_types to indicate we now have the final results.
3395+
return Context.getCanonicalTemplateSpecializationType(
3396+
TemplateName(Context.get__builtin_expand_typesDecl()), SortedArguments);
3397+
}
3398+
case BTK__builtin_expand_types: {
3399+
assert(Converted.size() == 1);
3400+
assert(Converted[0].getKind() == TemplateArgument::Pack);
3401+
auto InputArgs = Converted[0].getPackAsArray();
3402+
// Just return he inputs as is, the code processing template argument lists
3403+
// recognizes our builtin and does the actual expansion.
3404+
return Context.getCanonicalTemplateSpecializationType(TemplateName(BTD),
3405+
InputArgs);
3406+
}
33233407
}
33243408
llvm_unreachable("unexpected BuiltinTemplateDecl!");
33253409
}
@@ -5501,6 +5585,70 @@ static bool diagnoseMissingArgument(Sema &S, SourceLocation Loc,
55015585
return true;
55025586
}
55035587

5588+
/// If there is a top-level __builtin_expand_types, it gets expanded and
5589+
/// replaced with its underlying arguments.
5590+
static void
5591+
TryExpandBuiltinTemplateArgumentWrapper(Sema &S,
5592+
TemplateArgumentListInfo &ArgList) {
5593+
auto &Context = S.getASTContext();
5594+
llvm::ArrayRef<TemplateArgumentLoc> Args = ArgList.arguments();
5595+
5596+
// These builtins are rare, so defer doing anything until we actually see one.
5597+
llvm::SmallVector<unsigned> ExpandableIndices;
5598+
for (unsigned I = 0; I < Args.size(); ++I) {
5599+
auto A = Args[I].getArgument();
5600+
if (A.getKind() != TemplateArgument::Type)
5601+
continue;
5602+
auto *TST = A.getAsType()
5603+
.getDesugaredType(Context)
5604+
->getAs<TemplateSpecializationType>();
5605+
if (!TST)
5606+
continue;
5607+
auto TName = TST->getTemplateName();
5608+
if (TName.getKind() == TemplateName::DeducedTemplate)
5609+
continue;
5610+
auto *T = dyn_cast_or_null<BuiltinTemplateDecl>(TName.getAsTemplateDecl());
5611+
if (!T || T->getBuiltinTemplateKind() != clang::BTK__builtin_expand_types)
5612+
continue;
5613+
ExpandableIndices.push_back(I);
5614+
}
5615+
if (ExpandableIndices.empty())
5616+
return;
5617+
5618+
// We know that some expansion needs to take place, so prepare to do it.
5619+
TemplateArgumentListInfo ExpandedArgList;
5620+
unsigned NextUnprocessedArg = 0;
5621+
auto CopyUpTo = [&](unsigned J) {
5622+
for (; NextUnprocessedArg < J; ++NextUnprocessedArg) {
5623+
ExpandedArgList.addArgument(ArgList[NextUnprocessedArg]);
5624+
}
5625+
};
5626+
5627+
// Do the actual expansion by looping over the indices we identified before.
5628+
for (unsigned NextExpandable : ExpandableIndices) {
5629+
CopyUpTo(NextExpandable);
5630+
// FIXME: attemp to carry around source location information.
5631+
auto ToExpand = Args[NextExpandable]
5632+
.getArgument()
5633+
.getAsType()
5634+
.getDesugaredType(Context)
5635+
->getAs<TemplateSpecializationType>()
5636+
->template_arguments();
5637+
for (TemplateArgument A : ToExpand) {
5638+
assert(A.getKind() == TemplateArgument::Type);
5639+
ExpandedArgList.addArgument(TemplateArgumentLoc(
5640+
A, Context.getTrivialTypeSourceInfo(A.getAsType())));
5641+
}
5642+
NextUnprocessedArg = NextExpandable + 1;
5643+
}
5644+
5645+
// Carry over any leftovers.
5646+
CopyUpTo(ArgList.size());
5647+
assert(NextUnprocessedArg == ArgList.size());
5648+
5649+
ArgList = std::move(ExpandedArgList);
5650+
}
5651+
55045652
/// Check that the given template argument list is well-formed
55055653
/// for specializing the given template.
55065654
bool Sema::CheckTemplateArgumentList(
@@ -5517,6 +5665,10 @@ bool Sema::CheckTemplateArgumentList(
55175665
// template.
55185666
TemplateArgumentListInfo NewArgs = TemplateArgs;
55195667

5668+
// Process any immediate pack expansions from builtin templates.
5669+
// E.g. from __builtin_sort_types.
5670+
TryExpandBuiltinTemplateArgumentWrapper(*this, NewArgs);
5671+
55205672
TemplateParameterList *Params = GetTemplateParameterList(Template);
55215673

55225674
SourceLocation RAngleLoc = NewArgs.getRAngleLoc();

clang/lib/Sema/SemaType.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4327,6 +4327,25 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
43274327
}
43284328
}
43294329

4330+
// Some builtin templates can be used in very limited ways, i.e. they must be
4331+
// used in template arguments and cannot have any qualifiers or form
4332+
// complicated types. Diagnose any misuses.
4333+
if (auto *TST = declSpecType.getDesugaredType(Context)
4334+
->getAs<TemplateSpecializationType>();
4335+
TST && isMultivaluedBuiltinTemplateName(TST->getTemplateName())) {
4336+
if (D.getNumTypeObjects() != 0 || D.getDeclSpec().getTypeQualifiers() ||
4337+
D.getContext() != DeclaratorContext::TemplateArg) {
4338+
// Use non-desugared type in diagnostics for better error messages.
4339+
S.Diag(D.getDeclSpec().getBeginLoc(),
4340+
diag::err_multivalued_builtin_template_outside_template_args)
4341+
<< declSpecType->getAs<TemplateSpecializationType>()
4342+
->getTemplateName();
4343+
// No need to change anything after reporting an error, we should be able
4344+
// to recover going forward by never actually expanding the builtin to its
4345+
// template arguments.
4346+
}
4347+
}
4348+
43304349
// Determine whether we should infer _Nonnull on pointer types.
43314350
std::optional<NullabilityKind> inferNullability;
43324351
bool inferNullabilityCS = false;

clang/test/Import/builtin-template/Inputs/S.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@ using TypePackElement = __type_pack_element<i, T...>;
1414

1515
template <int i>
1616
struct X;
17+
18+
19+
template <template <class...> class Templ, class...Types>
20+
using TypePackDedup = Templ<__builtin_dedup_types<Types...>>;
21+
22+
template <class ...Ts>
23+
struct TypeList {};

clang/test/Import/builtin-template/test.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DSEQ | FileCheck --check-prefix=CHECK-SEQ %s
22
// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DPACK | FileCheck --check-prefix=CHECK-PACK %s
3-
// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DPACK -Xcc -DSEQ | FileCheck --check-prefixes=CHECK-SEQ,CHECK-PACK %s
3+
// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DDEDUP | FileCheck --check-prefix=CHECK-DEDUP %s
4+
// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DPACK -Xcc -DSEQ -Xcc -DDEDUP | FileCheck --check-prefixes=CHECK-SEQ,CHECK-PACK,CHECK-DEDUP %s
45

56
// CHECK-SEQ: BuiltinTemplateDecl {{.+}} <<invalid sloc>> <invalid sloc> implicit __make_integer_seq{{$}}
67
// CHECK-PACK: BuiltinTemplateDecl {{.+}} <<invalid sloc>> <invalid sloc> implicit __type_pack_element{{$}}
8+
// CHECK-DEDUP: BuiltinTemplateDecl {{.+}} <<invalid sloc>> <invalid sloc> implicit __builtin_dedup_types{{$}}
79

810
void expr() {
911
#ifdef SEQ
@@ -20,4 +22,10 @@ void expr() {
2022
static_assert(__is_same(TypePackElement<0, X<0>, X<1>>, X<0>), "");
2123
static_assert(__is_same(TypePackElement<1, X<0>, X<1>>, X<1>), "");
2224
#endif
25+
26+
#ifdef DEDUP
27+
static_assert(__is_same(TypePackDedup<TypeList>, TypeList<>), "");
28+
static_assert(__is_same(TypePackDedup<TypeList, int, double, int>, TypeList<int, double>), "");
29+
static_assert(__is_same(TypePackDedup<TypeList, X<0>, X<1>, X<1>, X<2>, X<0>>, TypeList<X<0>, X<1>, X<2>>), "");
30+
#endif
2331
}

0 commit comments

Comments
 (0)