diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index 9a5c5d83226..9717c053d8c 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -343,6 +343,27 @@ struct Walker : public VisitorType { Module* currModule = nullptr; // current module being processed }; +// Define which expression classes are leaves. We can handle them more +// optimally below. The accuracy of this list is tested in leaves.cpp. +template struct IsLeaf : std::false_type {}; + +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; +template<> struct IsLeaf : std::true_type {}; + // Walks in post-order, i.e., children first. When there isn't an obvious // order to operands, we follow them in order of execution. @@ -369,6 +390,10 @@ struct PostWalker : public Walker { // Note that even if this ends up being a runtime check, it should be faster // than pushing empty tasks, as the check is much faster than the push/pop/ // call, and a large number of our calls (most, perhaps) are not overridden. + // + // If we do *not* have an empty visitor, we can still optimize in the case + // of a leaf: leaves have no children, so we can just call doVisit* rather + // than push that task, pop it later, and call that. #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 11 #define DELEGATE_START(id) \ if (&SubType::visit##id != \ @@ -377,17 +402,22 @@ struct PostWalker : public Walker { self->pushTask(SubType::doVisit##id, currp); \ } \ [[maybe_unused]] auto* cast = curr->cast(); -#else +#else // constexpr #define DELEGATE_START(id) \ if constexpr (&SubType::visit##id != \ &Visitor::visit##id || \ &SubType::doVisit##id != \ &Walker::doVisit##id) { \ + if constexpr (IsLeaf::value && \ + &SubType::scan == &PostWalker::scan) { \ + SubType::doVisit##id(self, currp); \ + return; \ + } \ self->pushTask(SubType::doVisit##id, currp); \ } \ [[maybe_unused]] auto* cast = curr->cast(); -#endif +#endif // constexpr #define DELEGATE_GET_FIELD(id, field) cast->field diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 058766f58e4..41d16f28e92 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -12,6 +12,7 @@ set(unittest_SOURCES dataflow.cpp dfa_minimization.cpp disjoint_sets.cpp + leaves.cpp glbs.cpp interpreter.cpp intervals.cpp diff --git a/test/gtest/leaves.cpp b/test/gtest/leaves.cpp new file mode 100644 index 00000000000..2457f0d35f7 --- /dev/null +++ b/test/gtest/leaves.cpp @@ -0,0 +1,73 @@ +#include "wasm-traversal.h" +#include "wasm.h" + +#include "gtest/gtest.h" + +using LeavesTest = ::testing::Test; + +using namespace wasm; + +TEST_F(LeavesTest, Manual) { + // Verify some interesting cases manually. + + // LocalGet is a leaf. + EXPECT_TRUE(IsLeaf::value); + // GlobalSet is not a leaf due to a child. + EXPECT_FALSE(IsLeaf::value); + // Return is not a leaf due to an optional child. + EXPECT_FALSE(IsLeaf::value); + // Call is not a leaf due to a vector of children. + EXPECT_FALSE(IsLeaf::value); +} + +TEST_F(LeavesTest, Automatic) { + // Verify them all automatically. + + // Count total expression classes and total with children. + size_t total = 0, totalWithChildren = 0; + +#define DELEGATE_FIELD_CASE_START(id) \ + { \ + bool hasChildren = false; + +#define DELEGATE_FIELD_CHILD(id, field) hasChildren = true; + +#define DELEGATE_FIELD_OPTIONAL_CHILD(id, field) hasChildren = true; + +#define DELEGATE_FIELD_CHILD_VECTOR(id, field) hasChildren = true; + + // Verify that IsLeaf has the right value. +#define DELEGATE_FIELD_CASE_END(id) \ + EXPECT_EQ(IsLeaf::value, !hasChildren); \ + total++; \ + if (hasChildren) { \ + totalWithChildren++; \ + } \ + } + +#define DELEGATE_FIELD_INT(id, field) +#define DELEGATE_FIELD_LITERAL(id, field) +#define DELEGATE_FIELD_NAME(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_DEF(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) +#define DELEGATE_FIELD_TYPE(id, field) +#define DELEGATE_FIELD_HEAPTYPE(id, field) +#define DELEGATE_FIELD_ADDRESS(id, field) +#define DELEGATE_FIELD_INT_ARRAY(id, field) +#define DELEGATE_FIELD_INT_VECTOR(id, field) +#define DELEGATE_FIELD_NAME_VECTOR(id, field) +#define DELEGATE_FIELD_NAME_USE_VECTOR(id, field) +#define DELEGATE_FIELD_TYPE_VECTOR(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(id, field) + +#define DELEGATE_FIELD_MAIN_START +#define DELEGATE_FIELD_MAIN_END + +#include "wasm-delegations-fields.def" + + // Not all have children (this just verifies the macros are actually doing + // something). + EXPECT_LT(totalWithChildren, total); + EXPECT_GT(totalWithChildren, 0); + EXPECT_GT(total, 0); +}