Skip to content
This repository was archived by the owner on Mar 28, 2023. It is now read-only.

Commit 2ace807

Browse files
authored
[SYCL][ESIMD] Enhance enum logging support (#917)
* [SYCL][ESIMD] Add `value_pack::generate_named_by()` It's usefull to have for value packs with enum type * [SYCL][ESIMD] Add `log::StringMaker<>` for specialization We should avoid function templates specialization in general * [SYCL][ESIMD] Use `StringMaker` in `value_pack` generation By using `log::stringify()` call instead of the hard-coded `std::to_string()` calls we enable the `StringMaker` usage with all benefits of any custom specialization provided. Signed-off-by: Kochetkov, Yuriy <[email protected]>
1 parent d944065 commit 2ace807

File tree

2 files changed

+78
-33
lines changed

2 files changed

+78
-33
lines changed

SYCL/ESIMD/api/functional/logger.hpp

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,34 +62,55 @@ template <> struct fp_hex_representation<double> : support_flag<true> {
6262

6363
} // namespace detail
6464

65-
// Provides generic stringification for logging purposes
66-
template <typename T> static std::string stringify(T val) {
67-
if constexpr (std::is_convertible_v<T, std::string>) {
68-
return val;
69-
} else if constexpr (type_traits::is_sycl_floating_point_v<T>) {
70-
// Define the output precision based on the type precision itself
71-
// For example, state 9 decimal digits for 32-bit floating point
72-
const auto significand_decimal_digits = sizeof(T) * 2 + 1;
73-
74-
std::ostringstream out;
75-
out.precision(significand_decimal_digits);
76-
out << val << " [";
77-
if constexpr (detail::fp_hex_representation<T>::is_supported()) {
78-
// Print out hex representation using type-punning
79-
using hex_type = typename detail::fp_hex_representation<T>::type;
80-
const auto &representation = reinterpret_cast<const hex_type &>(val);
81-
out << "0x" << std::hex << representation;
65+
// Provides a stringification helper for safe specialization
66+
// Benefits:
67+
// - makes partial specialization possible
68+
// - avoids unexpected behaviour for function specializations and overloads
69+
template <typename T> struct StringMaker {
70+
static std::string stringify(T val) {
71+
if constexpr (std::is_convertible_v<T, std::string>) {
72+
return val;
73+
} else if constexpr (type_traits::is_sycl_floating_point_v<T>) {
74+
// Define the output precision based on the type precision itself
75+
// For example, state 9 decimal digits for 32-bit floating point
76+
const auto significand_decimal_digits = sizeof(T) * 2 + 1;
77+
78+
std::ostringstream out;
79+
out.precision(significand_decimal_digits);
80+
out << val << " [";
81+
if constexpr (detail::fp_hex_representation<T>::is_supported()) {
82+
// Print out hex representation using type-punning
83+
using hex_type = typename detail::fp_hex_representation<T>::type;
84+
const auto &representation = reinterpret_cast<const hex_type &>(val);
85+
out << "0x" << std::hex << representation;
86+
} else {
87+
// Make support gap explicit to address if required
88+
out << " - ";
89+
}
90+
out << "]";
91+
return out.str();
92+
} else if constexpr (std::is_enum_v<T>) {
93+
// Any enumeration requires explicit cast to the underlying type
94+
// Please note that StringMaker can be safely specialized for any
95+
// enumeration type for more human-readable logs if required
96+
return std::to_string(static_cast<std::underlying_type_t<T>>(val));
8297
} else {
83-
// Make support gap explicit to address if required
84-
out << " - ";
98+
return std::to_string(val);
8599
}
86-
out << "]";
87-
return out.str();
88-
} else {
89-
return std::to_string(val);
90100
}
101+
};
102+
103+
// Provides generic stringification for logging purposes
104+
// To tweak for specific type, please:
105+
// - either provide overload,
106+
// - or specialize the StringMaker for this type
107+
template <typename T> static std::string stringify(T val) {
108+
return log::StringMaker<T>::stringify(val);
91109
}
92110

111+
// Overload to improve performance a bit; works as the first-class citizen
112+
inline const std::string &stringify(const std::string &val) { return val; }
113+
93114
// Print line to the log using a custom set of parameters
94115
//
95116
// Lambda can be used to print out any custom information,
@@ -138,7 +159,7 @@ template <typename... argsT> inline void print_line(const argsT &...args) {
138159
// logging disabled
139160
template <typename... argsT> inline void debug(const argsT &...args) {
140161
#ifdef ESIMD_TESTS_VERBOSE_LOG
141-
print_line(args);
162+
print_line(args...);
142163
#else
143164
// Suppress unused variables warning
144165
(static_cast<void>(args), ...);

SYCL/ESIMD/api/functional/type_coverage.hpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#pragma once
1515

16+
#include "logger.hpp"
1617
#include "type_traits.hpp"
1718

1819
#include <cassert>
@@ -103,16 +104,14 @@ template <typename T, T... values> struct value_pack {
103104
}
104105

105106
// Factory function to generate the type pack with stringified values stored
106-
// within
107+
// within. Would work with or without specific StringMaker specializations.
108+
// For example:
109+
// const auto targets =
110+
// value_pack<sycl::target, sycl::target::device>::generate_named();
111+
//
107112
static inline auto generate_named() {
108-
if constexpr (std::is_enum_v<T>) {
109-
// Any enumeration requires explicit cast to the underlying type
110-
return named_type_pack<std::integral_constant<T, values>...>::generate(
111-
std::to_string(static_cast<std::underlying_type_t<T>>(values))...);
112-
} else {
113-
return named_type_pack<std::integral_constant<T, values>...>::generate(
114-
std::to_string(values)...);
115-
}
113+
return named_type_pack<std::integral_constant<T, values>...>::generate(
114+
log::stringify(values)...);
116115
}
117116

118117
// Factory function to generate the type pack with names given for each value
@@ -131,6 +130,31 @@ template <typename T, T... values> struct value_pack {
131130
return named_type_pack<std::integral_constant<T, values>...>::generate(
132131
std::forward<argsT>(args)...);
133132
}
133+
134+
// Factory function to generate the type pack using generator function
135+
//
136+
// It could be especially usefull for value packs with enums. For example:
137+
// enum class access {read, write};
138+
// template <access ... values>
139+
// using access_pack = value_pack<access, values...>;
140+
//
141+
// const auto generator = [&](const access& value) {
142+
// switch (value) {
143+
// case access:read:
144+
// return "read access";
145+
// break;
146+
// ...
147+
// };
148+
// };
149+
// const auto accesses =
150+
// access_pack<access::read, access::write>::generate_named_by(generator);
151+
//
152+
template <typename GeneratorT>
153+
static auto generate_named_by(const GeneratorT &nameGenerator) {
154+
static_assert(std::is_invocable_v<GeneratorT, T>,
155+
"Unexpected name generator type");
156+
return generate_named(nameGenerator(values)...);
157+
}
134158
};
135159

136160
// Alias to use mainly for simd vector sizes; no overhead as alias doesn't

0 commit comments

Comments
 (0)