llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.59k stars 11.34k forks source link

source_location 18.x regression from 8c2b0d4175dcfe669a43d0173fd00ed3 #81155

Open AaronBallman opened 6 months ago

AaronBallman commented 6 months ago

This was found internally at Intel from some customer code. The original test case was stripped down to:

template <bool> using enable_if_t = int;
template <class Ty> Ty Returns_exactly() noexcept;
struct InvokeBase {
  template <class Fx, class... Args>
  static auto Call(Fx func, Args... args) -> decltype(func(args...));
};
template <class Fx1, class Fx2 = Fx1, bool = false> struct Invoke;
template <class Fx1, class Fx2> struct Invoke<Fx1, Fx2, false> : InvokeBase {};
template <int> struct F { };
template <class...> struct InvocableR;
template <class Rx, class Callable, class... Args>
struct InvocableR<Rx, Callable, Args...> : 
  F<noexcept(Invoke<Rx>::Call(Returns_exactly<Rx>(), Returns_exactly<Callable>()))> {
  static constexpr bool value = false;
};
template <class Rx, class... Args> constexpr bool IsInvocableRV = InvocableR<Rx, Args...>::value;
template <class, class... Types> class FuncClass {
public:
  template <class Fx> using EnableIfCallable = enable_if_t<IsInvocableRV<Fx, Types...>>;
};
template <class> struct FuncImpl;
template <class Ret, class... Types> struct FuncImpl<Ret(Types...)> {
  using type = FuncClass<Ret, Types...>;
};
template <class FTy> class function {
  using Base = typename FuncImpl<FTy>::type;

public:
  template <class Fx, typename Base::template EnableIfCallable<Fx> = 0> function(Fx) {}
};

struct codeloc {
  static constexpr codeloc current_location(const char *name = __builtin_FUNCTION()) {
    return codeloc();
  }
  constexpr codeloc() {}
};
class buff {
public:
  buff(buff &, codeloc = codeloc::current_location());
};
void Help(function<void(buff)>) {
}
void Test() {
  Help([](buff) {});
}

https://godbolt.org/z/qao5jhv1e

And with help from @Endilll it was further reduced to:

struct buff {
  buff(buff &, const char * = __builtin_FUNCTION());
};

template <class Ty>
Ty declval();

template <class Fx>
auto Call(buff arg) -> decltype(Fx{}(arg));

template <typename>
struct F {};

template <class Fx>
struct InvocableR : F<decltype(Call<Fx>(declval<buff>()))> {
  static constexpr bool value = false;
};

template <class Fx, bool = InvocableR<Fx>::value>
void Help(Fx) {}

void Test() {
  Help([](buff) {});
}

https://godbolt.org/z/3n3K46WT8

It's possibly worth noting that the behavior may be different on Windows than on Linux.

CC @cor3ntin

llvmbot commented 6 months ago

@llvm/issue-subscribers-c-20

Author: Aaron Ballman (AaronBallman)

