[clang] [Clang][HIP] Target-dependent overload resolution in declarators and specifiers (PR #103031)

Fabian Ritter via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 10 08:13:28 PDT 2024


================
@@ -0,0 +1,703 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsyntax-only -verify=expected,onhost %s
+// RUN: %clang_cc1 -triple nvptx64-nvidia-cuda -fsyntax-only -fcuda-is-device -verify=expected,ondevice %s
+
+
+// Tests to ensure that functions with host and device overloads in that are
+// called outside of function bodies and variable initializers, e.g., in
+// template arguments are resolved with respect to the declaration to which they
+// belong.
+
+// Opaque types used for tests:
+struct DeviceTy {};
+struct HostTy {};
+struct HostDeviceTy {};
+struct TemplateTy {};
+
+struct TrueTy { static const bool value = true; };
+struct FalseTy { static const bool value = false; };
+
+// Select one of two types based on a boolean condition.
+template <bool COND, typename T, typename F> struct select_type {};
+template <typename T, typename F> struct select_type<true, T, F> { typedef T type; };
+template <typename T, typename F> struct select_type<false, T, F> { typedef F type; };
+
+template <bool C> struct check : public select_type<C, TrueTy, FalseTy> { };
+
+// Check if two types are the same.
+template<class T, class U> struct is_same : public FalseTy { };
+template<class T> struct is_same<T, T> : public TrueTy { };
+
+// A static assertion that fails at compile time if the expression E does not
+// have type T.
+#define ASSERT_HAS_TYPE(E, T) static_assert(is_same<decltype(E), T>::value);
+
+
+// is_on_device() is true when called in a device context and false if called in a host context.
+__attribute__((host)) constexpr bool is_on_device(void) { return false; }
+__attribute__((device)) constexpr bool is_on_device(void) { return true; }
+
+
+// this type depends on whether it occurs in host or device code
+#define targetdep_t select_type<is_on_device(), DeviceTy, HostTy>::type
+
+// Defines and typedefs with different values in host and device compilation.
+#ifdef __CUDA_ARCH__
+#define CurrentTarget DEVICE
+typedef DeviceTy CurrentTargetTy;
+typedef DeviceTy TemplateIfHostTy;
+#else
+#define CurrentTarget HOST
+typedef HostTy CurrentTargetTy;
+typedef TemplateTy TemplateIfHostTy;
+#endif
+
+
+
+// targetdep_t in function declarations should depend on the target of the
+// declared function.
+__attribute__((device)) targetdep_t decl_ret_early_device(void);
+ASSERT_HAS_TYPE(decl_ret_early_device(), DeviceTy)
+
+__attribute__((host)) targetdep_t decl_ret_early_host(void);
+ASSERT_HAS_TYPE(decl_ret_early_host(), HostTy)
+
+__attribute__((host,device)) targetdep_t decl_ret_early_host_device(void);
+ASSERT_HAS_TYPE(decl_ret_early_host_device(), CurrentTargetTy)
+
+// If the function target is specified too late and can therefore not be
+// considered for overload resolution in targetdep_t, warn.
+targetdep_t __attribute__((device)) decl_ret_late_device(void); // expected-warning {{target attribute has been ignored for overload resolution}}
+ASSERT_HAS_TYPE(decl_ret_late_device(), HostTy)
+
+// No warning necessary if the ignored attribute doesn't change the result.
+targetdep_t __attribute__((host)) decl_ret_late_host(void);
+ASSERT_HAS_TYPE(decl_ret_late_host(), HostTy)
+
+targetdep_t __attribute__((host,device)) decl_ret_late_host_device(void); // expected-warning {{target attribute has been ignored for overload resolution}}
+ASSERT_HAS_TYPE(decl_ret_late_host_device(), HostTy)
+
+// An odd way of writing this, but it's possible.
+__attribute__((device)) targetdep_t __attribute__((host)) decl_ret_early_device_late_host(void); // expected-warning {{target attribute has been ignored for overload resolution}}
+ASSERT_HAS_TYPE(decl_ret_early_device_late_host(), DeviceTy)
+
+
+// The same for function definitions and parameter types:
+__attribute__((device)) targetdep_t ret_early_device(targetdep_t x) {
+  ASSERT_HAS_TYPE(ret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+__attribute__((host)) targetdep_t ret_early_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(ret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+__attribute__((host, device)) targetdep_t ret_early_hostdevice(targetdep_t x) {
+  ASSERT_HAS_TYPE(ret_early_hostdevice({}), CurrentTargetTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+// The parameter is still after the attribute, so it needs no warning.
+targetdep_t __attribute__((device)) // expected-warning {{target attribute has been ignored for overload resolution}}
+ret_late_device(targetdep_t x) {
+  ASSERT_HAS_TYPE(ret_late_device({}), HostTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+targetdep_t __attribute__((host, device)) // expected-warning {{target attribute has been ignored for overload resolution}}
+ret_late_hostdevice(targetdep_t x) {
+  ASSERT_HAS_TYPE(ret_late_hostdevice({}), HostTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+targetdep_t __attribute__((host)) ret_late_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(ret_late_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+__attribute__((device)) targetdep_t __attribute__((host)) // expected-warning {{target attribute has been ignored for overload resolution}}
+ret_early_device_late_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(ret_early_device_late_host({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+// The attribute is even later, so we can't choose the expected overload.
+targetdep_t ret_verylate_device(targetdep_t x) __attribute__((device)) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(ret_verylate_device({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+// It's possible to get two different wrong types:
+targetdep_t __attribute__((device)) // expected-warning {{target attribute has been ignored for overload resolution}}
+ret_late_device_verylate_host(targetdep_t x) __attribute__((host)) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(ret_late_device_verylate_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+
+// Increasingly unusual ways to specify a return type:
+
+// The attribute is specified much earlier than the overload happens, works as
+// expected.
+__attribute__((device)) auto autoret_early_device(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(autoret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+// The attribute is specified much earlier than the overload happens, works as
+// expected.
+__attribute__((host)) auto autoret_early_host(targetdep_t x) -> targetdep_t  {
+  ASSERT_HAS_TYPE(autoret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+// The attribute is specified much earlier than the overload happens, works as
+// expected.
+__attribute__((host,device)) auto autoret_early_hostdevice(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(autoret_early_hostdevice({}), CurrentTargetTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+
+// The attribute is still specified earlier than the overload happens, works as
+// expected.
+auto __attribute__((device)) autoret_late_device(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(autoret_late_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+// The attribute is still specified earlier than the overload happens, works as
+// expected.
+auto __attribute__((host)) autoret_late_host(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(autoret_late_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+// The attribute is still specified earlier than the overload happens, works as
+// expected.
+auto __attribute__((host,device)) autoret_late_hostdevice(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(autoret_late_hostdevice({}), CurrentTargetTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+
+// There should be no problem if the return type is inferred from an expression in the body:
+auto __attribute__((device)) fullauto_device(targetdep_t x) {
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return (targetdep_t)(x);
+}
+ASSERT_HAS_TYPE(fullauto_device({}), DeviceTy)
+
+auto __attribute__((host)) fullauto_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(x, HostTy)
+  return (targetdep_t)(x);
+}
+ASSERT_HAS_TYPE(fullauto_host({}), HostTy)
+
+// The return type is as expected, but the argument type precedes the attribute,
+// so we don't get the right type for it.
+auto fullauto_verylate_device(targetdep_t x) __attribute__((device)) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(x, HostTy)
+  return targetdep_t();
+}
+ASSERT_HAS_TYPE(fullauto_verylate_device({}), DeviceTy)
+
+auto fullauto_verylate_host(targetdep_t x) __attribute__((host)) {
+  ASSERT_HAS_TYPE(x, HostTy)
+  return targetdep_t();
+}
+ASSERT_HAS_TYPE(fullauto_verylate_host({}), HostTy)
+
+
+// MS __declspec syntax:
+__declspec(__device__) targetdep_t ms_ret_early_device(targetdep_t x) {
+  ASSERT_HAS_TYPE(ms_ret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+__declspec(__host__) targetdep_t ms_ret_early_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(ms_ret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+__declspec(__host__) __declspec(__device__) targetdep_t ms_ret_early_hostdevice(targetdep_t x) {
+  ASSERT_HAS_TYPE(ms_ret_early_hostdevice({}), CurrentTargetTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+targetdep_t __declspec(__device__) ms_ret_late_device(targetdep_t x) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(ms_ret_late_device({}), HostTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+targetdep_t __declspec(__host__) ms_ret_late_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(ms_ret_late_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+targetdep_t __declspec(__host__) __declspec(__device__) ms_ret_late_hostdevice(targetdep_t x) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(ms_ret_late_hostdevice({}), HostTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+__declspec(__device__) targetdep_t __declspec(__host__) ms_ret_early_device_late_host(targetdep_t x) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(ms_ret_early_device_late_host({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+__declspec(__device__) auto ms_autoret_early_device(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(ms_autoret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+__declspec(__host__) auto ms_autoret_early_host(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(ms_autoret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+__declspec(__host__) __declspec(__device__) auto ms_autoret_early_hostdevice(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(ms_autoret_early_hostdevice({}), CurrentTargetTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+
+auto __declspec(__device__) ms_autoret_late_device(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(ms_autoret_late_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+auto __declspec(__host__) ms_autoret_late_host(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(ms_autoret_late_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+auto __declspec(__host__) __declspec(__device__) ms_autoret_late_hostdevice(targetdep_t x) -> targetdep_t {
+  ASSERT_HAS_TYPE(ms_autoret_late_hostdevice({}), CurrentTargetTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+
+// Class/Struct member functions:
+
+struct MethodTests {
+  __attribute__((device)) targetdep_t ret_early_device(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_early_device({}), DeviceTy)
+    ASSERT_HAS_TYPE(x, DeviceTy)
+    return {};
+  }
+
+  __attribute__((host)) targetdep_t ret_early_host(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_early_host({}), HostTy)
+    ASSERT_HAS_TYPE(x, HostTy)
+    return {};
+  }
+
+  __attribute__((host,device)) targetdep_t ret_early_hostdevice(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_early_hostdevice({}), CurrentTargetTy)
+    ASSERT_HAS_TYPE(x, CurrentTargetTy)
+    return {};
+  }
+
+  __attribute__((device)) auto autoret_early_device(targetdep_t x) -> targetdep_t {
+    ASSERT_HAS_TYPE(autoret_early_device({}), DeviceTy)
+    ASSERT_HAS_TYPE(x, DeviceTy)
+    return {};
+  }
+  __attribute__((host)) auto autoret_early_host(targetdep_t x) -> targetdep_t {
+    ASSERT_HAS_TYPE(autoret_early_host({}), HostTy)
+    ASSERT_HAS_TYPE(x, HostTy)
+    return {};
+  }
+
+  __attribute__((host,device)) auto autoret_early_hostdevice(targetdep_t x) -> targetdep_t {
+    ASSERT_HAS_TYPE(autoret_early_hostdevice({}), CurrentTargetTy)
+    ASSERT_HAS_TYPE(x, CurrentTargetTy)
+    return {};
+  }
+
+
+  // Overloaded call happens in return type, attribute is after that.
+  targetdep_t __attribute__((device)) ret_late_device(targetdep_t x) {  // expected-warning {{target attribute has been ignored for overload resolution}}
+    ASSERT_HAS_TYPE(ret_late_device({}), HostTy)
+    ASSERT_HAS_TYPE(x, DeviceTy)
+    return {};
+  }
+
+  targetdep_t __attribute__((host)) ret_late_host(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_late_host({}), HostTy)
+    ASSERT_HAS_TYPE(x, HostTy)
+    return {};
+  }
+
+  targetdep_t __attribute__((host,device)) ret_late_hostdevice(targetdep_t x) {  // expected-warning {{target attribute has been ignored for overload resolution}}
+    ASSERT_HAS_TYPE(ret_late_hostdevice({}), HostTy)
+    ASSERT_HAS_TYPE(x, CurrentTargetTy)
+    return {};
+  }
+
+
+  // Member declarations (tested in the 'tests' function further below):
+  __attribute__((device)) targetdep_t decl_ret_early_device(void);
+  __attribute__((host)) targetdep_t decl_ret_early_host(void);
+  __attribute__((host,device)) targetdep_t decl_ret_early_hostdevice(void);
+  targetdep_t __attribute__((device)) decl_ret_late_device(void);  // expected-warning {{target attribute has been ignored for overload resolution}}
+  targetdep_t __attribute__((host)) decl_ret_late_host(void);
+  targetdep_t __attribute__((host,device)) decl_ret_late_hostdevice(void);  // expected-warning {{target attribute has been ignored for overload resolution}}
+
+  // for out of line definitions:
+  __attribute__((device)) targetdep_t ool_ret_early_device(targetdep_t x);
+  __attribute__((host)) targetdep_t ool_ret_early_host(targetdep_t x);
+  __attribute__((host,device)) targetdep_t ool_ret_early_hostdevice(targetdep_t x);
+  targetdep_t __attribute__((device)) ool_ret_late_device(targetdep_t x);  // expected-warning {{target attribute has been ignored for overload resolution}}
+  targetdep_t __attribute__((host)) ool_ret_late_host(targetdep_t x);
+  targetdep_t __attribute__((host,device)) ool_ret_late_hostdevice(targetdep_t x);  // expected-warning {{target attribute has been ignored for overload resolution}}
+
+};
+
+__attribute__((device)) targetdep_t MethodTests::ool_ret_early_device(targetdep_t x) {
+  ASSERT_HAS_TYPE(ool_ret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+__attribute__((host)) targetdep_t MethodTests::ool_ret_early_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(ool_ret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+__attribute__((host,device)) targetdep_t MethodTests::ool_ret_early_hostdevice(targetdep_t x) {
+  ASSERT_HAS_TYPE(ool_ret_early_hostdevice({}), CurrentTargetTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+targetdep_t __attribute__((device)) MethodTests::ool_ret_late_device(targetdep_t x) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(ool_ret_late_device({}), HostTy)
+  ASSERT_HAS_TYPE(x, DeviceTy)
+  return {};
+}
+
+targetdep_t __attribute__((host)) MethodTests::ool_ret_late_host(targetdep_t x) {
+  ASSERT_HAS_TYPE(ool_ret_late_host({}), HostTy)
+  ASSERT_HAS_TYPE(x, HostTy)
+  return {};
+}
+
+targetdep_t __attribute__((host,device)) MethodTests::ool_ret_late_hostdevice(targetdep_t x) { // expected-warning {{target attribute has been ignored for overload resolution}}
+  ASSERT_HAS_TYPE(ool_ret_late_hostdevice({}), HostTy)
+  ASSERT_HAS_TYPE(x, CurrentTargetTy)
+  return {};
+}
+
+
+// members of templated structs should also work.
+template <unsigned int N>
+struct TemplateMethodTests {
+  __attribute__((device)) targetdep_t ret_early_device(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_early_device({}), DeviceTy)
+    ASSERT_HAS_TYPE(x, DeviceTy)
+    return {};
+  }
+
+  __attribute__((host)) targetdep_t ret_early_host(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_early_host({}), HostTy)
+    ASSERT_HAS_TYPE(x, HostTy)
+    return {};
+  }
+
+  __attribute__((host,device)) targetdep_t ret_early_hostdevice(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_early_hostdevice({}), CurrentTargetTy)
+    ASSERT_HAS_TYPE(x, CurrentTargetTy)
+    return {};
+  }
+
+  __attribute__((device)) auto autoret_early_device(targetdep_t x) -> targetdep_t {
+    ASSERT_HAS_TYPE(autoret_early_device({}), DeviceTy)
+    ASSERT_HAS_TYPE(x, DeviceTy)
+    return {};
+  }
+
+  __attribute__((host)) auto autoret_early_host(targetdep_t x) -> targetdep_t {
+    ASSERT_HAS_TYPE(autoret_early_host({}), HostTy)
+    ASSERT_HAS_TYPE(x, HostTy)
+    return {};
+  }
+
+  __attribute__((host,device)) auto autoret_early_hostdevice(targetdep_t x) -> targetdep_t {
+    ASSERT_HAS_TYPE(autoret_early_hostdevice({}), CurrentTargetTy)
+    ASSERT_HAS_TYPE(x, CurrentTargetTy)
+    return {};
+  }
+
+  targetdep_t __attribute__((device)) ret_late_device(targetdep_t x) { // expected-warning {{target attribute has been ignored for overload resolution}}
+    ASSERT_HAS_TYPE(ret_late_device({}), HostTy)
+    ASSERT_HAS_TYPE(x, DeviceTy)
+    return {};
+  }
+
+  targetdep_t __attribute__((host)) ret_late_host(targetdep_t x) {
+    ASSERT_HAS_TYPE(ret_late_host({}), HostTy)
+    ASSERT_HAS_TYPE(x, HostTy)
+    return {};
+  }
+
+  targetdep_t __attribute__((host,device)) ret_late_hostdevice(targetdep_t x) { // expected-warning {{target attribute has been ignored for overload resolution}}
+    ASSERT_HAS_TYPE(ret_late_hostdevice({}), HostTy)
+    ASSERT_HAS_TYPE(x, CurrentTargetTy)
+    return {};
+  }
+
+
+  __attribute__((device)) targetdep_t decl_ret_early_device(void);
+  __attribute__((host)) targetdep_t decl_ret_early_host(void);
+  __attribute__((host,device)) targetdep_t decl_ret_early_hostdevice(void);
+
+  targetdep_t __attribute__((device)) decl_ret_late_device(void); // expected-warning {{target attribute has been ignored for overload resolution}}
+  targetdep_t __attribute__((host)) decl_ret_late_host(void);
+  targetdep_t __attribute__((host,device)) decl_ret_late_hostdevice(void); // expected-warning {{target attribute has been ignored for overload resolution}}
+};
+
+void tests(void) {
+  MethodTests mt;
+
+  ASSERT_HAS_TYPE(mt.ret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(mt.ret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(mt.ret_early_hostdevice({}), CurrentTargetTy)
+
+  ASSERT_HAS_TYPE(mt.autoret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(mt.autoret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(mt.autoret_early_hostdevice({}), CurrentTargetTy)
+
+  // The target attribute is too late to be considered:
+  ASSERT_HAS_TYPE(mt.ret_late_device({}), HostTy)
+  ASSERT_HAS_TYPE(mt.ret_late_host({}), HostTy)
+  ASSERT_HAS_TYPE(mt.ret_late_hostdevice({}), HostTy)
+
+  ASSERT_HAS_TYPE(mt.decl_ret_early_device(), DeviceTy)
+  ASSERT_HAS_TYPE(mt.decl_ret_early_host(), HostTy)
+  ASSERT_HAS_TYPE(mt.decl_ret_early_hostdevice(), CurrentTargetTy)
+
+  // The target attribute is too late to be considered:
+  ASSERT_HAS_TYPE(mt.decl_ret_late_device(), HostTy)
+  ASSERT_HAS_TYPE(mt.decl_ret_late_host(), HostTy)
+  ASSERT_HAS_TYPE(mt.decl_ret_late_hostdevice(), HostTy)
+
+  TemplateMethodTests<42> tmt;
+  ASSERT_HAS_TYPE(tmt.ret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(tmt.ret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(tmt.ret_early_hostdevice({}), CurrentTargetTy)
+
+  ASSERT_HAS_TYPE(tmt.autoret_early_device({}), DeviceTy)
+  ASSERT_HAS_TYPE(tmt.autoret_early_host({}), HostTy)
+  ASSERT_HAS_TYPE(tmt.autoret_early_hostdevice({}), CurrentTargetTy)
+
+  ASSERT_HAS_TYPE(tmt.ret_late_device({}), HostTy)
+  ASSERT_HAS_TYPE(tmt.ret_late_host({}), HostTy)
+  ASSERT_HAS_TYPE(tmt.ret_late_hostdevice({}), HostTy)
+
+  ASSERT_HAS_TYPE(tmt.decl_ret_early_device(), DeviceTy)
+  ASSERT_HAS_TYPE(tmt.decl_ret_early_host(), HostTy)
+  ASSERT_HAS_TYPE(tmt.decl_ret_early_hostdevice(), CurrentTargetTy)
+
+  ASSERT_HAS_TYPE(tmt.decl_ret_late_device(), HostTy)
+  ASSERT_HAS_TYPE(tmt.decl_ret_late_host(), HostTy)
+  ASSERT_HAS_TYPE(tmt.decl_ret_late_hostdevice(), HostTy)
+}
+
+
+// global variables:
+__attribute__((device)) targetdep_t var_early_device = {};
+ASSERT_HAS_TYPE(var_early_device, DeviceTy)
+
+targetdep_t var_early_host = {};
+ASSERT_HAS_TYPE(var_early_host, HostTy)
+
+targetdep_t __attribute__((device)) var_late_device = {}; // expected-warning {{target attribute has been ignored for overload resolution}}
+ASSERT_HAS_TYPE(var_late_device, HostTy)
+
+
+// Tests for the overload candidate ordering compared to templates:
+
+enum Candidate {
+  TEMPLATE,
+  HOST,
+  DEVICE,
+  HOSTDEVICE,
+};
+
+// (1.) If the overloaded functions are constexpr
+
+// (1.a) Prefer fitting overloads.
+template <typename T> constexpr Candidate ce_template_vs_H_D_functions(T arg) { return TEMPLATE; }
+__attribute__((device)) constexpr Candidate ce_template_vs_H_D_functions(float arg) { return DEVICE; }
+__attribute__((host)) constexpr Candidate ce_template_vs_H_D_functions(float arg) { return HOST; }
+
+__attribute__((device)) check<ce_template_vs_H_D_functions(1.0f) == DEVICE>::type
+test_ce_template_vs_H_D_functions_for_device() {
+  return TrueTy();
+}
+
+__attribute__((host)) check<ce_template_vs_H_D_functions(1.0f) == HOST>::type
+test_ce_template_vs_H_D_functions_for_host() {
+  return TrueTy();
+}
+
+__attribute__((host,device)) check<ce_template_vs_H_D_functions(1.0f) == CurrentTarget>::type
+test_ce_template_vs_H_D_functions_for_hd() {
+  return TrueTy();
+}
+
+
+// (1.b) Always prefer an HD candidate over a template candidate.
+template <typename T> constexpr Candidate ce_template_vs_HD_function(T arg) { return TEMPLATE; }
+__attribute__((host, device)) constexpr Candidate ce_template_vs_HD_function(float arg) { return HOSTDEVICE; }
+
+__attribute__((device)) check<ce_template_vs_HD_function(1.0f) == HOSTDEVICE>::type
+test_ce_template_vs_HD_function_for_device() {
+  return TrueTy();
+}
+
+__attribute__((host)) check<ce_template_vs_HD_function(1.0f) == HOSTDEVICE>::type
+test_ce_template_vs_HD_function_for_host() {
+  return TrueTy();
+}
+
+__attribute__((host,device)) check<ce_template_vs_HD_function(1.0f) == HOSTDEVICE>::type
+test_ce_template_vs_HD_function_for_hd() {
+  return TrueTy();
+}
+
+
+// (1.c) Even wrong-sided calls are okay if the called function is constexpr, so
+// prefer the device overload over the template.
+template <typename T> constexpr Candidate ce_template_vs_D_function(T arg) { return TEMPLATE; }
+__attribute__((device)) constexpr Candidate ce_template_vs_D_function(float arg) { return DEVICE; }
+
+__attribute__((host)) check<ce_template_vs_D_function(1.0f) == DEVICE>::type
+test_ce_template_vs_D_function_for_host() {
+  return TrueTy();
+}
+
+__attribute__((device)) check<ce_template_vs_D_function(1.0f) == DEVICE>::type
+test_ce_template_vs_D_function_for_device() {
+  return TrueTy();
+}
+
+__attribute__((host,device)) check<ce_template_vs_D_function(1.0f) == DEVICE>::type
+test_ce_template_vs_D_function_for_hd() {
+  return TrueTy();
+}
+
+
+// (2.) If the overloaded functions are NOT constexpr
+
+// (2.a) Prefer fitting overloads.
+template <typename T> TemplateTy template_vs_H_D_functions(T arg) { return {}; }
+__attribute__((device)) DeviceTy template_vs_H_D_functions(float arg) { return {}; }
+__attribute__((host)) HostTy template_vs_H_D_functions(float arg) { return {}; }
+
+__attribute__((device)) check<is_same<decltype(template_vs_H_D_functions(1.0f)), DeviceTy>::value>::type
+test_template_vs_H_D_functions_for_device() {
+  return TrueTy{};
+}
+
+__attribute__((host)) check<is_same<decltype(template_vs_H_D_functions(1.0f)), HostTy>::value>::type
+test_template_vs_H_D_functions_for_host() {
+  return TrueTy{};
+}
+
+__attribute__((host,device)) check<is_same<decltype(template_vs_H_D_functions(1.0f)), CurrentTargetTy>::value>::type
+test_template_vs_H_D_functions_for_hd() {
+  return TrueTy{};
+}
+
+// (2.b) Always prefer an HD candidate over a template candidate.
+template <typename T> TemplateTy template_vs_HD_function(T arg) { return {}; }
+__attribute__((host,device)) HostDeviceTy template_vs_HD_function(float arg) { return {}; }
+
+__attribute__((device)) check<is_same<decltype(template_vs_HD_function(1.0f)), HostDeviceTy>::value>::type
+test_template_vs_HD_function_for_device() {
+  return TrueTy{};
+}
+
+__attribute__((host)) check<is_same<decltype(template_vs_HD_function(1.0f)), HostDeviceTy>::value>::type
+test_template_vs_HD_function_for_host() {
+  return TrueTy{};
+}
+
+__attribute__((host,device)) check<is_same<decltype(template_vs_HD_function(1.0f)), HostDeviceTy>::value>::type
+test_template_vs_HD_function_for_hd() {
+  return TrueTy{};
+}
+
+
+// (2.c) For non-constexpr functions, prefer a sameside or native template
+// function over a wrongside non-template function:
+template <typename T> TemplateTy template_vs_D_function(T arg) { return {}; }
+__attribute__((device)) DeviceTy template_vs_D_function(float arg) { return {}; }
+
+__attribute__((host,device)) check<is_same<decltype(template_vs_D_function(1.0f)), TemplateIfHostTy>::value>::type
+test_template_vs_D_function_for_hd() {
+  return TrueTy{};
+}
+
+__attribute__((device)) check<is_same<decltype(template_vs_D_function(1.0f)), DeviceTy>::value>::type
+test_template_vs_D_function_for_device() {
+  return TrueTy{};
+}
+
+__attribute__((host)) check<is_same<decltype(template_vs_D_function(1.0f)), TemplateTy>::value>::type
+test_template_vs_D_function_for_host() {
+  return TrueTy{};
+}
+
+
+// If only a wrongside function is available, it is selected.
+__attribute__((device)) DeviceTy only_D_function(float arg) { return {}; }
+
+__attribute__((host)) check<is_same<decltype(only_D_function(1.0f)), DeviceTy>::value>::type
+test_only_D_function_for_host() {
+  return TrueTy{};
+}
+
+// Default arguments for template parameters occur before the target attribute,
+// so we can't identify the "right" overload for them.
+template <typename T = targetdep_t>
+__attribute__((device)) // expected-warning {{target attribute has been ignored for overload resolution}}
----------------
ritter-x2a wrote:

Thanks for the reply!

> e.g. deferring parsing of default template arguments, or looking ahead for template function host/device attributes before parsing the default template arguments?

I don't think that's possible. We would need to parse what comes after the default template arguments before resolving overloads in the default template arguments, but overload resolution can affect the parse tree of the default template arguments (see, e.g., [this example on godbolt.org](https://godbolt.org/z/1qWvvajGY)).
Looking through the code base, I found only [one instance of deferred parsing](https://github.com/llvm/llvm-project/blob/main/clang/lib/Parse/ParseDeclCXX.cpp#L3388), but as far as I can tell it only defers parsing of an initializer past the handling of its declarator, and does not switch the order in which things are parsed.

> Also, does this change pass internal PSDB?

The current state of the PR has passed PSDB without failures, but there are warnings that a target attribute has been ignored (the warning that this PR introduces) in rocPRIM, because of cases like [this default template argument](https://github.com/ROCm/rocPRIM/blob/100b3ea65b61edf5af0b7f3fb98b8670cce51c2a/rocprim/include/rocprim/block/block_load_func.hpp#L372C29-L372C45).
The result in the rocPRIM case should still be correct because the called constexpr function has only a device overload, so no host overload could be chosen instead.

As a side note: When experimenting with the above template-parameter-attribute syntax, I noticed that, in the current state of the PR, such an attribute in a template parameter (or any other sub-declaration) would affect the overload resolution of calls in the remaining declaration, outside of the template parameter where it occurs. That's probably not what we want.
I haven't pushed a fix for that yet because the fix relates to whether we decide to use the above template-parameter-attribute syntax.

https://github.com/llvm/llvm-project/pull/103031


More information about the cfe-commits mailing list