/* * 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::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. // // bval, ival, fval // // Match any boolean, any integer 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<Expression::FrozzleId> { // 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<Frozzle*> { // 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<Frozzle*, 0> { // Expression* operator()(Frozzle* curr) { return curr->foo; } // }; // template<> struct GetComponent<Frozzle*, 1> { // Expression* operator()(Frozzle* curr) { return curr->bar; } // }; // template<> struct GetComponent<Frozzle*, 2> { // 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<Frozzle*, S1, S2, S3>(&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<class S1, class S2, class S3> // inline decltype(auto) frozzle(Frozzle** binder, // S1&& s1, S2&& s2, S3&& s3) { // return Matcher<Frozzle*, S1, S2, S3>(binder, {}, s1, s2, s3); // } // template<class S1, class S2, class S3> // inline decltype(auto) frozzle(S1&& s1, S2&& s2, S3&& s3) { // return Matcher<Frozzle*, S1, S2, S3>(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<PrefixCallKind> { // 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<PrefixCallKind> { // bool operator()(Call* curr, Name prefix) { // return curr->name.startsWith(prefix); // } // }; // // Note that the first parameter to `MatchSelf<Kind>::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<PrefixCallKind>(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<class Matcher> 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<class Kind> 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<class Kind> struct MatchTypes { using matched_t = typename std::conditional_t< std::is_base_of<Expression, std::remove_pointer_t<Kind>>::value, Kind, typename KindTypeRegistry<Kind>::matched_t>; static constexpr bool isExpr = std::is_base_of<Expression, std::remove_pointer_t<matched_t>>::value; using candidate_t = typename std::conditional_t<isExpr, Expression*, matched_t>; }; template<class Kind> using matched_t = typename MatchTypes<Kind>::matched_t; template<class Kind> using candidate_t = typename MatchTypes<Kind>::candidate_t; template<class Kind> using data_t = typename KindTypeRegistry<Kind>::data_t; // Defined if the matched type is a specific expression pointer, so can be // `dynCast`ed to from Expression*. template<class Kind> using enable_if_castable_t = typename std::enable_if< std::is_base_of<Expression, std::remove_pointer_t<matched_t<Kind>>>::value && !std::is_same<Expression*, matched_t<Kind>>::value, int>::type; // Opposite of above template<class Kind> using enable_if_not_castable_t = typename std::enable_if< !std::is_base_of<Expression, std::remove_pointer_t<matched_t<Kind>>>::value || std::is_same<Expression*, matched_t<Kind>>::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<class Kind, enable_if_castable_t<Kind> = 0> inline bool dynCastCandidate(candidate_t<Kind> candidate, matched_t<Kind>& out) { out = candidate->template dynCast<std::remove_pointer_t<matched_t<Kind>>>(); return out != nullptr; } // Otherwise we are not matching an Expression, so this is infallible. template<class Kind, enable_if_not_castable_t<Kind> = 0> inline bool dynCastCandidate(candidate_t<Kind> candidate, matched_t<Kind>& 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<class Kind> struct MatchSelf { bool operator()(matched_t<Kind>, data_t<Kind>) { 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<class Kind> 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<Kind>) // // 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<class Kind, int pos> struct GetComponent; // A type-level linked list to hold an arbitrary number of matchers. template<class...> struct SubMatchers {}; template<class CurrMatcher, class... NextMatchers> struct SubMatchers<CurrMatcher, NextMatchers...> { CurrMatcher curr; SubMatchers<NextMatchers...> 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<class Kind, int pos, class CurrMatcher = void, class... NextMatchers> struct Components { static inline bool match(matched_t<Kind> candidate, SubMatchers<CurrMatcher, NextMatchers...>& matchers) { return matchers.curr.matches(GetComponent<Kind, pos>{}(candidate)) && Components<Kind, pos + 1, NextMatchers...>::match(candidate, matchers.next); } }; template<class Kind, int pos> struct Components<Kind, pos> { static_assert(pos == NumComponents<Kind>::value, "Unexpected number of submatchers"); static inline bool match(matched_t<Kind>, SubMatchers<>) { // Base case when there are no components left; trivially true. return true; } }; template<class Kind, class... Matchers> struct Matcher { matched_t<Kind>* binder; data_t<Kind> data; SubMatchers<Matchers...> submatchers; Matcher(matched_t<Kind>* binder, data_t<Kind> data, Matchers... submatchers) : binder(binder), data(data), submatchers(submatchers...) {} inline bool matches(candidate_t<Kind> candidate) { matched_t<Kind> casted; if (dynCastCandidate<Kind>(candidate, casted)) { if (binder != nullptr) { *binder = casted; } return MatchSelf<Kind>{}(casted, data) && Components<Kind, 0, Matchers...>::match(casted, submatchers); } return false; } }; // Concrete low-level matcher implementations. Not intended for direct external // use. // Any<T>: matches any value of the expected type template<class T> struct AnyKind {}; template<class T> struct KindTypeRegistry<AnyKind<T>> { using matched_t = T; using data_t = unused_t; }; template<class T> inline decltype(auto) Any(T* binder) { return Matcher<AnyKind<T>>(binder, {}); } // Exact<T>: matches exact values of the expected type template<class T> struct ExactKind {}; template<class T> struct KindTypeRegistry<ExactKind<T>> { using matched_t = T; using data_t = T; }; template<class T> struct MatchSelf<ExactKind<T>> { bool operator()(T self, T expected) { return self == expected; } }; template<class T> inline decltype(auto) Exact(T* binder, T data) { return Matcher<ExactKind<T>>(binder, data); } // {Bool,I32,I64,Int,F32,F64,Float,Number}Lit: // match `Literal` of the expected `Type` struct BoolLK { static bool matchType(Literal lit) { return lit.type == Type::i32 && (uint32_t)lit.geti32() <= 1U; } static int32_t getVal(Literal lit) { return lit.geti32(); } }; 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<class T> struct LitKind {}; template<class T> struct KindTypeRegistry<LitKind<T>> { using matched_t = Literal; using data_t = unused_t; }; template<class T> struct MatchSelf<LitKind<T>> { bool operator()(Literal lit, unused_t) { return T::matchType(lit); } }; template<class T> struct NumComponents<LitKind<T>> { static constexpr size_t value = 1; }; template<class T> struct GetComponent<LitKind<T>, 0> { decltype(auto) operator()(Literal lit) { return T::getVal(lit); } }; template<class S> inline decltype(auto) BoolLit(Literal* binder, S&& s) { return Matcher<LitKind<BoolLK>, S>(binder, {}, s); } template<class S> inline decltype(auto) I32Lit(Literal* binder, S&& s) { return Matcher<LitKind<I32LK>, S>(binder, {}, s); } template<class S> inline decltype(auto) I64Lit(Literal* binder, S&& s) { return Matcher<LitKind<I64LK>, S>(binder, {}, s); } template<class S> inline decltype(auto) IntLit(Literal* binder, S&& s) { return Matcher<LitKind<IntLK>, S>(binder, {}, s); } template<class S> inline decltype(auto) F32Lit(Literal* binder, S&& s) { return Matcher<LitKind<F32LK>, S>(binder, {}, s); } template<class S> inline decltype(auto) F64Lit(Literal* binder, S&& s) { return Matcher<LitKind<F64LK>, S>(binder, {}, s); } template<class S> inline decltype(auto) FloatLit(Literal* binder, S&& s) { return Matcher<LitKind<FloatLK>, S>(binder, {}, s); } struct NumberLitKind {}; template<> struct KindTypeRegistry<NumberLitKind> { using matched_t = Literal; using data_t = int32_t; }; template<> struct MatchSelf<NumberLitKind> { 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<NumberLitKind>(binder, expected); } // Const template<> struct NumComponents<Const*> { static constexpr size_t value = 1; }; template<> struct GetComponent<Const*, 0> { Literal operator()(Const* c) { return c->value; } }; template<class S> inline decltype(auto) ConstMatcher(Const** binder, S&& s) { return Matcher<Const*, S>(binder, {}, s); } // Unary, UnaryOp and AbstractUnaryOp template<> struct NumComponents<Unary*> { static constexpr size_t value = 2; }; template<> struct GetComponent<Unary*, 0> { UnaryOp operator()(Unary* curr) { return curr->op; } }; template<> struct GetComponent<Unary*, 1> { 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<class T> struct UnaryOpKind {}; template<class T> struct KindTypeRegistry<UnaryOpKind<T>> { using matched_t = Unary*; using data_t = typename T::Op; }; template<class T> struct MatchSelf<UnaryOpKind<T>> { bool operator()(Unary* curr, typename T::Op op) { return curr->op == T::getOp(curr->value->type, op); } }; template<class T> struct NumComponents<UnaryOpKind<T>> { static constexpr size_t value = 1; }; template<class T> struct GetComponent<UnaryOpKind<T>, 0> { Expression* operator()(Unary* curr) { return curr->value; } }; template<class S1, class S2> inline decltype(auto) UnaryMatcher(Unary** binder, S1&& s1, S2&& s2) { return Matcher<Unary*, S1, S2>(binder, {}, s1, s2); } template<class S> inline decltype(auto) UnaryOpMatcher(Unary** binder, UnaryOp op, S&& s) { return Matcher<UnaryOpKind<UnaryOpK>, S>(binder, op, s); } template<class S> inline decltype(auto) AbstractUnaryOpMatcher(Unary** binder, Abstract::Op op, S&& s) { return Matcher<UnaryOpKind<AbstractUnaryOpK>, S>(binder, op, s); } // Binary, BinaryOp and AbstractBinaryOp template<> struct NumComponents<Binary*> { static constexpr size_t value = 3; }; template<> struct GetComponent<Binary*, 0> { BinaryOp operator()(Binary* curr) { return curr->op; } }; template<> struct GetComponent<Binary*, 1> { Expression* operator()(Binary* curr) { return curr->left; } }; template<> struct GetComponent<Binary*, 2> { 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<class T> struct BinaryOpKind {}; template<class T> struct KindTypeRegistry<BinaryOpKind<T>> { using matched_t = Binary*; using data_t = typename T::Op; }; template<class T> struct MatchSelf<BinaryOpKind<T>> { bool operator()(Binary* curr, typename T::Op op) { return curr->op == T::getOp(curr->left->type, op); } }; template<class T> struct NumComponents<BinaryOpKind<T>> { static constexpr size_t value = 2; }; template<class T> struct GetComponent<BinaryOpKind<T>, 0> { Expression* operator()(Binary* curr) { return curr->left; } }; template<class T> struct GetComponent<BinaryOpKind<T>, 1> { Expression* operator()(Binary* curr) { return curr->right; } }; template<class S1, class S2, class S3> inline decltype(auto) BinaryMatcher(Binary** binder, S1&& s1, S2&& s2, S3&& s3) { return Matcher<Binary*, S1, S2, S3>(binder, {}, s1, s2, s3); } template<class S1, class S2> inline decltype(auto) BinaryOpMatcher(Binary** binder, BinaryOp op, S1&& s1, S2&& s2) { return Matcher<BinaryOpKind<BinaryOpK>, S1, S2>(binder, op, s1, s2); } template<class S1, class S2> inline decltype(auto) AbstractBinaryOpMatcher(Binary** binder, Abstract::Op op, S1&& s1, S2&& s2) { return Matcher<BinaryOpKind<AbstractBinaryOpK>, S1, S2>(binder, op, s1, s2); } // Select template<> struct NumComponents<Select*> { static constexpr size_t value = 3; }; template<> struct GetComponent<Select*, 0> { Expression* operator()(Select* curr) { return curr->ifTrue; } }; template<> struct GetComponent<Select*, 1> { Expression* operator()(Select* curr) { return curr->ifFalse; } }; template<> struct GetComponent<Select*, 2> { Expression* operator()(Select* curr) { return curr->condition; } }; template<class S1, class S2, class S3> inline decltype(auto) SelectMatcher(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Matcher<Select*, S1, S2, S3>(binder, {}, s1, s2, s3); } } // namespace Internal // Public matching API inline decltype(auto) bval() { return Internal::ConstMatcher( nullptr, Internal::BoolLit(nullptr, Internal::Any<bool>(nullptr))); } inline decltype(auto) bval(bool x) { return Internal::ConstMatcher( nullptr, Internal::BoolLit(nullptr, Internal::Exact<bool>(nullptr, x))); } inline decltype(auto) bval(bool* binder) { return Internal::ConstMatcher( nullptr, Internal::BoolLit(nullptr, Internal::Any(binder))); } inline decltype(auto) bval(Literal* binder) { return Internal::ConstMatcher( nullptr, Internal::BoolLit(binder, Internal::Any<bool>(nullptr))); } inline decltype(auto) bval(Const** binder) { return Internal::ConstMatcher( binder, Internal::BoolLit(nullptr, Internal::Any<bool>(nullptr))); } inline decltype(auto) i32() { return Internal::ConstMatcher( nullptr, Internal::I32Lit(nullptr, Internal::Any<int32_t>(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<int32_t>(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<int32_t>(nullptr))); } inline decltype(auto) i32(Const** binder) { return Internal::ConstMatcher( binder, Internal::I32Lit(nullptr, Internal::Any<int32_t>(nullptr))); } inline decltype(auto) i64() { return Internal::ConstMatcher( nullptr, Internal::I64Lit(nullptr, Internal::Any<int64_t>(nullptr))); } inline decltype(auto) i64(int64_t x) { return Internal::ConstMatcher( nullptr, Internal::I64Lit(nullptr, Internal::Exact<int64_t>(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<int64_t>(nullptr))); } inline decltype(auto) i64(Const** binder) { return Internal::ConstMatcher( binder, Internal::I64Lit(nullptr, Internal::Any<int64_t>(nullptr))); } inline decltype(auto) f32() { return Internal::ConstMatcher( nullptr, Internal::F32Lit(nullptr, Internal::Any<float>(nullptr))); } inline decltype(auto) f32(float x) { return Internal::ConstMatcher( nullptr, Internal::F32Lit(nullptr, Internal::Exact<float>(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<float>(nullptr))); } inline decltype(auto) f32(Const** binder) { return Internal::ConstMatcher( binder, Internal::F32Lit(nullptr, Internal::Any<float>(nullptr))); } inline decltype(auto) f64() { return Internal::ConstMatcher( nullptr, Internal::F64Lit(nullptr, Internal::Any<double>(nullptr))); } inline decltype(auto) f64(double x) { return Internal::ConstMatcher( nullptr, Internal::F64Lit(nullptr, Internal::Exact<double>(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<double>(nullptr))); } inline decltype(auto) f64(Const** binder) { return Internal::ConstMatcher( binder, Internal::F64Lit(nullptr, Internal::Any<double>(nullptr))); } inline decltype(auto) ival() { return Internal::ConstMatcher( nullptr, Internal::IntLit(nullptr, Internal::Any<int64_t>(nullptr))); } inline decltype(auto) ival(int64_t x) { return Internal::ConstMatcher( nullptr, Internal::IntLit(nullptr, Internal::Exact<int64_t>(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<int64_t>(nullptr))); } inline decltype(auto) ival(Const** binder) { return Internal::ConstMatcher( binder, Internal::IntLit(nullptr, Internal::Any<int64_t>(nullptr))); } inline decltype(auto) ival(Literal* binder, int64_t x) { return Internal::ConstMatcher( nullptr, Internal::IntLit(binder, Internal::Exact<int64_t>(nullptr, x))); } inline decltype(auto) ival(Const** binder, int64_t x) { return Internal::ConstMatcher( binder, Internal::IntLit(nullptr, Internal::Exact<int64_t>(nullptr, x))); } inline decltype(auto) fval() { return Internal::ConstMatcher( nullptr, Internal::FloatLit(nullptr, Internal::Any<double>(nullptr))); } inline decltype(auto) fval(double x) { return Internal::ConstMatcher( nullptr, Internal::FloatLit(nullptr, Internal::Exact<double>(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<double>(nullptr))); } inline decltype(auto) fval(Const** binder) { return Internal::ConstMatcher( binder, Internal::FloatLit(nullptr, Internal::Any<double>(nullptr))); } inline decltype(auto) fval(Literal* binder, double x) { return Internal::ConstMatcher( nullptr, Internal::FloatLit(binder, Internal::Exact<double>(nullptr, x))); } inline decltype(auto) fval(Const** binder, double x) { return Internal::ConstMatcher( binder, Internal::FloatLit(nullptr, Internal::Exact<double>(nullptr, x))); } inline decltype(auto) constant() { return Internal::ConstMatcher(nullptr, Internal::Any<Literal>(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<Literal>(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<Expression*>(nullptr); } inline decltype(auto) any(Expression** binder) { return Internal::Any(binder); } template<class S> inline decltype(auto) unary(S&& s) { return Internal::UnaryMatcher(nullptr, Internal::Any<UnaryOp>(nullptr), s); } template<class S> inline decltype(auto) unary(Unary** binder, S&& s) { return Internal::UnaryMatcher(binder, Internal::Any<UnaryOp>(nullptr), s); } template<class S> inline decltype(auto) unary(UnaryOp* binder, S&& s) { return Internal::UnaryMatcher(nullptr, Internal::Any<UnaryOp>(binder), s); } template<class S> inline decltype(auto) unary(UnaryOp op, S&& s) { return Internal::UnaryOpMatcher(nullptr, op, s); } template<class S> inline decltype(auto) unary(Abstract::Op op, S&& s) { return Internal::AbstractUnaryOpMatcher(nullptr, op, s); } template<class S> inline decltype(auto) unary(Unary** binder, UnaryOp op, S&& s) { return Internal::UnaryOpMatcher(binder, op, s); } template<class S> inline decltype(auto) unary(Unary** binder, Abstract::Op op, S&& s) { return Internal::AbstractUnaryOpMatcher(binder, op, s); } template<class S1, class S2> inline decltype(auto) binary(S1&& s1, S2&& s2) { return Internal::BinaryMatcher( nullptr, Internal::Any<BinaryOp>(nullptr), s1, s2); } template<class S1, class S2> inline decltype(auto) binary(Binary** binder, S1&& s1, S2&& s2) { return Internal::BinaryMatcher( binder, Internal::Any<BinaryOp>(nullptr), s1, s2); } template<class S1, class S2> inline decltype(auto) binary(BinaryOp* binder, S1&& s1, S2&& s2) { return Internal::BinaryMatcher( nullptr, Internal::Any<BinaryOp>(binder), s1, s2); } template<class S1, class S2> inline decltype(auto) binary(BinaryOp op, S1&& s1, S2&& s2) { return Internal::BinaryOpMatcher(nullptr, op, s1, s2); } template<class S1, class S2> inline decltype(auto) binary(Abstract::Op op, S1&& s1, S2&& s2) { return Internal::AbstractBinaryOpMatcher(nullptr, op, s1, s2); } template<class S1, class S2> inline decltype(auto) binary(Binary** binder, BinaryOp op, S1&& s1, S2&& s2) { return Internal::BinaryOpMatcher(binder, op, s1, s2); } template<class S1, class S2> inline decltype(auto) binary(Binary** binder, Abstract::Op op, S1&& s1, S2&& s2) { return Internal::AbstractBinaryOpMatcher(binder, op, s1, s2); } template<class S1, class S2, class S3> inline decltype(auto) select(S1&& s1, S2&& s2, S3&& s3) { return Internal::SelectMatcher(nullptr, s1, s2, s3); } template<class S1, class S2, class S3> inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Internal::SelectMatcher(binder, s1, s2, s3); } } // namespace wasm::Match #endif // wasm_ir_match_h