This was found internally at Intel from some customer code. The original test case was stripped down to: ``` template <bool> using enable_if_t = int; template <class Ty> Ty Returns_exactly() noexcept; struct InvokeBase { template <class Fx, class... Args> static auto Call(Fx func, Args... args) -> decltype(func(args...)); }; template <class Fx1, class Fx2 = Fx1, bool = false> struct Invoke; template <class Fx1, class Fx2> struct Invoke<Fx1, Fx2, false> : InvokeBase {}; template <int> struct F { }; template <class...> struct InvocableR; template <class Rx, class Callable, class... Args> struct InvocableR<Rx, Callable, Args...> : F<noexcept(Invoke<Rx>::Call(Returns_exactly<Rx>(), Returns_exactly<Callable>()))> { static constexpr bool value = false; }; template <class Rx, class... Args> constexpr bool IsInvocableRV = InvocableR<Rx, Args...>::value; template <class, class... Types> class FuncClass { public: template <class Fx> using EnableIfCallable = enable_if_t<IsInvocableRV<Fx, Types...>>; }; template <class> struct FuncImpl; template <class Ret, class... Types> struct FuncImpl<Ret(Types...)> { using type = FuncClass<Ret, Types...>; }; template <class FTy> class function { using Base = typename FuncImpl<FTy>::type; public: template <class Fx, typename Base::template EnableIfCallable<Fx> = 0> function(Fx) {} }; struct codeloc { static constexpr codeloc current_location(const char *name = __builtin_FUNCTION()) { return codeloc(); } constexpr codeloc() {} }; class buff { public: buff(buff &, codeloc = codeloc::current_location()); }; void Help(function<void(buff)>) { } void Test() { Help([](buff) {}); } ``` https://godbolt.org/z/qao5jhv1e And with help from @Endilll it was further reduced to: ``` struct buff { buff(buff &, const char * = __builtin_FUNCTION()); }; template <class Ty> Ty declval(); template <class Fx> auto Call(buff arg) -> decltype(Fx{}(arg)); template <typename> struct F {}; template <class Fx> struct InvocableR : F<decltype(Call<Fx>(declval<buff>()))> { static constexpr bool value = false; }; template <class Fx, bool = InvocableR<Fx>::value> void Help(Fx) {} void Test() { Help([](buff) {}); } ``` https://godbolt.org/z/3n3K46WT8 It's possibly worth noting that the behavior may be different on Windows than on Linux. CC @cor3ntin
llvmbot commented 6 months ago

@llvm/issue-subscribers-clang-frontend

Author: Aaron Ballman (AaronBallman)

This was found internally at Intel from some customer code. The original test case was stripped down to: ``` template <bool> using enable_if_t = int; template <class Ty> Ty Returns_exactly() noexcept; struct InvokeBase { template <class Fx, class... Args> static auto Call(Fx func, Args... args) -> decltype(func(args...)); }; template <class Fx1, class Fx2 = Fx1, bool = false> struct Invoke; template <class Fx1, class Fx2> struct Invoke<Fx1, Fx2, false> : InvokeBase {}; template <int> struct F { }; template <class...> struct InvocableR; template <class Rx, class Callable, class... Args> struct InvocableR<Rx, Callable, Args...> : F<noexcept(Invoke<Rx>::Call(Returns_exactly<Rx>(), Returns_exactly<Callable>()))> { static constexpr bool value = false; }; template <class Rx, class... Args> constexpr bool IsInvocableRV = InvocableR<Rx, Args...>::value; template <class, class... Types> class FuncClass { public: template <class Fx> using EnableIfCallable = enable_if_t<IsInvocableRV<Fx, Types...>>; }; template <class> struct FuncImpl; template <class Ret, class... Types> struct FuncImpl<Ret(Types...)> { using type = FuncClass<Ret, Types...>; }; template <class FTy> class function { using Base = typename FuncImpl<FTy>::type; public: template <class Fx, typename Base::template EnableIfCallable<Fx> = 0> function(Fx) {} }; struct codeloc { static constexpr codeloc current_location(const char *name = __builtin_FUNCTION()) { return codeloc(); } constexpr codeloc() {} }; class buff { public: buff(buff &, codeloc = codeloc::current_location()); }; void Help(function<void(buff)>) { } void Test() { Help([](buff) {}); } ``` https://godbolt.org/z/qao5jhv1e And with help from @Endilll it was further reduced to: ``` struct buff { buff(buff &, const char * = __builtin_FUNCTION()); }; template <class Ty> Ty declval(); template <class Fx> auto Call(buff arg) -> decltype(Fx{}(arg)); template <typename> struct F {}; template <class Fx> struct InvocableR : F<decltype(Call<Fx>(declval<buff>()))> { static constexpr bool value = false; }; template <class Fx, bool = InvocableR<Fx>::value> void Help(Fx) {} void Test() { Help([](buff) {}); } ``` https://godbolt.org/z/3n3K46WT8 It's possibly worth noting that the behavior may be different on Windows than on Linux. CC @cor3ntin
cor3ntin commented 6 months ago

I spent some time looking into this, I confirm the bug but I struggle finding a solution, or an exact root cause. Considering __builtin_FUNCTION value dependent causes AddTemplateOverloadCandidate (for Call) to fail.

I was not able to find where we look at value dependence of default values