diff --git a/docs/C++.md b/docs/C++.md index e720d56a05..bee36272a4 100644 --- a/docs/C++.md +++ b/docs/C++.md @@ -17,7 +17,7 @@ we use a nested class with a reference member to the containing class called `self`. ### RTTI -IR nodes and their handling code (e.g. Visitors) make extensive use of RTTI to perform type-checking and corresponding downcasting from `Node*` down to a particular implementation. C++ native RTTI as implemented in `dynamic_cast` and `typeid` built-ins is inherently slow and has lots of overhead. To circumvent this IR node classes use dedicated light-weight RTTI designed for semi-open class hierarchies. The implementation itself requires some boilerplate that is automatically generated by `ir-generator` for all IR classes from `.def`. The corresponding RTTI functionality could be accessed via `ICastable` interface from `lib/castable.h`. The lower-level RTTI implementation details are in `lib/rtti.h`. Overall, use of `dynamic_cast` and `typeid` for IR nodes is discouraged. +IR nodes and their handling code (e.g. Visitors) make extensive use of RTTI to perform type-checking and corresponding downcasting from `Node*` down to a particular implementation. C++ native RTTI as implemented in `dynamic_cast` and `typeid` built-ins is inherently slow and has lots of overhead. To circumvent this IR node classes use dedicated light-weight RTTI designed for semi-open class hierarchies. The implementation itself requires some boilerplate that is automatically generated by `ir-generator` for all IR classes from `.def`. The corresponding RTTI functionality could be accessed via `ICastable` interface from `lib/castable.h`. There are also freestandig helper functions and type traits in `lib/rtti_utils.h`, these helpers are mainly useful with generic algorithms and ranges. The lower-level RTTI implementation details are in `lib/rtti.h`. Overall, use of `dynamic_cast` and `typeid` for IR nodes is discouraged. #### Using lightweight RTTI for other class hierarchies Other class hierarchies besides `IR::Node` descendants could also benefit from lightweight RTTI functionality. In order to use it one needs to annotate the intended class hierarchy as follows: diff --git a/lib/rtti_utils.h b/lib/rtti_utils.h new file mode 100644 index 0000000000..84fa33a5fd --- /dev/null +++ b/lib/rtti_utils.h @@ -0,0 +1,147 @@ +/* +Copyright 2024-present Intel Corporation. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/// @file +/// @brief Utilities that help with use of custom-RTTI-enabled classes. + +#ifndef LIB_RTTI_UTILS_H_ +#define LIB_RTTI_UTILS_H_ + +#include + +#include "rtti.h" + +namespace RTTI { + +/// A trait that check T is custom-RTTI-enabled. Works just like standard property type traits. +/// One would normally use the _v variant. +/// NOTE: Custom-RTTI-enabled classes should not only derive from RTTI::Base, but should also +/// declare typeId properly. However, not doing so would be a bug and use of such class in +/// RTTI-related operations would lead to static_assert failuire in RTTI::TypeInfo, so we just check +/// the base. +template +struct has_rtti : std::is_base_of {}; + +/// A trait that check T is custom-RTTI-enabled, variable version. Works just like standard property +/// type traits. +template +inline constexpr const bool has_rtti_v = has_rtti::value; + +/// A type trait that check that all the types are custom-RTTI-enabled. +template +inline constexpr const bool all_have_rtti_v = (has_rtti_v && ...); + +/// A type condition for checking that T is custom-RTTI-enabled. A specialization of std::enable_if +/// and can be used in the same way. +/// @tparam T The type to be checked. +/// @tparam R The type that will be used for the ::type member if T is custom-rtti-enabled. +/// Defaults to void. +template +struct enable_if_has_rtti : std::enable_if, R> {}; + +/// A type condition for checking that all the types have RTTI. The resulting type is either not +/// defined if some of the types does not have RTTI (so a SFINAE use will fail), or it is defined to +/// be void. If you need another result type, please use std::enable_if, T>. +template +using enable_if_all_have_rtti_t = std::enable_if_t, void>; + +/// A type condition for checking that T is custom-RTTI-enabled. A specialization of +/// std::enable_if_t and can be used in the same way. +/// @tparam T The type to be checked. +/// @tparam R The type that will be used for the ::type member if T is custom-rtti-enabled. +/// Defaults to void. +template +using enable_if_has_rtti_t = typename enable_if_has_rtti::type; + +namespace Detail { + +template > +struct ToType { + template > + To *operator()(From *obj) const { + return obj ? obj->template to() : nullptr; + } + + template > + const To *operator()(const From *obj) const { + return obj ? obj->template to() : nullptr; + } +}; + +template +// TODO(C++20): use concepts to check enable_if_all_have_rtti_t> & that there is at +// least 1 target type. +struct IsType { + static_assert(sizeof...(Targets) > 0, + "At least one target type needs to be given for RTTI::is"); + static_assert(all_have_rtti_v, + "All types in RTTI::is need to be custom-rtti-enabled"); + + template > + bool operator()(const From *obj) const { + return obj && (obj->template is() || ...); + } +}; + +} // namespace Detail + +/// A freestanding wrapper over From::to(). It is an object, but is intended to be used a +/// function (similarly to C++20 range-implementing objects). Can be passed to functions expecting a +/// callable. +/// The callable has following signatures: +/// template +/// * To *(From *) +/// * const To *(const From *) +/// +/// Examples: +/// * RTTI::to(n) +/// * std::copy_if(a.begin(), a.end(), std::back_inserter(b), RTTI::to); +/// +/// @tparam To The target type to cast to. +/// @returns nullptr if the from value is null or not of the To type, otherwise from cast to +/// To-type. +template +inline const Detail::ToType to; + +/// A freestanding wrapper over From::is(). Can be used to check if the value is of the +/// given type. A callable object with behaving as if it had the following signature: +/// template +/// bool (From *) +/// +/// Examples: +/// * if (RTTI::is(typeOrNull)) { ... } +/// * std::find(x.begin(), x.end(), RTTI::is); +/// +/// @tparam Target The target type, from is checked to be of this type. +/// @returns true if the from object is not null and is of the target type, false otherwise. +template +inline const Detail::IsType is; + +/// Similar to @ref RTTI::is, but accept accepts multiple types and succeeds if any of them matches +/// the type of the object. +/// +/// Examples: +/// * if (RTTI::isAny(typeOrNull)) { ... } +/// * std::find(x.begin(), x.end(), RTTI::isAny); +/// +/// @tparam Targets The target type, from is checked to be of one of these types. +/// @returns true if the from object is not null and is of any of the target types, false otherwise. +template +inline const Detail::IsType isAny; + +} // namespace RTTI + +#endif // LIB_RTTI_UTILS_H_ diff --git a/test/gtest/rtti_test.cpp b/test/gtest/rtti_test.cpp index 57e535052e..96e161a7a2 100644 --- a/test/gtest/rtti_test.cpp +++ b/test/gtest/rtti_test.cpp @@ -20,6 +20,7 @@ limitations under the License. #include "ir/json_loader.h" #include "ir/node.h" #include "ir/vector.h" +#include "lib/rtti_utils.h" namespace Test { @@ -100,4 +101,73 @@ TEST(RTTI, JsonRestore) { EXPECT_EQ(e2->typeId(), IR::NodeKind::Add); } +TEST(RttiUtils, to) { + const auto *c = IR::Constant::get(IR::Type::Bits::get(4), 2); + const IR::Node *n = c; + const IR::Node *nullNode = nullptr; + + EXPECT_EQ(RTTI::to(n), c); + EXPECT_EQ(RTTI::to(c), c); + EXPECT_EQ(RTTI::to(c), nullptr); + EXPECT_EQ(RTTI::to(nullNode), nullptr); + + EXPECT_EQ(RTTI::to(n), n->to()); + EXPECT_EQ(RTTI::to(c->type), nullptr); + + std::vector from{c, new IR::Add(c, c), n, new IR::LNot(c)}; + std::vector to; + std::transform(from.begin(), from.end(), std::back_inserter(to), RTTI::to); + + ASSERT_EQ(to.size(), 4); + EXPECT_EQ(to[0], nullptr); + ASSERT_NE(to[1], nullptr); + EXPECT_TRUE(to[1]->is()); + EXPECT_EQ(to[2], nullptr); + ASSERT_NE(to[3], nullptr); + EXPECT_TRUE(to[3]->is()); +} + +TEST(RttiUtils, is) { + const auto *c = IR::Constant::get(IR::Type::Bits::get(4), 2); + const IR::Node *n = c; + const IR::Node *nullNode = nullptr; + + EXPECT_TRUE(RTTI::is(n)); + EXPECT_TRUE(RTTI::is(c)); + EXPECT_FALSE(RTTI::is(c)); + EXPECT_FALSE(RTTI::is(c)); + EXPECT_FALSE(RTTI::is(nullNode)); + + std::vector from{c, new IR::Add(c, c), n, new IR::LNot(c)}; + auto it = std::find_if(from.begin(), from.end(), RTTI::is); + + EXPECT_NE(it, from.end()); + EXPECT_EQ(it, std::prev(from.end())); + EXPECT_EQ(*it, from[3]); +} + +TEST(RttiUtils, isAny) { + const auto *c = IR::Constant::get(IR::Type::Bits::get(4), 2); + const IR::Node *n = c; + const IR::Node *nullNode = nullptr; + + EXPECT_TRUE(RTTI::isAny(n)); + EXPECT_TRUE(RTTI::isAny(c)); + EXPECT_FALSE(RTTI::isAny(c)); + EXPECT_FALSE(RTTI::isAny(c)); + EXPECT_FALSE(RTTI::isAny(nullNode)); + + EXPECT_TRUE((RTTI::isAny(n))); + // EXPECT_TRUE(RTTI::isAny<>(n)); // does not compile, with is right + EXPECT_FALSE((RTTI::isAny(c))); + + std::vector from{c, new IR::Add(c, c), n, new IR::LNot(c)}; + auto it = std::find_if(from.begin(), from.end(), + RTTI::isAny); + + EXPECT_NE(it, from.end()); + EXPECT_EQ(it, std::next(from.begin())); + EXPECT_EQ(*it, from[1]); +} + } // namespace Test