-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[Clang] Normalize constraints before checking for satisfaction #161671
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
[Clang] Normalize constraints before checking for satisfaction #161671
Conversation
We substitute into concept ids for diagnostics purposes. However, most of the time this happen in SFINEA context and impact performance noticeably.
We may substitute _Fn::template __f (a dependent template name) into a template-template parameter name, when building a parameter mapping. They can't have the same NNSLoc, so the assertion doesn't hold. Also we missed a case when profiling the concept, as in InjectedClassNameType.
|
@zmodem Thanks, I'll investigate. Note that we expected some performance degradation on very concept heavy codebase without having a good way to quantify it - and it's not clear how much of that we can claw back, but we will certainly try to improve that as much as we can - I certainly agree that 10-15% is a lot There are still places where our concept implementations is non-conforming so in theory we would have to make the performance worse still, but these non-conformances are less observables so we'll try to focus on improving performances before doing anything else to our concepts implementation. |
|
I discussed this with Hans today, and we think one way to evaluate the performance impact would be to bootstrap clang itself with C++20 enabled. I think Chromium, and probably most C++ code in the wild, isn't using C++20 concepts heavily, and is mostly picking them up through libc++. I really would not want to eat a 16% C++ compile time regression on normal C++ code. As a point of comparison, we're using 32-bit source locations to avoid a 4% peak memory usage increase with 0% measurable regression, which is very conservative. It's hard, though, since it's a performance vs conformance (arguably correctness) tradeoff, it's just not a correctness regression, which we usually weight very highly when making tradeoff decisions. :) |
…ion. When establishing constraint satisfaction, we were building expressions even for compound constraint, This is unecessary extra work that acount for ~20% of the performance regression observed here llvm#161671 (comment)
…ion. When establishing constraint satisfaction, we were building expressions even for compound constraint, This is unecessary extra work that acount for ~20% of the performance regression observed here llvm#161671 (comment)
…tion (#164611) When establishing constraint satisfaction, we were building expressions even for compound constraint, This is unnecessary extra work that accounts for ~20% of the performance regression observed here #161671 (comment)
…ng satisfaction (#164611) When establishing constraint satisfaction, we were building expressions even for compound constraint, This is unnecessary extra work that accounts for ~20% of the performance regression observed here llvm/llvm-project#161671 (comment)
|
I bisected a breakage to this commit, and while I'm still unsure whether the code is breaking is valid or not, it's behaving unexpectedly: the breakage is affected by other template instantiations. I suspect something is wrong with this patch. Reduced/filed as #164750. |
@rupprecht #164777 will fix it. Thanks for the heads up! |
|
Here's another example TU. |
I looked into this, building a stage0 clang and libc++ configured with: and then using that to do a debug shared-library build of clang in c++20 mode: The increase from this change is small but measurable: about 5 CPU-minutes, or 1.5 %. I suppose it makes sense that the difference is smaller, since this codebase has no use of concepts at all besides via the STL, as opposed to Chromium which does seem to have a fair amount of concepts use in its base library, Abseil and so on. I'm not sure how to proceed here. A 16% build time increase (as mentioned up-thread) is very significant for us. @AaronBallman do you have any thoughts here? I did see that #61811 mention "We should get performance data for that change and give feedback to WG21 if it looks like this is going to have a significant performance impact." So it sounds like this was somewhat anticipated?
I don't know much about concepts, but is there some way we could keep the old, fast, code path for most cases, and only use the new more expensive logic when that's not sufficient? |
16% is a bigger performance regression than I think we were anticipating.
I think we expected a performance hit, but more like 1-3%, not 10+%; that's significant enough that if we can't solve it, it should be feedback given to WG21 because that feels like too much of a regression to swallow. CC @cor3ntin (it may be a while before he responds, the dev conference is next week and WG21 meetings are the week after). |
|
Note that I already pushed something that reduces the performance cost by a few percents. both me and Younan will keep exploring additional mitigations in the coming weeks. We think there are specific expressions which are unexpectedly costly, we are still trying to isolate exactly the problematic cases. |
|
Thanks both! See you at the dev meeting :) |
…tion (llvm#164611) When establishing constraint satisfaction, we were building expressions even for compound constraint, This is unnecessary extra work that accounts for ~20% of the performance regression observed here llvm#161671 (comment)
…tion (llvm#164611) When establishing constraint satisfaction, we were building expressions even for compound constraint, This is unnecessary extra work that accounts for ~20% of the performance regression observed here llvm#161671 (comment)
The logical or expression should be parenthesized. The issue was brought by llvm#161671 Fixes llvm#164104
…tion (llvm#164611) When establishing constraint satisfaction, we were building expressions even for compound constraint, This is unnecessary extra work that accounts for ~20% of the performance regression observed here llvm#161671 (comment)
|
Hi @cor3ntin , we've started seeing Clang crashes after this commit. They only reproduce with our Clang header modules setup on a pretty large input. Reduction has been crawling slowly, so I don't expect to get a nice little test case soon (or ever). The crashes are sporadic, but reproduce more or less consistently when clang is built with msan: It looks like serialization or deserialization issue. |
I see. We'll try to dig a bit deeper ourselves, maybe we'll get somewhere before @cor3ntin is back. And the test case reduction is running in the background (it's 30% through now, hopefully, there will be more progress by the end of the week). |
|
After a few iterations of cvise and manual reduction I came up with this: //--- test.sh
#!/bin/bash
$CLANG -isystem . -fmodule-name=stl -fmodule-map-file=stl.cppmap -Xclang=-fno-cxx-modules -Xclang=-fmodule-map-file-home-is-cwd -xc++ -Xclang=-emit-module -Xclang=-fmodules-local-submodule-visibility -fmodules -fno-implicit-modules -fno-implicit-module-maps -std=gnu++20 -nostdinc++ -nostdlib++ -c stl.cppmap -o stl.pcm && \
$CLANG -isystem . -fmodule-name=test -Xclang=-fno-cxx-modules -Xclang=-fmodule-map-file-home-is-cwd -fmodules -fno-implicit-modules -fno-implicit-module-maps -Xclang=-fmodule-file=stl.pcm -fmodule-map-file=stl.cppmap -std=gnu++20 -nostdinc++ -nostdlib++ -fsyntax-only test.cc
//--- test.cc
#include <bitset>
using std::size_t;
namespace std {
template <> struct char_traits<char> {
using comparison_category = strong_ordering;
};
template <class _CharT, class> class basic_string_view {
public:
using size_type = size_t;
basic_string_view(_CharT *, size_type);
template <contiguous_iterator _It, sized_sentinel_for<_It> _End>
basic_string_view(_It, _End);
int compare(basic_string_view);
};
template <class _CharT, class _Traits>
auto operator>(basic_string_view<_CharT, _Traits> __lhs,
type_identity_t<basic_string_view<_CharT>> __rhs) {
(__comparison_category<typename _Traits::comparison_category>);
return (__lhs.compare(__rhs));
}
template <class...> class tuple {};
template <class... _Tp> tuple<_Tp...> tie(_Tp...);
template <class... _Tp, class... _Up>
requires true
common_comparison_category_t<__synth_three_way_result<_Tp>...>
operator<=>(tuple<_Tp...>, tuple<_Up...>);
template <class _ToType, class _FromType>
requires true
_ToType bit_cast(_FromType);
template <class _Tp>
concept __is_derived_from_optional = requires(_Tp __t) { [] {}(__t); };
template <class> class optional {};
template <class _Tp, class _Up>
requires(!__is_derived_from_optional<_Up>)
_Up operator<=(_Tp, _Up);
template <class _CharT, class, class> class basic_string {
public:
using __self_view = basic_string_view<_CharT>;
operator __self_view() {}
};
template <class _CharT, class _Traits, class _Allocator>
auto operator<=>(basic_string<_CharT> __lhs,
basic_string<_Traits, _Allocator> __rhs) {
return basic_string_view<_CharT>(__lhs) > __rhs;
}
template <class> struct atomic {};
template <class _Tp>
requires is_floating_point_v<_Tp>
struct atomic<_Tp>;
template <class _InputIterator, class _Size, class _OutputIterator>
constexpr _OutputIterator copy_n(_InputIterator __first, _Size,
_OutputIterator __result) {
return copy(__first, __first, __result);
}
template <class _JoinViewIterator>
requires true
struct __segmented_iterator_traits<_JoinViewIterator>;
template <class _Tp, size_t _Size> struct array {
using value_type = _Tp;
_Tp __elems_[_Size];
constexpr value_type *data() { return __elems_; }
};
template <class _Fn>
requires true
auto bind_front(_Fn) {}
enum class _Trait { _TriviallyAvailable, _Unavailable };
template <typename _Tp, template <typename> class _IsTriviallyAvailable,
template <typename> class>
constexpr _Trait __trait =
_IsTriviallyAvailable<_Tp>::value ? _Trait::_TriviallyAvailable
: _Trait::_Unavailable;
constexpr _Trait __common_trait(initializer_list<_Trait>) {
_Trait __result = _Trait::_TriviallyAvailable;
return __result;
}
template <typename... _Types> struct __traits {
static constexpr _Trait __copy_constructible_trait =
__common_trait({__trait<_Types, is_trivially_copy_constructible,
is_copy_constructible>...});
};
template <class _Traits, _Trait = _Traits::__copy_constructible_trait>
class __copy_constructor {};
template <class... _Types> class variant {
__copy_constructor<__traits<_Types...>> __impl_;
};
} // namespace std
namespace absl {
template <typename T> using remove_cvref_t = std::remove_cvref<T>;
template <typename> class Span {};
template <typename T> Span<T> MakeSpan(T *);
template <class Hash> class raw_hash_set {
using e = remove_cvref_t<decltype(std::declval<Hash>()(std::declval<int>))>;
std::atomic<char> p(char *ptr) {
std::string_view(ptr, 0);
{
std::bit_cast<void *>(ptr);
}
};
};
template <typename F>
F WrapUnique(F)
requires true;
} // namespace absl
class MD {
public:
static bool Equals();
};
namespace util {
template <class> struct tag;
template <class> struct intrinsics;
template <class, class = void> struct has_all_el {};
template <class T> struct has_all_el<T, typename tag<T>::e>;
template <class T> struct has_all_elements : has_all_el<T> {};
template <size_t> struct wrap {};
template <size_t N> using rank = wrap<N>;
rank<0> rank_selector;
namespace internal {
template <typename... C> class ChainComparators {
ChainComparators()
requires true;
};
} // namespace internal
template <typename... C>
internal::ChainComparators<C...> ChainComparators(C...);
class {
bool oeto() { ChainComparators(); }
} D;
class I6 {
bool p(I6 rhs) { ChainComparators(rhs); }
};
template <size_t storage_size> class CompileTimeString {
public:
template <size_t length>
consteval CompileTimeString(const char (&s)[length]) : s(AsStdArray(s)) {}
std::array<char, storage_size> s;
template <size_t length>
consteval std::array<char, storage_size> AsStdArray(const char (&s)[length]) {
std::array<char, storage_size> out;
std::copy_n(s, storage_size, out.data());
return out;
}
};
template <size_t length>
CompileTimeString(const char (&)[length]) -> CompileTimeString<length - 1>;
template <class T>
requires true
struct tag<T>;
template <class = void> struct hasher {
template <class T> size_t b(T, rank<0>);
template <class T, class = std::enable_if<has_all_elements<T>::a>>
size_t b(T, rank<4>);
template <class T, class Self = hasher,
class = decltype(Self().b(std::declval<T>, rank_selector))>
size_t operator()(T);
};
using hash_t = hasher<>;
struct TaskId {
bool operator<(TaskId other) { tie(t, l) < tie(t); }
std::string t;
uint16_t l;
};
template <auto P>
requires true
class Opt {
Opt<MD::Equals>;
};
CompileTimeString s = "";
} // namespace util
class C {
void g() { std::bind_front(this); }
};
template <size_t> class PT {
template <size_t k>
requires true
PT(PT<k>);
};
void q() { absl::WrapUnique(new int); }
template <typename T>
requires true
void Su(T) {
Su(std::string());
}
class Packet {
public:
template <typename T>
requires true
T *Get();
absl::raw_hash_set<util::hash_t> ap_;
};
template <typename H>
requires true
H Construct;
template <typename H>
requires true
void Enc() {
Construct<int>;
Enc<int>;
std::variant<PT<16>, PT<32>> y_;
}
uint8_t Gset(std::optional<size_t> n) { (n <= 0); }
struct POption {
void Parse(Packet *p) {
Parse2<int>(
absl::MakeSpan(reinterpret_cast<const char *>(p->Get<POption>())));
}
template <typename AddonT>
requires true
bool Parse2(absl::Span<const char>);
};
enum AE { AT };
class A {
public:
static constexpr AE ST = AT;
};
class S2 {
S2(S2 &);
};
template <auto eid>
requires true
class B;
template <typename T>
using CanonicalType =
std::conditional<std::convertible_to<T, std::string_view>, std::string, T>;
template <typename> class M;
template <typename Anchor, typename... Fields>
concept IsValidAnchor = std::derived_from<Anchor, M<CanonicalType<Fields>...>>;
template <auto eid>
requires true
class P {
using Bas = B<eid>;
template <typename Anchor>
requires true
P(Anchor);
template <typename Anchor>
requires IsValidAnchor<Anchor, std::string>
P(Anchor);
S2 s;
};
struct S {
P<A::ST> p;
};
//--- bitset
#include <equality_comparable.h>
#include <synth_three_way.h>
namespace std {
template <class _Lhs, class>
concept assignable_from = requires(_Lhs __lhs) {
{ __lhs } -> same_as<_Lhs>;
};
template <class _Tp> using __add_lvalue_reference_t = _Tp;
template <class> struct is_copy_constructible;
template <class _Tp> constexpr bool is_class_v = __is_class(_Tp);
template <class _Tp>
concept __class_or_enum = is_class_v<_Tp>;
namespace ranges {
namespace __swap {
template <class _Tp, class>
concept __unqualified_swappable_with = requires(_Tp __t) { swap(__t); };
template <class _Tp>
concept __exchangeable =
!__unqualified_swappable_with<_Tp, _Tp> && assignable_from<_Tp &, _Tp>;
struct __fn {
template <class _Tp, class _Up>
requires __unqualified_swappable_with<_Tp, _Up>
void operator()(_Tp, _Up);
template <__exchangeable _Tp> void operator()(_Tp, _Tp);
};
} // namespace __swap
namespace {
auto swap = __swap::__fn{};
}
} // namespace ranges
template <class _Tp>
concept swappable = requires(_Tp __a, _Tp __b) { ranges::swap(__a, __b); };
template <class _Tp>
concept movable = swappable<_Tp>;
template <class _Tp> using remove_cv_t = _Tp;
template <class> struct __libcpp_is_floating_point : false_type {};
template <class _Tp>
struct is_floating_point : __libcpp_is_floating_point<_Tp> {};
template <class _Tp>
constexpr bool is_floating_point_v = is_floating_point<_Tp>::value;
template <bool, class, class> struct conditional;
template <bool> struct enable_if;
template <bool _Bp> using __enable_if_t = enable_if<_Bp>;
template <class _Tp>
concept __dereferenceable = requires(_Tp __t) { __t; };
template <__dereferenceable>
using iter_reference_t = struct random_access_iterator_tag;
} // namespace std
typedef char __uint8_t;
typedef int __uint16_t;
typedef __uint8_t uint8_t;
typedef __uint16_t uint16_t;
namespace std {
template <class _T1, class _T2> struct pair {
_T1 first;
_T2 second;
};
template <class _T1, class _T2> constexpr pair<_T1, _T2> make_pair(_T1, _T2) {
return pair<_T1, _T2>();
}
template <class _Iter, class> struct __unwrap_range_impl {
static constexpr auto __unwrap(_Iter __first, _Iter __last) {
return pair{__first, __last};
}
};
template <class _Iter, class _Sent>
constexpr auto __unwrap_range(_Iter __first, _Sent __last) {
return __unwrap_range_impl<_Iter, _Sent>::__unwrap(__first, __last);
}
template <class _In, class _Out>
constexpr pair<_In *, _Out *> __copy_trivial_impl(_In *, _In *__last,
_Out *__result) {
return make_pair(__last, __result);
}
template <class _Algorithm, class _InIter, class _Sent, class _OutIter>
constexpr pair<_InIter, _OutIter>
__copy_move_unwrap_iters(_InIter __first, _Sent __last, _OutIter __out_first) {
auto __range = __unwrap_range(__first, __last);
auto __result = _Algorithm()(__first, (__range.second), __out_first);
return make_pair(__first, ((__result.second)));
}
template <class _Ep> class initializer_list {
const _Ep *_;
size_t e;
};
template <class> struct __segmented_iterator_traits;
template <class, size_t = 0> bool __has_specialization_v;
template <class _Tp> bool __has_specialization_v<_Tp, sizeof(_Tp)>;
template <class _Iterator>
const bool __is_segmented_iterator_v =
__has_specialization_v<__segmented_iterator_traits<_Iterator>>;
struct __copy_impl {
template <class _InIter, class _OutIter,
__enable_if_t<__is_segmented_iterator_v<_InIter>> = 0>
pair<_InIter, _OutIter> operator()(_InIter, _InIter, _OutIter);
template <class _In, class _Out>
constexpr pair<_In *, _Out *> operator()(_In *__first, _In *__last,
_Out *__result) {
return __copy_trivial_impl(__first, __last, __result);
}
};
template <class _InIter, class _Sent, class _OutIter>
pair<_InIter, _OutIter> constexpr __copy(_InIter __first, _Sent __last,
_OutIter __result) {
return __copy_move_unwrap_iters<__copy_impl>(__first, __last, __result);
}
template <class _InputIterator, class _OutputIterator>
constexpr _OutputIterator copy(_InputIterator __first, _InputIterator __last,
_OutputIterator __result) {
return __copy(__first, __last, __result).second;
}
template <class _Bp, class _Dp>
inline constexpr bool is_base_of_v = __is_base_of(_Bp, _Dp);
template <class, class _Bp>
concept derived_from = is_base_of_v<_Bp, _Bp>;
namespace ranges {
namespace __iter_move {
template <class _Tp>
concept __unqualified_iter_move = __class_or_enum<remove_cv_t<_Tp>>;
template <class _Tp>
concept __move_deref = requires(_Tp &__t) { __t; };
template <class _Tp>
concept __just_deref = !__unqualified_iter_move<_Tp> && !__move_deref<_Tp>;
struct __fn {
template <class _Ip>
requires __unqualified_iter_move<_Ip>
auto operator()(_Ip);
template <class _Ip>
requires __move_deref<_Ip>
constexpr auto operator()(_Ip __i) -> decltype(__i);
template <class _Ip>
requires __just_deref<_Ip>
auto operator()(_Ip);
};
} // namespace __iter_move
namespace {
auto iter_move = __iter_move::__fn{};
}
} // namespace ranges
template <__dereferenceable _Tp>
using iter_rvalue_reference_t = decltype(ranges::iter_move(declval<_Tp>));
template <class _In>
concept __indirectly_readable_impl =
common_reference_with<iter_reference_t<_In>, iter_rvalue_reference_t<_In>>;
template <class _In>
concept indirectly_readable = __indirectly_readable_impl<remove_cvref_t<_In>>;
template <class _Ip>
concept weakly_incrementable = movable<_Ip>;
template <class _Ip>
concept input_or_output_iterator = requires(_Ip __i) {
{ __i } -> __referenceable;
} && weakly_incrementable<_Ip>;
template <class _Sp, class _Ip>
concept sentinel_for = __weakly_equality_comparable_with<_Sp, _Ip>;
template <class, class _Ip>
concept sized_sentinel_for = requires(_Ip __i) { __i; };
template <class _Ip>
concept input_iterator =
input_or_output_iterator<_Ip> && indirectly_readable<_Ip>;
template <class _Ip>
concept forward_iterator = input_iterator<_Ip> && sentinel_for<_Ip, _Ip>;
template <class _Ip>
concept bidirectional_iterator = forward_iterator<_Ip>;
template <class _Ip>
concept random_access_iterator = bidirectional_iterator<_Ip>;
template <class _Ip>
concept contiguous_iterator = random_access_iterator<_Ip>;
template <class _Tp>
struct is_trivially_copy_constructible
: integral_constant<bool, __is_trivially_constructible(
_Tp, __add_lvalue_reference_t<const _Tp>)> {};
template <class> struct char_traits;
template <class _CharT, class = _CharT, class = _CharT> class basic_string;
using string = basic_string<char>;
template <class _CharT, class = char_traits<_CharT>> class basic_string_view;
typedef basic_string_view<char> string_view;
} // namespace std
//--- synth_three_way.h
#ifndef _LIBCPP___COMPARE_SYNTH_THREE_WAY_H
#define _LIBCPP___COMPARE_SYNTH_THREE_WAY_H
#include <equality_comparable.h>
namespace std {
struct _CmpUnspecifiedParam {
template <class _Tp> _CmpUnspecifiedParam(_Tp);
};
class partial_ordering;
class weak_ordering {
public:
static weak_ordering equivalent;
friend bool operator<(weak_ordering, _CmpUnspecifiedParam);
};
class strong_ordering;
template <class _Tp>
concept __comparison_category = is_same_v<_Tp, strong_ordering>;
using size_t = decltype(sizeof(int));
namespace __comp_detail {
auto __get_comp_type() { return weak_ordering::equivalent; }
} // namespace __comp_detail
template <class...> struct common_comparison_category {
using type = decltype(__comp_detail::__get_comp_type());
};
template <class...>
using common_comparison_category_t = common_comparison_category<>::type;
template <class _Tp, class>
concept __partially_ordered_with = requires(_Tp __t) {
{ __t } -> __boolean_testable;
};
template <class, class _Cat>
concept __compares_as = same_as<common_comparison_category_t<>, _Cat>;
template <class _Tp, class _Up, class _Cat = partial_ordering>
concept three_way_comparable_with =
__weakly_equality_comparable_with<_Tp, _Up> &&
__partially_ordered_with<_Tp, _Up> && requires(_Up __u) {
{ __u } -> __compares_as<_Cat>;
};
auto __synth_three_way = []<class _Tp, class _Up>(_Tp __t, _Up __u)
requires requires {
{ __t < __u };
}
{
if (three_way_comparable_with<_Tp, _Up>) {
}
};
template <class _Tp, class _Up = _Tp>
using __synth_three_way_result = decltype(__synth_three_way(_Tp(), _Up()));
} // namespace std
#endif
//--- equality_comparable.h
namespace std {
template <class _Tp, _Tp __v> struct integral_constant {
static const _Tp value = __v;
};
typedef integral_constant<bool, false> false_type;
template <bool _Val> using _BoolConstant = integral_constant<bool, _Val>;
template <class _Tp, class _Up> constexpr bool is_same_v = __is_same(_Tp, _Up);
template <class _Tp, class _Up>
using _IsSame = _BoolConstant<__is_same(_Tp, _Up)>;
template <class _From, class _To>
constexpr bool is_convertible_v = __is_convertible(_From, _To);
template <class _Tp> _Tp declval();
template <class _From, class _To>
concept convertible_to = is_convertible_v<_From, _To>;
template <class _Tp>
concept __boolean_testable_impl = convertible_to<_Tp, bool>;
template <class _Tp>
concept __boolean_testable = requires(_Tp __t) {
{ __t } -> __boolean_testable_impl;
};
template <class _Tp, class _Up>
concept __same_as_impl = _IsSame<_Tp, _Up>::value;
template <class _Tp, class _Up>
concept same_as = __same_as_impl<_Up, _Tp>;
template <class, class = void> bool __is_referenceable_v;
template <class _Tp> const bool __is_referenceable_v<_Tp> = true;
template <class _Tp>
concept __referenceable = __is_referenceable_v<_Tp>;
template <class _Tp> using add_pointer_t = _Tp;
template <class _Tp> struct type_identity {
typedef _Tp type;
};
template <class _Tp> using type_identity_t = type_identity<_Tp>::type;
template <class _From, class> using __copy_cv_t = _From;
template <class> bool is_reference_v;
template <class> struct remove_cvref;
template <class _Tp> using remove_cvref_t = _Tp;
template <class _Tp> using remove_reference_t = _Tp;
template <class, class _Yp> using __cond_res = decltype(false ?: declval<_Yp>);
template <class _Ap, class _Bp, class = _Ap, class = remove_reference_t<_Bp>>
struct __common_ref;
template <class _Xp, class _Yp> using __common_ref_t = __common_ref<_Xp, _Yp>;
template <class _Xp, class _Yp>
using __cv_cond_res = __cond_res<__copy_cv_t<_Xp, _Yp>, __copy_cv_t<_Yp, _Xp>>;
template <class _Ap, class _Bp, class _Xp, class _Yp>
requires is_reference_v<__cv_cond_res<_Xp, _Yp>>
struct __common_ref<_Ap, _Bp, _Xp, _Yp>;
template <class _Tp, class _Up> using __common_ref_D = __common_ref_t<_Tp, _Up>;
template <class _Ap, class _Bp, class _Xp, class _Yp>
requires is_convertible_v<_Ap, __common_ref_D<_Xp, _Yp>>
struct __common_ref<_Ap, _Bp, _Xp, _Yp>;
template <class _Tp, class _Up>
requires is_convertible_v<_Up, add_pointer_t<__common_ref_t<_Tp, _Up>>>
_Up common_reference_with;
template <class _Tp, class>
concept __weakly_equality_comparable_with = requires(_Tp __t) {
{ __t } -> __boolean_testable;
};
} // namespace std
//--- stl.cppmap
module "stl" {
module "synth_three_way.h" {
header "synth_three_way.h"
}
header "bitset"
}(as usual, this may easily be overreduced and not completely valid, but the initial inputs were totally fine, AFAICT) |
@cor3ntin please take a look. |
|
I am still afaik, I will take a look when I come back - hopefully next week Cc @zyn0217 |
Thanks! This is not a pressing issue for us now, since we could mitigate this for a time being. Have a nice time off! |
|
Coming back to the compile-time issue, I've been trying to find examples that may be instructive in figuring out what's getting slower. The attached file seems interesting, because it's basically a large table, and so it uses a few constructs many many times:usb_ids_gen.ii.gz Building with is 140% slower after this change. |
In the standard, constraint satisfaction checking is done on the normalized form of a constraint.
Clang instead substitutes on the non-normalized form, which causes us to report substitution failures in template arguments or concept ids, which is non-conforming but unavoidable without a parameter mapping
This patch normalizes before satisfaction checking. However, we preserve concept-id nodes in the normalized form, solely for diagnostics purposes.
This addresses #61811 and related concepts conformance bugs, ideally to make the remaining implementation of concept template parameters easier
Fixes #135190
Fixes #61811
Co-authored-by: Younan Zhang [email protected]