/* * Copyright 2020 WebAssembly Community Group participants * * 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. */ // // match.h - Provides an easily extensible layered API for matching expression // patterns and extracting their components. The low-level API provides modular // building blocks for creating matchers for any data type and the high-level // API provides a succinct and flexible interface for matching expressions and // extracting useful information from them. #ifndef wasm_ir_match_h #define wasm_ir_match_h #include "ir/abstract.h" #include "wasm.h" namespace wasm { namespace Match { // The available matchers are: // // i32, i64, f32, f64 // // Match constants of the corresponding type. Takes zero or one argument. The // argument can be a specific value to match or it can be a pointer to a // value, Literal, or Const* at which to store the matched entity. // // ival, fval // // Match any integer constant or any floating point constant. Takes neither, // either, or both of two possible arguments: first, a pointer to a value, // Literal, or Const* at which to store the matched entity and second, a // specific value to match. // // constant // // Matches any numeric Const expression. Takes neither, either, or both of // two possible arguments: first, a pointer to either Literal or Const* at // which to store the matched entity and second, a specific value (given as // an int32_t) to match.. // // any // // Matches any Expression. Optionally takes as an argument a pointer to // Expression* at which to store the matched Expression*. // // unary // // Matches Unary expressions. Takes an optional pointer to Unary* at which to // store the matched Unary*, followed by either a UnaryOp or an Abstract::Op // describing which unary expressions to match, followed by a matcher to // apply to the unary expression's operand. // // binary // // Matches Binary expressions. Takes an optional pointer to Binary* at which // to store the matched Binary*, followed by either a BinaryOp or an // Abstract::Op describing which binary expresions to match, followed by // matchers to apply to the binary expression's left and right operands. // // select // // Matches Select expressions. Takes an optional pointer to Select* at which // to store the matched Select*, followed by matchers to apply to the ifTrue, // ifFalse, and condition operands. // // // How to create new matchers: // // Lets add a matcher for an expression type that is declared in wasm.h: // // class Frozzle : public SpecificExpression { // public: // Expression* foo; // Expression* bar; // Expression* baz; // }; // // This expression is very simple; in order to match it, all we need to do is // apply other matchers to its subexpressions. The matcher infrastructure will // handle this automatically once we tell it how to access the subexpressions. // To tell the matcher infrastructure how many subexpressions there are we need // to specialize `NumComponents`. // // template<> struct NumComponents { // static constexpr size_t value = 3; // }; // // And to tell the matcher infrastructure how to access those three // subexpressions, we need to specialize `GetComponent` three times. // // template<> struct GetComponent { // Expression* operator()(Frozzle* curr) { return curr->foo; } // }; // template<> struct GetComponent { // Expression* operator()(Frozzle* curr) { return curr->bar; } // }; // template<> struct GetComponent { // Expression* operator()(Frozzle* curr) { return curr->baz; } // }; // // For simple expressions, that's all we need to do to get a fully functional // matcher that we can construct and use like this, where S1, S2, and S3 are // the types of the submatchers to use and s1, s2, and s3 are instances of // those types: // // Frozzle* extracted; // auto matcher = Matcher(&extracted, {}, s1, s2, s3); // if (matches(expr, matcher)) { // // `extracted` set to `expr` here // } // // It's annoying to have to write out the types S1, S2, and S3 and we don't get // class template argument deduction (CTAD) until C++17, so it's useful to // create a wrapper function so can take advantage of function template // argument deduction. We can also take this opportunity to make the interface // more compact. // // template // inline decltype(auto) frozzle(Frozzle** binder, // S1&& s1, S2&& s2, S3&& s3) { // return Matcher(binder, {}, s1, s2, s3); // } // template // inline decltype(auto) frozzle(S1&& s1, S2&& s2, S3&& s3) { // return Matcher(nullptr, {}, s1, s2, s3); // } // // Notice that we make the interface more compact by providing overloads with // and without the binder. Here is the final matcher usage: // // Frozzle* extracted; // if (matches(expr, frozzle(&extracted, s1, s2, s3))) { // // `extracted` set to `expr` here // } // // Some matchers are more complicated, though, because they need to do // something besides just applying submatchers to the components of an // expression. These matchers require slightly more work. // // // Complex matchers: // // Lets add a matcher that will match calls to functions whose names start with // certain prefixes. Since this is not a normal matcher for Call expressions, // we can't identify it by the Call* type. Instead, we have to create a new // identifier type, called a "Kind" for it. // // struct PrefixCallKind {}; // // Next, since we're not in the common case of using a specific expression // pointer as our kind, we have to tell the matcher infrastructure what type of // thing this matcher matches. Since we want this matcher to be able to match // any given prefix, we also need the matcher to contain the given prefix as // state, and we need to tell the matcher infrastructure what type that state // is as well. To specify these types, we need to specialize // `KindTypeRegistry` for `PrefixCallKind`. // // template<> struct KindTypeRegistry { // using matched_t = Call*; // using data_t = Name; // }; // // Note that because `matched_t` is set to a specific expression pointer, this // matcher will automatically be able to be applied to any `Expression*`, not // just `Call*`. If `matched_t` were not a specific expression pointer, this // matcher would only be able to be applied to types compatible with // `matched_t`. Also note that if a matcher does not need to store any state, // its `data_t` should be set to `unused_t`. // // Now we need to tell the matcher infrastructure what custom logic to apply // for this matcher. We do this by specializing `MatchSelf`. // // template<> struct MatchSelf { // bool operator()(Call* curr, Name prefix) { // return curr->name.startsWith(prefix); // } // }; // // Note that the first parameter to `MatchSelf::operator()` will be that // kind's `matched_t` and the second parameter will be that kind's `data_t`, // which may be `unused_t`. (TODO: detect if `data_t` is `unused_t` and don't // expose it in the Matcher interface if so.) // // After this, everything is the same as in the simple matcher case. This // particular matcher doesn't need to recurse into any subcomponents, so we can // skip straight to creating the wrapper function. // // decltype(auto) prefixCall(Call** binder, Name prefix) { // return Matcher(binder, prefix); // } // // Now we can use the new matcher: // // Call* call; // if (matches(expr, prefixCall(&call, "__foo"))) { // // `call` set to `expr` here // } // // The main entrypoint for matching. If the match succeeds, all variables bound // in the matcher will be set to their corresponding matched values. Otherwise, // the value of the bound variables is unspecified and may have changed. template inline bool matches(Expression* expr, Matcher matcher) { return matcher.matches(expr); } namespace Internal { struct unused_t {}; // Each matcher has a `Kind`, which controls how candidate values are // destructured and inspected. For most matchers, `Kind` is a pointer to the // matched subtype of Expression, but when there are multiple matchers for the // same kind of expression, they are disambiguated by having different `Kind`s. // In this case, or if the matcher matches something besides a pointer to a // subtype of Expression, or if the matcher requires additional state, the // matched type and the type of additional state must be associated with the // `Kind` via a specialization of `KindTypeRegistry`. template struct KindTypeRegistry { // The matched type using matched_t = void; // The type of additional state needed to perform a match. Can be set to // `unused_t` if it's not needed. using data_t = unused_t; }; // Given a `Kind`, produce the type `matched_t` that is matched by that Kind and // the type `candidate_t` that is the type of the parameter of the `matches` // method. These types are only different if `matched_t` is a pointer to a // subtype of Expression, in which case `candidate_t` is Expression*. template struct MatchTypes { using matched_t = typename std::conditional_t< std::is_base_of>::value, Kind, typename KindTypeRegistry::matched_t>; static constexpr bool isExpr = std::is_base_of>::value; using candidate_t = typename std::conditional_t; }; template using matched_t = typename MatchTypes::matched_t; template using candidate_t = typename MatchTypes::candidate_t; template using data_t = typename KindTypeRegistry::data_t; // Defined if the matched type is a specific expression pointer, so can be // `dynCast`ed to from Expression*. template using enable_if_castable_t = typename std::enable_if< std::is_base_of>>::value && !std::is_same>::value, int>::type; // Opposite of above template using enable_if_not_castable_t = typename std::enable_if< !std::is_base_of>>::value || std::is_same>::value, int>::type; // Do a normal dynCast from Expression* to the subtype, storing the result in // `out` and returning `true` iff the cast succeeded. template = 0> inline bool dynCastCandidate(candidate_t candidate, matched_t& out) { out = candidate->template dynCast>>(); return out != nullptr; } // Otherwise we are not matching an Expression, so this is infallible. template = 0> inline bool dynCastCandidate(candidate_t candidate, matched_t& out) { out = candidate; return true; } // Matchers can optionally specialize this to perform custom matching logic // before recursing into submatchers, potentially short-circuiting the match. // Uses a struct because partial specialization of functions is not allowed. template struct MatchSelf { bool operator()(matched_t, data_t) { return true; } }; // Used to statically ensure that each matcher has the correct number of // submatchers. This needs to be specialized for each kind of matcher that has // submatchers. template struct NumComponents { static constexpr size_t value = 0; }; // Every kind of matcher needs to partially specialize this for each of its // components. Each specialization should define // // T operator()(matched_t) // // where T is the component's type. Components will be matched from first to // last. Uses a struct instead of a function because partial specialization of // functions is not allowed. template struct GetComponent; // A type-level linked list to hold an arbitrary number of matchers. template struct SubMatchers {}; template struct SubMatchers { CurrMatcher curr; SubMatchers next; SubMatchers(CurrMatcher curr, NextMatchers... next) : curr(curr), next(next...){}; }; // Iterates through the components of the candidate, applying a submatcher to // each component. Uses a struct instead of a function because partial // specialization of functions is not allowed. template struct Components { static inline bool match(matched_t candidate, SubMatchers& matchers) { return matchers.curr.matches(GetComponent{}(candidate)) && Components::match(candidate, matchers.next); } }; template struct Components { static_assert(pos == NumComponents::value, "Unexpected number of submatchers"); static inline bool match(matched_t, SubMatchers<>) { // Base case when there are no components left; trivially true. return true; } }; template struct Matcher { matched_t* binder; data_t data; SubMatchers submatchers; Matcher(matched_t* binder, data_t data, Matchers... submatchers) : binder(binder), data(data), submatchers(submatchers...) {} inline bool matches(candidate_t candidate) { matched_t casted; if (dynCastCandidate(candidate, casted)) { if (binder != nullptr) { *binder = casted; } return MatchSelf{}(casted, data) && Components::match(casted, submatchers); } return false; } }; // Concrete low-level matcher implementations. Not intended for direct external // use. // Any: matches any value of the expected type template struct AnyKind {}; template struct KindTypeRegistry> { using matched_t = T; using data_t = unused_t; }; template inline decltype(auto) Any(T* binder) { return Matcher>(binder, {}); } // Exact: matches exact values of the expected type template struct ExactKind {}; template struct KindTypeRegistry> { using matched_t = T; using data_t = T; }; template struct MatchSelf> { bool operator()(T self, T expected) { return self == expected; } }; template inline decltype(auto) Exact(T* binder, T data) { return Matcher>(binder, data); } // {I32,I64,Int,F32,F64,Float,Number}Lit: match `Literal` of the expected `Type` struct I32LK { static bool matchType(Literal lit) { return lit.type == Type::i32; } static int32_t getVal(Literal lit) { return lit.geti32(); } }; struct I64LK { static bool matchType(Literal lit) { return lit.type == Type::i64; } static int64_t getVal(Literal lit) { return lit.geti64(); } }; struct IntLK { static bool matchType(Literal lit) { return lit.type.isInteger(); } static int64_t getVal(Literal lit) { return lit.getInteger(); } }; struct F32LK { static bool matchType(Literal lit) { return lit.type == Type::f32; } static float getVal(Literal lit) { return lit.getf32(); } }; struct F64LK { static bool matchType(Literal lit) { return lit.type == Type::f64; } static double getVal(Literal lit) { return lit.getf64(); } }; struct FloatLK { static bool matchType(Literal lit) { return lit.type.isFloat(); } static double getVal(Literal lit) { return lit.getFloat(); } }; template struct LitKind {}; template struct KindTypeRegistry> { using matched_t = Literal; using data_t = unused_t; }; template struct MatchSelf> { bool operator()(Literal lit, unused_t) { return T::matchType(lit); } }; template struct NumComponents> { static constexpr size_t value = 1; }; template struct GetComponent, 0> { decltype(auto) operator()(Literal lit) { return T::getVal(lit); } }; template inline decltype(auto) I32Lit(Literal* binder, S&& s) { return Matcher, S>(binder, {}, s); } template inline decltype(auto) I64Lit(Literal* binder, S&& s) { return Matcher, S>(binder, {}, s); } template inline decltype(auto) IntLit(Literal* binder, S&& s) { return Matcher, S>(binder, {}, s); } template inline decltype(auto) F32Lit(Literal* binder, S&& s) { return Matcher, S>(binder, {}, s); } template inline decltype(auto) F64Lit(Literal* binder, S&& s) { return Matcher, S>(binder, {}, s); } template inline decltype(auto) FloatLit(Literal* binder, S&& s) { return Matcher, S>(binder, {}, s); } struct NumberLitKind {}; template<> struct KindTypeRegistry { using matched_t = Literal; using data_t = int32_t; }; template<> struct MatchSelf { bool operator()(Literal lit, int32_t expected) { return lit.type.isNumber() && Literal::makeFromInt32(expected, lit.type) == lit; } }; inline decltype(auto) NumberLit(Literal* binder, int32_t expected) { return Matcher(binder, expected); } // Const template<> struct NumComponents { static constexpr size_t value = 1; }; template<> struct GetComponent { Literal operator()(Const* c) { return c->value; } }; template inline decltype(auto) ConstMatcher(Const** binder, S&& s) { return Matcher(binder, {}, s); } // Unary, UnaryOp and AbstractUnaryOp template<> struct NumComponents { static constexpr size_t value = 2; }; template<> struct GetComponent { UnaryOp operator()(Unary* curr) { return curr->op; } }; template<> struct GetComponent { Expression* operator()(Unary* curr) { return curr->value; } }; struct UnaryOpK { using Op = UnaryOp; static UnaryOp getOp(Type, Op op) { return op; } }; struct AbstractUnaryOpK { using Op = Abstract::Op; static UnaryOp getOp(Type type, Abstract::Op op) { return Abstract::getUnary(type, op); } }; template struct UnaryOpKind {}; template struct KindTypeRegistry> { using matched_t = Unary*; using data_t = typename T::Op; }; template struct MatchSelf> { bool operator()(Unary* curr, typename T::Op op) { return curr->op == T::getOp(curr->value->type, op); } }; template struct NumComponents> { static constexpr size_t value = 1; }; template struct GetComponent, 0> { Expression* operator()(Unary* curr) { return curr->value; } }; template inline decltype(auto) UnaryMatcher(Unary** binder, S1&& s1, S2&& s2) { return Matcher(binder, {}, s1, s2); } template inline decltype(auto) UnaryOpMatcher(Unary** binder, UnaryOp op, S&& s) { return Matcher, S>(binder, op, s); } template inline decltype(auto) AbstractUnaryOpMatcher(Unary** binder, Abstract::Op op, S&& s) { return Matcher, S>(binder, op, s); } // Binary, BinaryOp and AbstractBinaryOp template<> struct NumComponents { static constexpr size_t value = 3; }; template<> struct GetComponent { BinaryOp operator()(Binary* curr) { return curr->op; } }; template<> struct GetComponent { Expression* operator()(Binary* curr) { return curr->left; } }; template<> struct GetComponent { Expression* operator()(Binary* curr) { return curr->right; } }; struct BinaryOpK { using Op = BinaryOp; static BinaryOp getOp(Type, Op op) { return op; } }; struct AbstractBinaryOpK { using Op = Abstract::Op; static BinaryOp getOp(Type type, Abstract::Op op) { return Abstract::getBinary(type, op); } }; template struct BinaryOpKind {}; template struct KindTypeRegistry> { using matched_t = Binary*; using data_t = typename T::Op; }; template struct MatchSelf> { bool operator()(Binary* curr, typename T::Op op) { return curr->op == T::getOp(curr->left->type, op); } }; template struct NumComponents> { static constexpr size_t value = 2; }; template struct GetComponent, 0> { Expression* operator()(Binary* curr) { return curr->left; } }; template struct GetComponent, 1> { Expression* operator()(Binary* curr) { return curr->right; } }; template inline decltype(auto) BinaryMatcher(Binary** binder, S1&& s1, S2&& s2, S3&& s3) { return Matcher(binder, {}, s1, s2, s3); } template inline decltype(auto) BinaryOpMatcher(Binary** binder, BinaryOp op, S1&& s1, S2&& s2) { return Matcher, S1, S2>(binder, op, s1, s2); } template inline decltype(auto) AbstractBinaryOpMatcher(Binary** binder, Abstract::Op op, S1&& s1, S2&& s2) { return Matcher, S1, S2>(binder, op, s1, s2); } // Select template<> struct NumComponents { static constexpr size_t value = 3; }; template<> struct GetComponent { Expression* operator()(Select* curr) { return curr->ifTrue; } }; template<> struct GetComponent { Expression* operator()(Select* curr) { return curr->ifFalse; } }; template<> struct GetComponent { Expression* operator()(Select* curr) { return curr->condition; } }; template inline decltype(auto) SelectMatcher(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Matcher(binder, {}, s1, s2, s3); } } // namespace Internal // Public matching API inline decltype(auto) i32() { return Internal::ConstMatcher( nullptr, Internal::I32Lit(nullptr, Internal::Any(nullptr))); } // Use int rather than int32_t to disambiguate literal 0, which otherwise could // be resolved to either the int32_t overload or any of the pointer overloads. inline decltype(auto) i32(int x) { return Internal::ConstMatcher( nullptr, Internal::I32Lit(nullptr, Internal::Exact(nullptr, x))); } inline decltype(auto) i32(int32_t* binder) { return Internal::ConstMatcher( nullptr, Internal::I32Lit(nullptr, Internal::Any(binder))); } inline decltype(auto) i32(Literal* binder) { return Internal::ConstMatcher( nullptr, Internal::I32Lit(binder, Internal::Any(nullptr))); } inline decltype(auto) i32(Const** binder) { return Internal::ConstMatcher( binder, Internal::I32Lit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) i64() { return Internal::ConstMatcher( nullptr, Internal::I64Lit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) i64(int64_t x) { return Internal::ConstMatcher( nullptr, Internal::I64Lit(nullptr, Internal::Exact(nullptr, x))); } // Disambiguate literal 0, which could otherwise be interpreted as a pointer inline decltype(auto) i64(int x) { return i64(int64_t(x)); } inline decltype(auto) i64(int64_t* binder) { return Internal::ConstMatcher( nullptr, Internal::I64Lit(nullptr, Internal::Any(binder))); } inline decltype(auto) i64(Literal* binder) { return Internal::ConstMatcher( nullptr, Internal::I64Lit(binder, Internal::Any(nullptr))); } inline decltype(auto) i64(Const** binder) { return Internal::ConstMatcher( binder, Internal::I64Lit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) f32() { return Internal::ConstMatcher( nullptr, Internal::F32Lit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) f32(float x) { return Internal::ConstMatcher( nullptr, Internal::F32Lit(nullptr, Internal::Exact(nullptr, x))); } // Disambiguate literal 0, which could otherwise be interpreted as a pointer inline decltype(auto) f32(int x) { return f32(float(x)); } inline decltype(auto) f32(float* binder) { return Internal::ConstMatcher( nullptr, Internal::F32Lit(nullptr, Internal::Any(binder))); } inline decltype(auto) f32(Literal* binder) { return Internal::ConstMatcher( nullptr, Internal::F32Lit(binder, Internal::Any(nullptr))); } inline decltype(auto) f32(Const** binder) { return Internal::ConstMatcher( binder, Internal::F32Lit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) f64() { return Internal::ConstMatcher( nullptr, Internal::F64Lit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) f64(double x) { return Internal::ConstMatcher( nullptr, Internal::F64Lit(nullptr, Internal::Exact(nullptr, x))); } // Disambiguate literal 0, which could otherwise be interpreted as a pointer inline decltype(auto) f64(int x) { return f64(double(x)); } inline decltype(auto) f64(double* binder) { return Internal::ConstMatcher( nullptr, Internal::F64Lit(nullptr, Internal::Any(binder))); } inline decltype(auto) f64(Literal* binder) { return Internal::ConstMatcher( nullptr, Internal::F64Lit(binder, Internal::Any(nullptr))); } inline decltype(auto) f64(Const** binder) { return Internal::ConstMatcher( binder, Internal::F64Lit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) ival() { return Internal::ConstMatcher( nullptr, Internal::IntLit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) ival(int64_t x) { return Internal::ConstMatcher( nullptr, Internal::IntLit(nullptr, Internal::Exact(nullptr, x))); } // Disambiguate literal 0, which could otherwise be interpreted as a pointer inline decltype(auto) ival(int x) { return ival(int64_t(x)); } inline decltype(auto) ival(int64_t* binder) { return Internal::ConstMatcher( nullptr, Internal::IntLit(nullptr, Internal::Any(binder))); } inline decltype(auto) ival(Literal* binder) { return Internal::ConstMatcher( nullptr, Internal::IntLit(binder, Internal::Any(nullptr))); } inline decltype(auto) ival(Const** binder) { return Internal::ConstMatcher( binder, Internal::IntLit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) ival(Literal* binder, int64_t x) { return Internal::ConstMatcher( nullptr, Internal::IntLit(binder, Internal::Exact(nullptr, x))); } inline decltype(auto) ival(Const** binder, int64_t x) { return Internal::ConstMatcher( binder, Internal::IntLit(nullptr, Internal::Exact(nullptr, x))); } inline decltype(auto) fval() { return Internal::ConstMatcher( nullptr, Internal::FloatLit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) fval(double x) { return Internal::ConstMatcher( nullptr, Internal::FloatLit(nullptr, Internal::Exact(nullptr, x))); } // Disambiguate literal 0, which could otherwise be interpreted as a pointer inline decltype(auto) fval(int x) { return fval(double(x)); } inline decltype(auto) fval(double* binder) { return Internal::ConstMatcher( nullptr, Internal::FloatLit(nullptr, Internal::Any(binder))); } inline decltype(auto) fval(Literal* binder) { return Internal::ConstMatcher( nullptr, Internal::FloatLit(binder, Internal::Any(nullptr))); } inline decltype(auto) fval(Const** binder) { return Internal::ConstMatcher( binder, Internal::FloatLit(nullptr, Internal::Any(nullptr))); } inline decltype(auto) fval(Literal* binder, double x) { return Internal::ConstMatcher( nullptr, Internal::FloatLit(binder, Internal::Exact(nullptr, x))); } inline decltype(auto) fval(Const** binder, double x) { return Internal::ConstMatcher( binder, Internal::FloatLit(nullptr, Internal::Exact(nullptr, x))); } inline decltype(auto) constant() { return Internal::ConstMatcher(nullptr, Internal::Any(nullptr)); } inline decltype(auto) constant(int x) { return Internal::ConstMatcher(nullptr, Internal::NumberLit(nullptr, x)); } inline decltype(auto) constant(Literal* binder) { return Internal::ConstMatcher(nullptr, Internal::Any(binder)); } inline decltype(auto) constant(Const** binder) { return Internal::ConstMatcher(binder, Internal::Any(nullptr)); } inline decltype(auto) constant(Literal* binder, int32_t x) { return Internal::ConstMatcher(nullptr, Internal::NumberLit(binder, x)); } inline decltype(auto) constant(Const** binder, int32_t x) { return Internal::ConstMatcher(binder, Internal::NumberLit(nullptr, x)); } inline decltype(auto) any() { return Internal::Any(nullptr); } inline decltype(auto) any(Expression** binder) { return Internal::Any(binder); } template inline decltype(auto) unary(S&& s) { return Internal::UnaryMatcher(nullptr, Internal::Any(nullptr), s); } template inline decltype(auto) unary(Unary** binder, S&& s) { return Internal::UnaryMatcher(binder, Internal::Any(nullptr), s); } template inline decltype(auto) unary(UnaryOp* binder, S&& s) { return Internal::UnaryMatcher(nullptr, Internal::Any(binder), s); } template inline decltype(auto) unary(UnaryOp op, S&& s) { return Internal::UnaryOpMatcher(nullptr, op, s); } template inline decltype(auto) unary(Abstract::Op op, S&& s) { return Internal::AbstractUnaryOpMatcher(nullptr, op, s); } template inline decltype(auto) unary(Unary** binder, UnaryOp op, S&& s) { return Internal::UnaryOpMatcher(binder, op, s); } template inline decltype(auto) unary(Unary** binder, Abstract::Op op, S&& s) { return Internal::AbstractUnaryOpMatcher(binder, op, s); } template inline decltype(auto) binary(S1&& s1, S2&& s2) { return Internal::BinaryMatcher( nullptr, Internal::Any(nullptr), s1, s2); } template inline decltype(auto) binary(Binary** binder, S1&& s1, S2&& s2) { return Internal::BinaryMatcher( binder, Internal::Any(nullptr), s1, s2); } template inline decltype(auto) binary(BinaryOp* binder, S1&& s1, S2&& s2) { return Internal::BinaryMatcher( nullptr, Internal::Any(binder), s1, s2); } template inline decltype(auto) binary(BinaryOp op, S1&& s1, S2&& s2) { return Internal::BinaryOpMatcher(nullptr, op, s1, s2); } template inline decltype(auto) binary(Abstract::Op op, S1&& s1, S2&& s2) { return Internal::AbstractBinaryOpMatcher(nullptr, op, s1, s2); } template inline decltype(auto) binary(Binary** binder, BinaryOp op, S1&& s1, S2&& s2) { return Internal::BinaryOpMatcher(binder, op, s1, s2); } template inline decltype(auto) binary(Binary** binder, Abstract::Op op, S1&& s1, S2&& s2) { return Internal::AbstractBinaryOpMatcher(binder, op, s1, s2); } template inline decltype(auto) select(S1&& s1, S2&& s2, S3&& s3) { return Internal::SelectMatcher(nullptr, s1, s2, s3); } template inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Internal::SelectMatcher(binder, s1, s2, s3); } } // namespace Match } // namespace wasm #endif // wasm_ir_match_h