diff --git a/llvm/include/llvm/ADT/Sequence.h b/llvm/include/llvm/ADT/Sequence.h --- a/llvm/include/llvm/ADT/Sequence.h +++ b/llvm/include/llvm/ADT/Sequence.h @@ -18,12 +18,70 @@ #include // assert #include // std::random_access_iterator_tag #include // std::numeric_limits -#include // std::underlying_type, std::is_enum +#include // std::underlying_type, std::is_enum, std::false_type +#include "llvm/Support/Compiler.h" // LLVM_ATTRIBUTE_UNUSED #include "llvm/Support/MathExtras.h" // AddOverflow / SubOverflow namespace llvm { +template struct iterable_enum_traits { + static constexpr bool is_iterable = true; +}; + +template struct non_iterable_enum_traits { + static constexpr bool is_iterable = false; +}; + +// Wrapper type to represent an enum value and its traits. +template struct enum_with_traits { + Enum value; + static_assert(std::is_enum::value, "Not an enum type"); + using enum_type = Enum; + using underlying_type = std::underlying_type_t; + using traits_type = Traits; +}; + +namespace detail { +// We cannot use std::false directly because it would make a static_assert in a +// function template trigger even when not instanciated. This templated version +// only evaluates to false when actually instantiated in a function. +// FIXME: Move this to STLExtras.h and replace similar utilities in the +// codebase with it. +template struct always_false : std::false_type {}; +} // namespace detail + +// Default implementation of (unimplemented) add_enum_traits. This should the +// worst overload that, when selected, produces a compilation error. +template auto add_enum_traits(EnumT value, ...) { + static_assert(std::is_enum::value, "Not an enum type"); + static_assert(detail::always_false::value, + "add_enum_triats not defined for this enum type"); +} + +template > +enum_with_traits forge_iterable_enum(EnumT value) { + return {value}; +} + +// Use to declare ENUM as safe to iterate over. This can be used for enums +// declared in namespace scope and inside class/struct. +// We mark it as potentially unused to silence false positives. +#define DECLARE_ITERABLE_ENUM(ENUM) \ + inline auto LLVM_ATTRIBUTE_UNUSED add_enum_traits(ENUM value) { \ + return llvm::forge_iterable_enum(value); \ + } + +// Use to declare ENUM as **unsafe** to iterate over. This can be used for enums +// declared in namespace scope and inside class/struct. +// We mark it as potentially unused to silence false positives. +#define DECLARE_NON_ITERABLE_ENUM(ENUM) \ + inline auto LLVM_ATTRIBUTE_UNUSED add_enum_traits(ENUM value) { \ + return llvm::forge_iterable_enum>( \ + value); \ + } + namespace detail { // Returns whether a value of type U can be represented with type T. @@ -56,7 +114,7 @@ template ::value, bool> = 0> static CheckedInt from(Enum FromValue) { - using type = typename std::underlying_type::type; + using type = typename std::underlying_type_t; return from(static_cast(FromValue)); } @@ -92,7 +150,7 @@ template ::value, bool> = 0> Enum to() const { - using type = typename std::underlying_type::type; + using type = std::underlying_type_t; return Enum(to()); } @@ -174,9 +232,7 @@ template friend struct SafeIntIterator; }; -} // namespace detail - -template struct iota_range { +template struct iota_range_impl { using value_type = T; using reference = T &; using const_reference = const T &; @@ -187,7 +243,7 @@ using difference_type = intmax_t; using size_type = std::size_t; - explicit iota_range(T Begin, T End, bool Inclusive) + explicit iota_range_impl(T Begin, T End, bool Inclusive) : BeginValue(Begin), PastEndValue(End) { assert(Begin <= End && "Begin must be less or equal to End."); if (Inclusive) @@ -206,34 +262,82 @@ private: static_assert(std::is_integral::value || std::is_enum::value, "T must be an integral or enum type"); - static_assert(std::is_same>::value, + static_assert(std::is_same>::value, "T must not be const nor volatile"); iterator BeginValue; iterator PastEndValue; }; +} // namespace detail + +template struct iota_range; + +// iota_range specialization for integral types. +template +struct iota_range::value>> + : detail::iota_range_impl { + using detail::iota_range_impl::iota_range_impl; +}; + +// iota_range specialization for enum types. The constructor checks +// the traits to make sure it is safe to iterate over this enum type. +template +struct iota_range::value>> + : detail::iota_range_impl { + explicit iota_range(Enum Begin, Enum End, bool Inclusive) + : detail::iota_range_impl(Begin, End, Inclusive) { + auto WithTraits = add_enum_traits(Begin); + (void)WithTraits; + using traits = typename decltype(WithTraits)::traits_type; + static_assert( + traits::is_iterable, + "Enum not iterable. Use DECLARE_ITERABLE_ENUM or override this " + "safety check with forge_iterable_enum."); + } +}; + +// iota_range specialization for enum_with_traits types. The constructor checks +// the traits to make sure it is safe to iterate over this enum type. +// This specialization serves as an escape hatch to iterate over enum types not +// marked with DECLARE_ITERABLE_ENUM, for example, by using forge_iterable_enum. +template +struct iota_range, + std::enable_if_t::value>> + : detail::iota_range_impl { + explicit iota_range(enum_with_traits Begin, + enum_with_traits End, bool Inclusive) + : detail::iota_range_impl(Begin.value, End.value, Inclusive) { + static_assert( + Traits::is_iterable, + "Enum not iterable. Use DECLARE_ITERABLE_ENUM or override this " + "safety check with forge_iterable_enum."); + }; +}; + /// Iterate over an integral/enum type from Begin up to - but not including - /// End. -/// Note on enum iteration: `seq` will generate each consecutive value, even if -/// no enumerator with that value exists. /// Note: Begin and End values have to be within [INTMAX_MIN, INTMAX_MAX] for /// forward iteration (resp. [INTMAX_MIN + 1, INTMAX_MAX] for reverse /// iteration). +/// Note on enum iteration: by default, `seq` treats enum types as non-iterable. +/// To mark an enum as safe for iteration, use `DECLARE_ITERABLE_ENUM`, or +/// implement `add_enum_traits`, or wrap enum values with `forge_iterable_enum`. template auto seq(T Begin, T End) { return iota_range(Begin, End, false); } /// Iterate over an integral/enum type from Begin to End inclusive. -/// Note on enum iteration: `seq_inclusive` will generate each consecutive -/// value, even if no enumerator with that value exists. /// Note: Begin and End values have to be within [INTMAX_MIN, INTMAX_MAX - 1] /// for forward iteration (resp. [INTMAX_MIN + 1, INTMAX_MAX - 1] for reverse /// iteration). +/// Note on enum iteration: by default, `seq_inclusive` treats enum types as +/// non-iterable. To mark an enum as safe for iteration, use +/// `DECLARE_ITERABLE_ENUM`, or implement `add_enum_traits`, or wrap enum values +/// with `forge_iterable_enum`. template auto seq_inclusive(T Begin, T End) { return iota_range(Begin, End, true); } - } // end namespace llvm #endif // LLVM_ADT_SEQUENCE_H diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h --- a/llvm/include/llvm/IR/InstrTypes.h +++ b/llvm/include/llvm/IR/InstrTypes.h @@ -19,6 +19,7 @@ #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Sequence.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" @@ -2367,6 +2368,8 @@ DEFINE_TRANSPARENT_OPERAND_ACCESSORS(FuncletPadInst, Value) +DECLARE_ITERABLE_ENUM(CmpInst::Predicate) + } // end namespace llvm #endif // LLVM_IR_INSTRTYPES_H diff --git a/llvm/include/llvm/Support/MachineValueType.h b/llvm/include/llvm/Support/MachineValueType.h --- a/llvm/include/llvm/Support/MachineValueType.h +++ b/llvm/include/llvm/Support/MachineValueType.h @@ -1402,9 +1402,11 @@ static MVT getVT(Type *Ty, bool HandleUnknown = false); public: - /// SimpleValueType Iteration - /// @{ - static auto all_valuetypes() { + friend DECLARE_ITERABLE_ENUM(MVT::SimpleValueType) + + /// SimpleValueType Iteration + /// @{ + static auto all_valuetypes() { return seq_inclusive(MVT::FIRST_VALUETYPE, MVT::LAST_VALUETYPE); } diff --git a/llvm/tools/llvm-exegesis/lib/X86/Target.cpp b/llvm/tools/llvm-exegesis/lib/X86/Target.cpp --- a/llvm/tools/llvm-exegesis/lib/X86/Target.cpp +++ b/llvm/tools/llvm-exegesis/lib/X86/Target.cpp @@ -33,6 +33,9 @@ #endif namespace llvm { + +// DECLARE_ITERABLE_ENUM(X86::CondCode); + namespace exegesis { static cl::OptionCategory @@ -619,13 +622,13 @@ public: X86SavedState() { #ifdef __x86_64__ -# if defined(_MSC_VER) +#if defined(_MSC_VER) _fxsave64(FPState); Eflags = __readeflags(); -# elif defined(__GNUC__) +#elif defined(__GNUC__) __builtin_ia32_fxsave64(FPState); Eflags = __builtin_ia32_readeflags_u64(); -# endif +#endif #else llvm_unreachable("X86 exegesis running on non-X86 target"); #endif @@ -635,15 +638,15 @@ // Restoring the X87 state does not flush pending exceptions, make sure // these exceptions are flushed now. #ifdef __x86_64__ -# if defined(_MSC_VER) +#if defined(_MSC_VER) _clearfp(); _fxrstor64(FPState); __writeeflags(Eflags); -# elif defined(__GNUC__) +#elif defined(__GNUC__) asm volatile("fwait"); __builtin_ia32_fxrstor64(FPState); __builtin_ia32_writeeflags_u64(Eflags); -# endif +#endif #else llvm_unreachable("X86 exegesis running on non-X86 target"); #endif @@ -919,7 +922,8 @@ case X86::OperandType::OPERAND_COND_CODE: { Exploration = true; auto CondCodes = - seq_inclusive(X86::CondCode::COND_O, X86::CondCode::LAST_VALID_COND); + seq_inclusive(forge_iterable_enum(X86::CondCode::COND_O), + forge_iterable_enum(X86::CondCode::LAST_VALID_COND)); Choices.reserve(CondCodes.size()); for (int CondCode : CondCodes) Choices.emplace_back(MCOperand::createImm(CondCode)); diff --git a/llvm/unittests/ADT/SequenceTest.cpp b/llvm/unittests/ADT/SequenceTest.cpp --- a/llvm/unittests/ADT/SequenceTest.cpp +++ b/llvm/unittests/ADT/SequenceTest.cpp @@ -68,17 +68,6 @@ EXPECT_EQ(Actual - (Actual + 2), -2); } -TEST(StrongIntTest, Enums) { - enum UntypedEnum { A = 3 }; - EXPECT_EQ(CheckedInt::from(A).to(), A); - - enum TypedEnum : uint32_t { B = 3 }; - EXPECT_EQ(CheckedInt::from(B).to(), B); - - enum class ScopedEnum : uint16_t { C = 3 }; - EXPECT_EQ(CheckedInt::from(ScopedEnum::C).to(), ScopedEnum::C); -} - #if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) TEST(StrongIntDeathTest, OutOfBounds) { // Values above 'INTMAX_MAX' are not representable. @@ -215,4 +204,78 @@ EXPECT_EQ(Backward[2], 7); } -} // anonymous namespace +enum UntypedEnum { A = 3 }; +enum TypedEnum : uint32_t { B = 3 }; + +DECLARE_ITERABLE_ENUM(UntypedEnum) +DECLARE_ITERABLE_ENUM(TypedEnum) + +namespace X { +enum class ScopedEnum : uint16_t { C = 3 }; +DECLARE_ITERABLE_ENUM(X::ScopedEnum) +} // namespace X + +TEST(StrongIntTest, Enums) { + EXPECT_EQ(CheckedInt::from(A).to(), A); + EXPECT_EQ(CheckedInt::from(B).to(), B); + EXPECT_EQ(CheckedInt::from(X::ScopedEnum::C).to(), + X::ScopedEnum::C); +} + +struct S { + enum NestedEnum { D = 4 }; + enum NestedEnum2 { E = 5 }; + enum NestedEnum3 { F = 6 }; + + // clang-format off + friend DECLARE_ITERABLE_ENUM(NestedEnum) + friend DECLARE_NON_ITERABLE_ENUM(NestedEnum2) + // clang-format on +}; + +TEST(SequenceTest, IterableEnums) { + EXPECT_TRUE(llvm::empty( + llvm::iota_range(UntypedEnum::A, UntypedEnum::A, false))); + EXPECT_THAT( + llvm::iota_range(UntypedEnum::A, UntypedEnum::A, true), + ElementsAre(UntypedEnum::A)); + + EXPECT_TRUE(llvm::empty( + llvm::iota_range(TypedEnum::B, TypedEnum::B, false))); + EXPECT_THAT(llvm::iota_range(TypedEnum::B, TypedEnum::B, true), + ElementsAre(TypedEnum::B)); + + EXPECT_TRUE(llvm::empty(llvm::iota_range( + X::ScopedEnum::C, X::ScopedEnum::C, false))); + EXPECT_THAT( + llvm::iota_range(X::ScopedEnum::C, X::ScopedEnum::C, true), + ElementsAre(X::ScopedEnum::C)); + + EXPECT_THAT(seq_inclusive(X::ScopedEnum::C, X::ScopedEnum::C), + ElementsAre(X::ScopedEnum::C)); + EXPECT_THAT(seq_inclusive(S::NestedEnum::D, S::NestedEnum::D), + ElementsAre(S::NestedEnum::D)); +} + +TEST(SequenceTest, ForgeIterableEnums) { + EXPECT_TRUE(llvm::empty(seq(forge_iterable_enum(S::NestedEnum2::E), + forge_iterable_enum(S::NestedEnum2::E)))); + EXPECT_THAT(seq_inclusive(forge_iterable_enum(S::NestedEnum2::E), + forge_iterable_enum(S::NestedEnum2::E)), + ElementsAre(S::NestedEnum2::E)); + + EXPECT_TRUE(llvm::empty(seq(forge_iterable_enum(S::NestedEnum3::F), + forge_iterable_enum(S::NestedEnum3::F)))); + EXPECT_THAT(seq_inclusive(forge_iterable_enum(S::NestedEnum3::F), + forge_iterable_enum(S::NestedEnum3::F)), + ElementsAre(S::NestedEnum3::F)); + + auto ValueWithTraits = forge_iterable_enum(S::NestedEnum3::F); + EXPECT_TRUE(llvm::empty(llvm::iota_range( + ValueWithTraits, ValueWithTraits, false))); + EXPECT_THAT(llvm::iota_range( + ValueWithTraits, ValueWithTraits, true), + ElementsAre(S::NestedEnum3::F)); +} + +} // namespace