[clang] [clang-tools-extra] [Clang] Add __builtin_type_pack_dedup template to deduplicate types in template arguments (PR #106730)
Ilya Biryukov via cfe-commits
cfe-commits at lists.llvm.org
Fri Feb 28 06:14:59 PST 2025
https://github.com/ilya-biryukov updated https://github.com/llvm/llvm-project/pull/106730
>From a6788c3ba0b00c7b935cbb764c25e12f30d2fe80 Mon Sep 17 00:00:00 2001
From: Ilya Biryukov <ibiryukov at google.com>
Date: Fri, 23 Aug 2024 17:27:26 +0200
Subject: [PATCH] [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.
---
.../clangd/unittests/FindTargetTests.cpp | 6 +
clang/include/clang/AST/DeclTemplate.h | 3 +
clang/include/clang/Basic/BuiltinTemplates.td | 14 ++
.../clang/Basic/DiagnosticSemaKinds.td | 4 +
clang/lib/AST/DeclTemplate.cpp | 14 ++
clang/lib/Parse/ParseTemplate.cpp | 14 ++
clang/lib/Sema/SemaTemplate.cpp | 152 ++++++++++++++++++
clang/lib/Sema/SemaType.cpp | 19 +++
.../test/Import/builtin-template/Inputs/S.cpp | 7 +
clang/test/Import/builtin-template/test.cpp | 10 +-
clang/test/PCH/dedup_types.cpp | 20 +++
.../SemaTemplate/dedup-types-builting.cpp.cpp | 73 +++++++++
.../test/SemaTemplate/sort-types-builtin.cpp | 47 ++++++
13 files changed, 382 insertions(+), 1 deletion(-)
create mode 100644 clang/test/PCH/dedup_types.cpp
create mode 100644 clang/test/SemaTemplate/dedup-types-builting.cpp.cpp
create mode 100644 clang/test/SemaTemplate/sort-types-builtin.cpp
diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
index fc54f89f4941e..d919cda1f5241 100644
--- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
@@ -731,6 +731,12 @@ TEST_F(TargetDeclTest, BuiltinTemplates) {
using type_pack_element = [[__type_pack_element]]<N, Pack...>;
)cpp";
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
+
+ Code = R"cpp(
+ template <template <class...> class Templ, class... Types>
+ using dedup_types = [[__builtin_dedup_types]]<Templ, Types...>;
+ )cpp";
+ EXPECT_DECLS("TemplateSpecializationTypeLoc", );
}
TEST_F(TargetDeclTest, MemberOfTemplate) {
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index b82f75dd63fa5..4f25d53594f28 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -1796,6 +1796,9 @@ class BuiltinTemplateDecl : public TemplateDecl {
BuiltinTemplateKind getBuiltinTemplateKind() const { return BTK; }
};
+bool isMultivaluedBuiltinTemplate(TemplateDecl *D);
+bool isMultivaluedBuiltinTemplateName(TemplateName N);
+
/// Provides information about an explicit instantiation of a variable or class
/// template.
struct ExplicitInstantiationInfo {
diff --git a/clang/include/clang/Basic/BuiltinTemplates.td b/clang/include/clang/Basic/BuiltinTemplates.td
index d46ce063d2f7e..deb47fa9ca03d 100644
--- a/clang/include/clang/Basic/BuiltinTemplates.td
+++ b/clang/include/clang/Basic/BuiltinTemplates.td
@@ -50,3 +50,17 @@ def __builtin_common_type : BuiltinTemplate<
Template<[Class<"TypeMember">], "HasTypeMember">,
Class<"HasNoTypeMember">,
Class<"Ts", /*is_variadic=*/1>]>;
+
+
+// template <class ...Args>
+def __builtin_expand_types : BuiltinTemplate<
+ [Class<"Args", /*is_variadic=*/1>]>;
+
+// template <class ...Args>
+def __builtin_dedup_types : BuiltinTemplate<
+ [Class<"Args", /*is_variadic=*/1>]>;
+
+// template <class ...Args>
+def __builtin_sort_types : BuiltinTemplate<
+ [Class<"Args", /*is_variadic=*/1>]>;
+
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d094c075ecee2..578c54611bcd8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3147,6 +3147,10 @@ def err_integer_sequence_integral_element_type : Error<
def err_type_pack_element_out_of_bounds : Error<
"a parameter pack may not be accessed at an out of bounds index">;
+// Multivalued builtin templates, e.g. __builtin_dedup_types and __builtin_sort_types
+def err_multivalued_builtin_template_outside_template_args : Error<
+ "%0 can only be used directly inside a template argument list and cannot have type qualifiers">;
+
// Objective-C++
def err_objc_decls_may_only_appear_in_global_scope : Error<
"Objective-C declarations may only appear in global scope">;
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index b8fe19c69dc29..381ca8181582d 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -273,6 +273,20 @@ void *allocateDefaultArgStorageChain(const ASTContext &C) {
return new (C) char[sizeof(void*) * 2];
}
+bool isMultivaluedBuiltinTemplate(TemplateDecl *D) {
+ auto *BD = llvm::dyn_cast<BuiltinTemplateDecl>(D);
+ return BD &&
+ (BD->getBuiltinTemplateKind() == clang::BTK__builtin_sort_types ||
+ BD->getBuiltinTemplateKind() == clang::BTK__builtin_expand_types ||
+ BD->getBuiltinTemplateKind() == clang::BTK__builtin_dedup_types);
+}
+
+bool isMultivaluedBuiltinTemplateName(TemplateName N) {
+ if (N.getKind() == TemplateName::DeducedTemplate)
+ return false;
+ auto *T = N.getAsTemplateDecl();
+ return T && isMultivaluedBuiltinTemplate(T);
+}
} // namespace clang
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp
index b2cb3d71e09f9..7aa18943f1227 100644
--- a/clang/lib/Parse/ParseTemplate.cpp
+++ b/clang/lib/Parse/ParseTemplate.cpp
@@ -13,7 +13,9 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/Basic/Builtins.h"
#include "clang/Basic/DiagnosticParse.h"
+#include "clang/Basic/DiagnosticSema.h"
#include "clang/Parse/Parser.h"
#include "clang/Parse/RAIIObjectsForParser.h"
#include "clang/Sema/DeclSpec.h"
@@ -21,6 +23,7 @@
#include "clang/Sema/ParsedTemplate.h"
#include "clang/Sema/Scope.h"
#include "clang/Sema/SemaDiagnostic.h"
+#include "llvm/Support/Casting.h"
#include "llvm/Support/TimeProfiler.h"
using namespace clang;
@@ -1469,6 +1472,17 @@ ParsedTemplateArgument Parser::ParseTemplateTemplateArgument() {
}
}
+ // We do not allow to reference builtin templates that produce multiple
+ // values, they would not have a well-defined semantics outside template
+ // arguments.
+ if (auto *T = !Result.isInvalid()
+ ? Result.getAsTemplate().get().getAsTemplateDecl()
+ : nullptr;
+ T && isMultivaluedBuiltinTemplate(T)) {
+ Actions.diagnoseMissingTemplateArguments(Result.getAsTemplate().get(),
+ Result.getLocation());
+ }
+
// If this is a pack expansion, build it as such.
if (EllipsisLoc.isValid() && !Result.isInvalid())
Result = Actions.ActOnPackExpansion(Result, EllipsisLoc);
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 38fa3ff3ab5b4..2b6036ff94bad 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -17,7 +17,12 @@
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Mangle.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/TemplateBase.h"
#include "clang/AST/TemplateName.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/TypeOrdering.h"
#include "clang/AST/TypeVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/DiagnosticSema.h"
@@ -36,9 +41,16 @@
#include "clang/Sema/SemaInternal.h"
#include "clang/Sema/Template.h"
#include "clang/Sema/TemplateDeduction.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/SaveAndRestore.h"
+#include "llvm/Support/raw_ostream.h"
#include <optional>
using namespace clang;
@@ -3320,6 +3332,78 @@ checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
}
return HasNoTypeMember;
}
+ case BTK__builtin_dedup_types: {
+ assert(Converted.size() == 1 && "__builtin_dedup_types should be given "
+ "a parameter pack");
+ TemplateArgument Ts = Converted[0];
+ // Delay the computation until we can compute the final result. We choose
+ // not to remove the duplicates upfront before substitution to keep the code
+ // simple.
+ if (Ts.isDependent())
+ return Context.getCanonicalTemplateSpecializationType(TemplateName(BTD),
+ Converted);
+ assert(Ts.getKind() == clang::TemplateArgument::Pack);
+ llvm::SmallVector<TemplateArgument> OutArgs;
+ llvm::SmallDenseSet<QualType> Seen;
+ // Synthesize a new template argument list, removing duplicates.
+ for (auto T : Ts.getPackAsArray()) {
+ assert(T.getKind() == clang::TemplateArgument::Type);
+ if (!Seen.insert(T.getAsType().getCanonicalType()).second)
+ continue;
+ OutArgs.push_back(T);
+ }
+ // Return __builtin_expand_types, it will handle the final expansion.
+ return Context.getCanonicalTemplateSpecializationType(
+ TemplateName(Context.get__builtin_expand_typesDecl()), OutArgs);
+ }
+ case BTK__builtin_sort_types: {
+ assert(Converted.size() == 1);
+ assert(Converted[0].getKind() == TemplateArgument::Pack);
+ // Delay if we have any dependencies, the mangled names may change after
+ // subsistution or may not be well-defined for dependent types.
+ if (Converted[0].isDependent())
+ return Context.getCanonicalTemplateSpecializationType(TemplateName(BTD),
+ Converted);
+
+ auto InputArgs = Converted[0].getPackAsArray();
+ std::unique_ptr<MangleContext> Mangler(
+ SemaRef.getASTContext().createMangleContext());
+
+ // Prepare our sort keys, i.e. the mangled names.
+ llvm::SmallVector<std::string> MangledNames(InputArgs.size());
+ for (unsigned I = 0; I < InputArgs.size(); ++I) {
+ llvm::raw_string_ostream OS(MangledNames[I]);
+ Mangler->mangleCanonicalTypeName(
+ InputArgs[I].getAsType().getCanonicalType(), OS);
+ }
+
+ // Sort array of indices into the InputArgs/MangledNames.
+ llvm::SmallVector<unsigned> Indexes(InputArgs.size());
+ for (unsigned I = 0; I < InputArgs.size(); ++I) {
+ Indexes[I] = I;
+ }
+ llvm::stable_sort(Indexes, [&](unsigned L, unsigned R) {
+ return MangledNames[L] < MangledNames[R];
+ });
+
+ llvm::SmallVector<TemplateArgument> SortedArguments;
+ SortedArguments.reserve(InputArgs.size());
+ for (unsigned I : Indexes)
+ SortedArguments.push_back(InputArgs[I]);
+
+ // Use __builtin_expand_types to indicate we now have the final results.
+ return Context.getCanonicalTemplateSpecializationType(
+ TemplateName(Context.get__builtin_expand_typesDecl()), SortedArguments);
+ }
+ case BTK__builtin_expand_types: {
+ assert(Converted.size() == 1);
+ assert(Converted[0].getKind() == TemplateArgument::Pack);
+ auto InputArgs = Converted[0].getPackAsArray();
+ // Just return he inputs as is, the code processing template argument lists
+ // recognizes our builtin and does the actual expansion.
+ return Context.getCanonicalTemplateSpecializationType(TemplateName(BTD),
+ InputArgs);
+ }
}
llvm_unreachable("unexpected BuiltinTemplateDecl!");
}
@@ -5501,6 +5585,70 @@ static bool diagnoseMissingArgument(Sema &S, SourceLocation Loc,
return true;
}
+/// If there is a top-level __builtin_expand_types, it gets expanded and
+/// replaced with its underlying arguments.
+static void
+TryExpandBuiltinTemplateArgumentWrapper(Sema &S,
+ TemplateArgumentListInfo &ArgList) {
+ auto &Context = S.getASTContext();
+ llvm::ArrayRef<TemplateArgumentLoc> Args = ArgList.arguments();
+
+ // These builtins are rare, so defer doing anything until we actually see one.
+ llvm::SmallVector<unsigned> ExpandableIndices;
+ for (unsigned I = 0; I < Args.size(); ++I) {
+ auto A = Args[I].getArgument();
+ if (A.getKind() != TemplateArgument::Type)
+ continue;
+ auto *TST = A.getAsType()
+ .getDesugaredType(Context)
+ ->getAs<TemplateSpecializationType>();
+ if (!TST)
+ continue;
+ auto TName = TST->getTemplateName();
+ if (TName.getKind() == TemplateName::DeducedTemplate)
+ continue;
+ auto *T = dyn_cast_or_null<BuiltinTemplateDecl>(TName.getAsTemplateDecl());
+ if (!T || T->getBuiltinTemplateKind() != clang::BTK__builtin_expand_types)
+ continue;
+ ExpandableIndices.push_back(I);
+ }
+ if (ExpandableIndices.empty())
+ return;
+
+ // We know that some expansion needs to take place, so prepare to do it.
+ TemplateArgumentListInfo ExpandedArgList;
+ unsigned NextUnprocessedArg = 0;
+ auto CopyUpTo = [&](unsigned J) {
+ for (; NextUnprocessedArg < J; ++NextUnprocessedArg) {
+ ExpandedArgList.addArgument(ArgList[NextUnprocessedArg]);
+ }
+ };
+
+ // Do the actual expansion by looping over the indices we identified before.
+ for (unsigned NextExpandable : ExpandableIndices) {
+ CopyUpTo(NextExpandable);
+ // FIXME: attemp to carry around source location information.
+ auto ToExpand = Args[NextExpandable]
+ .getArgument()
+ .getAsType()
+ .getDesugaredType(Context)
+ ->getAs<TemplateSpecializationType>()
+ ->template_arguments();
+ for (TemplateArgument A : ToExpand) {
+ assert(A.getKind() == TemplateArgument::Type);
+ ExpandedArgList.addArgument(TemplateArgumentLoc(
+ A, Context.getTrivialTypeSourceInfo(A.getAsType())));
+ }
+ NextUnprocessedArg = NextExpandable + 1;
+ }
+
+ // Carry over any leftovers.
+ CopyUpTo(ArgList.size());
+ assert(NextUnprocessedArg == ArgList.size());
+
+ ArgList = std::move(ExpandedArgList);
+}
+
/// Check that the given template argument list is well-formed
/// for specializing the given template.
bool Sema::CheckTemplateArgumentList(
@@ -5517,6 +5665,10 @@ bool Sema::CheckTemplateArgumentList(
// template.
TemplateArgumentListInfo NewArgs = TemplateArgs;
+ // Process any immediate pack expansions from builtin templates.
+ // E.g. from __builtin_sort_types.
+ TryExpandBuiltinTemplateArgumentWrapper(*this, NewArgs);
+
TemplateParameterList *Params = GetTemplateParameterList(Template);
SourceLocation RAngleLoc = NewArgs.getRAngleLoc();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 60096eebfdb6f..627d79da1cc33 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -4327,6 +4327,25 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
}
}
+ // Some builtin templates can be used in very limited ways, i.e. they must be
+ // used in template arguments and cannot have any qualifiers or form
+ // complicated types. Diagnose any misuses.
+ if (auto *TST = declSpecType.getDesugaredType(Context)
+ ->getAs<TemplateSpecializationType>();
+ TST && isMultivaluedBuiltinTemplateName(TST->getTemplateName())) {
+ if (D.getNumTypeObjects() != 0 || D.getDeclSpec().getTypeQualifiers() ||
+ D.getContext() != DeclaratorContext::TemplateArg) {
+ // Use non-desugared type in diagnostics for better error messages.
+ S.Diag(D.getDeclSpec().getBeginLoc(),
+ diag::err_multivalued_builtin_template_outside_template_args)
+ << declSpecType->getAs<TemplateSpecializationType>()
+ ->getTemplateName();
+ // No need to change anything after reporting an error, we should be able
+ // to recover going forward by never actually expanding the builtin to its
+ // template arguments.
+ }
+ }
+
// Determine whether we should infer _Nonnull on pointer types.
std::optional<NullabilityKind> inferNullability;
bool inferNullabilityCS = false;
diff --git a/clang/test/Import/builtin-template/Inputs/S.cpp b/clang/test/Import/builtin-template/Inputs/S.cpp
index d5c9a9ae0309d..cb62067c9d798 100644
--- a/clang/test/Import/builtin-template/Inputs/S.cpp
+++ b/clang/test/Import/builtin-template/Inputs/S.cpp
@@ -14,3 +14,10 @@ using TypePackElement = __type_pack_element<i, T...>;
template <int i>
struct X;
+
+
+template <template <class...> class Templ, class...Types>
+using TypePackDedup = Templ<__builtin_dedup_types<Types...>>;
+
+template <class ...Ts>
+struct TypeList {};
diff --git a/clang/test/Import/builtin-template/test.cpp b/clang/test/Import/builtin-template/test.cpp
index 590efad0c71dc..c03b388ed486b 100644
--- a/clang/test/Import/builtin-template/test.cpp
+++ b/clang/test/Import/builtin-template/test.cpp
@@ -1,9 +1,11 @@
// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DSEQ | FileCheck --check-prefix=CHECK-SEQ %s
// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DPACK | FileCheck --check-prefix=CHECK-PACK %s
-// 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
+// RUN: clang-import-test -dump-ast -import %S/Inputs/S.cpp -expression %s -Xcc -DDEDUP | FileCheck --check-prefix=CHECK-DEDUP %s
+// 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
// CHECK-SEQ: BuiltinTemplateDecl {{.+}} <<invalid sloc>> <invalid sloc> implicit __make_integer_seq{{$}}
// CHECK-PACK: BuiltinTemplateDecl {{.+}} <<invalid sloc>> <invalid sloc> implicit __type_pack_element{{$}}
+// CHECK-DEDUP: BuiltinTemplateDecl {{.+}} <<invalid sloc>> <invalid sloc> implicit __builtin_dedup_types{{$}}
void expr() {
#ifdef SEQ
@@ -20,4 +22,10 @@ void expr() {
static_assert(__is_same(TypePackElement<0, X<0>, X<1>>, X<0>), "");
static_assert(__is_same(TypePackElement<1, X<0>, X<1>>, X<1>), "");
#endif
+
+#ifdef DEDUP
+ static_assert(__is_same(TypePackDedup<TypeList>, TypeList<>), "");
+ static_assert(__is_same(TypePackDedup<TypeList, int, double, int>, TypeList<int, double>), "");
+ static_assert(__is_same(TypePackDedup<TypeList, X<0>, X<1>, X<1>, X<2>, X<0>>, TypeList<X<0>, X<1>, X<2>>), "");
+#endif
}
diff --git a/clang/test/PCH/dedup_types.cpp b/clang/test/PCH/dedup_types.cpp
new file mode 100644
index 0000000000000..c0527a07194c0
--- /dev/null
+++ b/clang/test/PCH/dedup_types.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -std=c++14 -x c++-header %s -emit-pch -o %t.pch
+// RUN: %clang_cc1 -std=c++14 -x c++ /dev/null -include-pch %t.pch
+
+// RUN: %clang_cc1 -std=c++14 -x c++-header %s -emit-pch -fpch-instantiate-templates -o %t.pch
+// RUN: %clang_cc1 -std=c++14 -x c++ /dev/null -include-pch %t.pch
+
+template <template <class...> class Templ, class...Types>
+using TypePackDedup = Templ<__builtin_dedup_types<Types...>>;
+
+template <class ...Ts>
+struct TypeList {};
+
+template <int i>
+struct X {};
+
+void fn1() {
+ TypeList<int, double> l1 = TypePackDedup<TypeList, int, double, int>{};
+ TypeList<> l2 = TypePackDedup<TypeList>{};
+ TypeList<X<0>, X<1>> x1 = TypePackDedup<TypeList, X<0>, X<1>, X<0>, X<1>>{};
+}
diff --git a/clang/test/SemaTemplate/dedup-types-builting.cpp.cpp b/clang/test/SemaTemplate/dedup-types-builting.cpp.cpp
new file mode 100644
index 0000000000000..0223219b60189
--- /dev/null
+++ b/clang/test/SemaTemplate/dedup-types-builting.cpp.cpp
@@ -0,0 +1,73 @@
+// RUN: %clang_cc1 %s -verify
+
+template <typename...> struct TypeList;
+
+static_assert(__is_same(
+ TypeList<__builtin_dedup_types<int, int*, int, double, float>>,
+ TypeList<int, int*, double, float>));
+
+template <template<typename ...> typename Templ, typename ...Types>
+struct Dependent {
+ using empty_list = Templ<__builtin_dedup_types<>>;
+ using same = Templ<__builtin_dedup_types<Types...>>;
+ using twice = Templ<__builtin_dedup_types<Types..., Types...>>;
+ using dep_only_types = TypeList<__builtin_dedup_types<Types...>>;
+ using dep_only_template = Templ<__builtin_dedup_types<int, double, int>>;
+};
+
+static_assert(__is_same(Dependent<TypeList>::empty_list, TypeList<>));
+static_assert(__is_same(Dependent<TypeList>::same, TypeList<>));
+static_assert(__is_same(Dependent<TypeList>::twice, TypeList<>));
+static_assert(__is_same(Dependent<TypeList>::dep_only_types, TypeList<>));
+static_assert(__is_same(Dependent<TypeList>::dep_only_template, TypeList<int, double>));
+
+static_assert(__is_same(Dependent<TypeList, int*, double*, int*>::empty_list, TypeList<>));
+static_assert(__is_same(Dependent<TypeList, int*, double*, int*>::same, TypeList<int*, double*>));
+static_assert(__is_same(Dependent<TypeList, int*, double*, int*>::twice, TypeList<int*, double*>));
+static_assert(__is_same(Dependent<TypeList, int*, double*, int*>::dep_only_types, TypeList<int*, double*>));
+static_assert(__is_same(Dependent<TypeList, int*, double*, int*>::dep_only_template, TypeList<int, double>));
+
+
+template <class ...T>
+using Twice = TypeList<T..., T...>;
+
+static_assert(__is_same(Twice<__builtin_dedup_types<int, double, int>>, TypeList<int, double, int, double>));
+
+
+template <int...> struct IntList;
+// Wrong kinds of template arguments.
+// FIXME: make the error message below point at this file.
+IntList<__builtin_dedup_types<int>>* wrong_template; // expected-error@* {{template argument for non-type template parameter must be an expression}}
+ // expected-note at -4 {{template parameter is declared here}}
+TypeList<__builtin_dedup_types<1, 2, 3>>* wrong_template_args; // expected-error {{template argument for template type parameter must be a type}}
+ // expected-note@* {{template parameter from hidden source}}
+__builtin_dedup_types<> not_enough_args; // expected-error {{'__builtin_dedup_types' can only be used directly inside a template argument list}}
+ // expected-note@* {{template declaration from hidden source}}
+__builtin_dedup_types missing_template_args; // expected-error {{use of template '__builtin_dedup_types' requires template arguments}}
+
+// Make sure various canonical / non-canonical type representations do not affect results
+// of the deduplication and the qualifiers do end up creating different types when C++ requires it.
+using Int = int;
+using CInt = const Int;
+using IntArray = Int[10];
+using CIntArray = Int[10];
+using IntPtr = int*;
+using CIntPtr = const int*;
+static_assert(
+ __is_same(
+ TypeList<__builtin_dedup_types<
+ Int, int,
+ const int, const Int, CInt, const CInt,
+ IntArray, Int[10], int[10],
+ const IntArray, const int[10], CIntArray, const CIntArray,
+ IntPtr, int*,
+ const IntPtr, int* const,
+ CIntPtr, const int*,
+ const IntPtr*, int*const*,
+ CIntPtr*, const int**,
+ const CIntPtr*, const int* const*
+ >>,
+ TypeList<int, const int, int[10], const int [10], int*, int* const, const int*, int*const *, const int**, const int*const*>),
+ "");
+
+// FIXME: tests for locations of template arguments, ideally we should point into the original locations of the template arguments.
diff --git a/clang/test/SemaTemplate/sort-types-builtin.cpp b/clang/test/SemaTemplate/sort-types-builtin.cpp
new file mode 100644
index 0000000000000..459654a4b8608
--- /dev/null
+++ b/clang/test/SemaTemplate/sort-types-builtin.cpp
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+template <class ...>
+struct Types;
+
+// Note that we check for both conditions to ensure our expressions do not accidentally end up being dependent.
+static_assert(__is_same(Types<__builtin_sort_types<int, double, int, double>>, Types<double, double, int, int>));
+static_assert(!__is_same(Types<__builtin_sort_types<int, double, int, double>>, Types<double, double, int, int>)); // expected-error {{}}
+
+__builtin_sort_types<int, double> err1; // expected-error {{}}
+Types<__builtin_sort_types<int, double>*> err2; // expected-error {{}}
+Types<const __builtin_sort_types<int, double>> *err3; // expected-error {{}}
+
+template <template <class...> class Inner>
+struct Wrapper {
+ using result = Inner<int,int,int>*;
+};
+Types<Wrapper<__builtin_sort_types>::result> *err11; // expected-error {{use of template '__builtin_sort_types' requires template arguments}} \
+ // expected-note@* {{template declaration from hidden source}}
+
+
+
+// Check we properly defer evaluations for dependent inputs.
+// It is important for performance to avoid repeated sorting (mangling is expensive).
+template <class T>
+struct CheckDependent {
+ template <class U>
+ struct Inner {
+ using S1 = Types<__builtin_sort_types<T, U>>;
+ using S2 = Types<__builtin_sort_types<U, T>>;
+ using S3 = Types<__builtin_sort_types<int, T>>;
+ using S4 = Types<__builtin_sort_types<U, int>>;
+ };
+};
+
+using IntDouble = CheckDependent<int>::Inner<double>;
+static_assert(__is_same(Types<double, int>, CheckDependent<int>::Inner<double>::S1));
+static_assert(!__is_same(Types<double, int>, CheckDependent<int>::Inner<double>::S1)); //expected-error {{}}
+
+
+// Check that we delay the instantiations.
+template <class ...Ts> struct NoInts;
+template <> struct NoInts<> {
+ static constexpr bool value = true;
+};
+template <class T, class ...Ts> struct NoInts<T, Ts...> {
+ static constexpr bool value = !__is_same(T, int) && NoInts<Ts...>::value;
+};
More information about the cfe-commits
mailing list