Program Listing for File expected.h

Program Listing for File expected.h#

Return to documentation for file (tvm/ffi/expected.h)

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#ifndef TVM_FFI_EXPECTED_H_
#define TVM_FFI_EXPECTED_H_

#include <tvm/ffi/any.h>
#include <tvm/ffi/error.h>

#include <sstream>
#include <string>
#include <type_traits>
#include <utility>

namespace tvm {
namespace ffi {

template <typename E = Error>
class Unexpected {
  static_assert(std::is_base_of_v<Error, std::remove_cv_t<E>>,
                "Unexpected<E> requires E to be Error or a subclass of Error.");

 public:
  // Special members are explicitly inlined to enable move cleanup optimizations
  TVM_FFI_INLINE ~Unexpected() = default;
  TVM_FFI_INLINE Unexpected(const Unexpected&) = default;
  TVM_FFI_INLINE Unexpected(Unexpected&&) noexcept = default;
  TVM_FFI_INLINE Unexpected& operator=(const Unexpected&) = default;
  TVM_FFI_INLINE Unexpected& operator=(Unexpected&&) noexcept = default;

  explicit Unexpected(E error) : error_(std::move(error)) {}

  const E& error() const& noexcept { return error_; }
  E& error() & noexcept { return error_; }
  const E&& error() const&& noexcept { return std::move(error_); }
  E&& error() && noexcept { return std::move(error_); }

 private:
  E error_;
};

#ifndef TVM_FFI_DOXYGEN_MODE
template <typename E>
Unexpected(E) -> Unexpected<E>;
#endif

template <typename T>
class Expected;


template <typename T, typename U>
inline constexpr bool type_subsumes_v<Expected<T>, Expected<U>> = type_subsumes_v<T, U>;

namespace details {

struct ExpectedUnsafe;

template <typename T>
inline constexpr bool is_expected_v = false;

template <typename T>
inline constexpr bool is_expected_v<Expected<T>> = true;

template <typename T>
inline constexpr bool is_unexpected_v = false;

template <typename E>
inline constexpr bool is_unexpected_v<Unexpected<E>> = true;

}  // namespace details

template <typename T>
class Expected {
 public:
  static_assert(
      !std::is_void_v<T>,
      "Expected with a cv-qualified void success type is not allowed. Use Expected<void>.");
  static_assert(!std::is_same_v<T, Error>, "Expected<Error> is not allowed. Use Error directly.");

  // Special members are explicitly inlined to enable move cleanup optimizations
  TVM_FFI_INLINE ~Expected() = default;
  TVM_FFI_INLINE Expected(const Expected&) = default;
  TVM_FFI_INLINE Expected(Expected&&) noexcept = default;
  TVM_FFI_INLINE Expected& operator=(const Expected&) = default;
  TVM_FFI_INLINE Expected& operator=(Expected&&) noexcept = default;

  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE Expected(T value) : data_(Any(std::move(value))) {}

  // Excludes Error, Unexpected, and Expected deliberately: Any subsumes all three, so without
  // these an Expected<Any> built from an error would store it as a success value. The Expected
  // exclusion also keeps this overload disjoint from Expected(Expected<U>) instead of relying on
  // partial ordering to choose between two paths that must agree.
  //
  // std::expected admits constructible sources and uses C++20 explicit(bool) to separate its
  // implicit subset. Under C++17, convertibility keeps exactly that implicit subset and drops only
  // explicit-only conversions; is_constructible plus explicit(bool) can extend it after an upgrade.
  template <typename U, typename = std::enable_if_t<!details::is_expected_v<std::decay_t<U>> &&
                                                    !details::is_unexpected_v<std::decay_t<U>> &&
                                                    !std::is_base_of_v<Error, std::decay_t<U>> &&
                                                    std::is_convertible_v<U, T>>>
  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE Expected(U&& value) : data_(Any(T(std::forward<U>(value)))) {}

  // Subsumption belongs only here: this source already contains a materialized U or Error whose
  // representation may be reused. Applying type_subsumes_v<Any, U> to the bare-value constructor
  // would accept every U, including types that cannot be materialized as Any, and fail in its body.
  // Taking by value gives a local to move from, copying an lvalue source and moving an rvalue. The
  // implicit copy constructor still wins for Expected<T> itself by the non-template tiebreaker.
  template <typename U,
            typename = std::enable_if_t<!std::is_void_v<U> &&
                                        (type_subsumes_v<T, U> || std::is_convertible_v<U, T>)>>
  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE Expected(Expected<U> other)
      : data_([&other]() {
          if constexpr (type_subsumes_v<T, U>) {
            // data_ holds a T or an Error. Subsumption proves the source representation already
            // satisfies that invariant, so adopt the raw storage without inspecting its state.
            // Do not make this unconditional: value() checks the success/error state, not the type.
            return details::AnyUnsafe::MoveTVMFFIAnyRawToAny(
                details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(other.data_)));
          } else {
            return other.is_err() ? Any(std::move(other).error())
                                  : Any(T(std::move(other).value()));
          }
        }()) {}

  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE Expected(Error error) : data_(Any(std::move(error))) {}

  template <typename E, typename = std::enable_if_t<std::is_base_of_v<Error, std::remove_cv_t<E>>>>
  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE Expected(Unexpected<E> unexpected) : data_(Any(std::move(unexpected).error())) {}

  TVM_FFI_INLINE int32_t type_index() const noexcept { return data_.type_index(); }

  TVM_FFI_INLINE bool is_ok() const noexcept {
    return data_.type_index() != TypeIndex::kTVMFFIError;
  }

  TVM_FFI_INLINE bool is_err() const noexcept {
    return data_.type_index() == TypeIndex::kTVMFFIError;
  }

  TVM_FFI_INLINE bool has_value() const noexcept { return is_ok(); }

  TVM_FFI_INLINE T value() const& {
    if (TVM_FFI_PREDICT_TRUE(is_ok())) {
      return details::AnyUnsafe::CopyFromAnyViewAfterCheck<T>(data_);
    }
    throw details::AnyUnsafe::CopyFromAnyViewAfterCheck<Error>(data_);
  }

  TVM_FFI_INLINE T value() && {
    if (TVM_FFI_PREDICT_TRUE(is_ok())) {
      return details::AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
    }
    throw details::AnyUnsafe::MoveFromAnyAfterCheck<Error>(std::move(data_));
  }

  template <typename U, typename = std::enable_if_t<
                            std::is_same_v<U, std::decay_t<U>> && !std::is_base_of_v<Error, U> &&
                            (TypeTraits<U>::storage_enabled || std::is_same_v<U, Any>)>>
  TVM_FFI_INLINE Expected<U> as_or_error() const& {
    if (TVM_FFI_PREDICT_FALSE(data_.type_index() != TypeIndex::kTVMFFIError &&
                              !details::AnyUnsafe::CheckAnyStrict<U>(data_))) {
      // Conversion-failure diagnostics may try fallback conversions, so use the stored type key.
      return Error("TypeError",
                   "Cannot treat type `" + data_.GetTypeKey() + "` as type `" +
                       details::Type2Str<U>::v() + "`",
                   "");
    }
    return Expected<U>(UnsafeInit{}, details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(data_)));
  }

  template <typename U, typename = std::enable_if_t<
                            std::is_same_v<U, std::decay_t<U>> && !std::is_base_of_v<Error, U> &&
                            (TypeTraits<U>::storage_enabled || std::is_same_v<U, Any>)>>
  TVM_FFI_INLINE Expected<U> as_or_error() && {
    if (TVM_FFI_PREDICT_FALSE(data_.type_index() != TypeIndex::kTVMFFIError &&
                              !details::AnyUnsafe::CheckAnyStrict<U>(data_))) {
      // Conversion-failure diagnostics may try fallback conversions, so use the stored type key.
      return Error("TypeError",
                   "Cannot treat type `" + data_.GetTypeKey() + "` as type `" +
                       details::Type2Str<U>::v() + "`",
                   "");
    }
    return Expected<U>(UnsafeInit{}, details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(data_)));
  }

  TVM_FFI_INLINE Error error() const& {
    // No branch hint: error() is itself a cold path — callers only invoke it
    // after observing !is_ok(), so the branch direction here doesn't matter.
    if (is_ok()) {
      TVM_FFI_THROW(RuntimeError) << "Bad expected access: contains value, not error";
    }
    return details::AnyUnsafe::CopyFromAnyViewAfterCheck<Error>(data_);
  }

  TVM_FFI_INLINE Error error() && {
    // No branch hint: error() is itself a cold path — callers only invoke it
    // after observing !is_ok(), so the branch direction here doesn't matter.
    if (is_ok()) {
      TVM_FFI_THROW(RuntimeError) << "Bad expected access: contains value, not error";
    }
    return details::AnyUnsafe::MoveFromAnyAfterCheck<Error>(std::move(data_));
  }

  template <typename U = std::remove_cv_t<T>>
  TVM_FFI_INLINE T value_or(U&& default_value) const& {
    if (TVM_FFI_PREDICT_TRUE(is_ok())) {
      return details::AnyUnsafe::CopyFromAnyViewAfterCheck<T>(data_);
    }
    return T(std::forward<U>(default_value));
  }

  template <typename U = std::remove_cv_t<T>>
  TVM_FFI_INLINE T value_or(U&& default_value) && {
    if (TVM_FFI_PREDICT_TRUE(is_ok())) {
      return details::AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
    }
    return T(std::forward<U>(default_value));
  }

 private:
  template <typename>
  friend class Expected;
  Expected() = default;
  TVM_FFI_INLINE Expected(UnsafeInit, TVMFFIAny raw) noexcept
      : data_(details::AnyUnsafe::MoveTVMFFIAnyRawToAny(raw)) {}

  friend struct details::ExpectedUnsafe;

  Any data_;  // Invariant: holds a T (type_index != kTVMFFIError) or an Error.
};

template <>
class Expected<void> {
 public:
  // Special members are explicitly inlined to enable move cleanup optimizations
  TVM_FFI_INLINE ~Expected() = default;
  TVM_FFI_INLINE Expected(const Expected&) = default;
  TVM_FFI_INLINE Expected(Expected&&) noexcept = default;
  TVM_FFI_INLINE Expected& operator=(const Expected&) = default;
  TVM_FFI_INLINE Expected& operator=(Expected&&) noexcept = default;

  Expected() = default;

  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE Expected(Error error) : data_(Any(std::move(error))) {}

  template <typename E, typename = std::enable_if_t<std::is_base_of_v<Error, std::remove_cv_t<E>>>>
  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE Expected(Unexpected<E> unexpected) : data_(Any(std::move(unexpected).error())) {}

  TVM_FFI_INLINE int32_t type_index() const noexcept { return data_.type_index(); }

  TVM_FFI_INLINE bool is_ok() const noexcept {
    return data_.type_index() != TypeIndex::kTVMFFIError;
  }

  TVM_FFI_INLINE bool is_err() const noexcept {
    return data_.type_index() == TypeIndex::kTVMFFIError;
  }

  TVM_FFI_INLINE bool has_value() const noexcept { return is_ok(); }

  TVM_FFI_INLINE void value() const& {
    if (TVM_FFI_PREDICT_FALSE(is_err())) {
      throw details::AnyUnsafe::CopyFromAnyViewAfterCheck<Error>(data_);
    }
  }

  TVM_FFI_INLINE void value() && {
    if (TVM_FFI_PREDICT_FALSE(is_err())) {
      throw details::AnyUnsafe::MoveFromAnyAfterCheck<Error>(std::move(data_));
    }
  }

  TVM_FFI_INLINE Error error() const& {
    if (is_ok()) {
      TVM_FFI_THROW(RuntimeError) << "Bad expected access: contains value, not error";
    }
    return details::AnyUnsafe::CopyFromAnyViewAfterCheck<Error>(data_);
  }

  TVM_FFI_INLINE Error error() && {
    if (is_ok()) {
      TVM_FFI_THROW(RuntimeError) << "Bad expected access: contains value, not error";
    }
    return details::AnyUnsafe::MoveFromAnyAfterCheck<Error>(std::move(data_));
  }

 private:
  TVM_FFI_INLINE Expected(UnsafeInit, TVMFFIAny raw) noexcept
      : data_(details::AnyUnsafe::MoveTVMFFIAnyRawToAny(raw)) {}

  friend struct details::ExpectedUnsafe;

  Any data_;  // Invariant: holds FFI None on success or an Error.
};

namespace details {

struct ExpectedUnsafe {
  template <typename T>
  TVM_FFI_INLINE static Expected<T> MoveFromTVMFFIAny(TVMFFIAny raw) {
    return Expected<T>(UnsafeInit{}, raw);
  }

  template <typename T>
  TVM_FFI_INLINE static TVMFFIAny MoveToTVMFFIAny(Expected<T>&& result) {
    return AnyUnsafe::MoveAnyToTVMFFIAny(std::move(result.data_));
  }

  template <typename T>
  TVM_FFI_INLINE static Any&& GetData(Expected<T>& result) noexcept {
    return std::move(result.data_);
  }

  template <typename T>
  TVM_FFI_INLINE static const Any& GetData(const Expected<T>& result) noexcept {
    return result.data_;
  }

  template <typename T, typename U>
  TVM_FFI_INLINE static T ValueAs(const Expected<U>& result) {
    if constexpr (std::is_void_v<T>) {
      static_assert(std::is_void_v<U>, "ExpectedUnsafe::ValueAs<void> requires an Expected<void>");
      result.value();
    } else {
      const Any& data = result.data_;
      if (TVM_FFI_PREDICT_TRUE(data.type_index() != TypeIndex::kTVMFFIError)) {
        return AnyUnsafe::CopyFromAnyViewAfterCheck<T>(data);
      }
      throw AnyUnsafe::CopyFromAnyViewAfterCheck<Error>(data);
    }
  }
};

class UnexpectedReturnHelper {
 public:
  TVM_FFI_INLINE explicit UnexpectedReturnHelper(Unexpected<Error>&& value) noexcept
      : value_(std::move(value)) {}

  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE operator TVMFFIAny() && noexcept {
    return ExpectedUnsafe::MoveToTVMFFIAny(Expected<Any>(std::move(value_)));
  }

  template <typename T>
  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE operator Expected<T>() && noexcept {
    return std::move(value_);
  }

 private:
  Unexpected<Error> value_;
};

template <typename T>
class ExpectedReturnHelper {
 public:
  TVM_FFI_INLINE explicit ExpectedReturnHelper(Expected<T>&& value) noexcept
      : value_(std::move(value)) {}

  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE operator TVMFFIAny() && noexcept {
    return ExpectedUnsafe::MoveToTVMFFIAny(std::move(value_));
  }

  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  TVM_FFI_INLINE operator Expected<T>() && noexcept { return std::move(value_); }

 private:
  Expected<T> value_;
};

}  // namespace details

// TypeTraits specialization for Expected<T>
template <typename T>
inline constexpr bool use_default_type_traits_v<Expected<T>> = false;

template <typename T>
struct TypeTraits<Expected<T>> : public TypeTraitsBase {
  TVM_FFI_INLINE static void CopyToAnyView(const Expected<T>& src, TVMFFIAny* result) {
    if (src.is_err()) {
      TypeTraits<Error>::CopyToAnyView(src.error(), result);
    } else {
      TypeTraits<T>::CopyToAnyView(src.value(), result);
    }
  }

  TVM_FFI_INLINE static void MoveToAny(Expected<T> src, TVMFFIAny* result) {
    if (src.is_err()) {
      TypeTraits<Error>::MoveToAny(std::move(src).error(), result);
    } else {
      TypeTraits<T>::MoveToAny(std::move(src).value(), result);
    }
  }

  TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
    return TypeTraits<T>::CheckAnyStrict(src) || TypeTraits<Error>::CheckAnyStrict(src);
  }

  TVM_FFI_INLINE static Expected<T> CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
    if (TypeTraits<T>::CheckAnyStrict(src)) {
      return TypeTraits<T>::CopyFromAnyViewAfterCheck(src);
    }
    return TypeTraits<Error>::CopyFromAnyViewAfterCheck(src);
  }

  TVM_FFI_INLINE static Expected<T> MoveFromAnyAfterCheck(TVMFFIAny* src) {
    if (TypeTraits<T>::CheckAnyStrict(src)) {
      return TypeTraits<T>::MoveFromAnyAfterCheck(src);
    }
    return TypeTraits<Error>::MoveFromAnyAfterCheck(src);
  }

  TVM_FFI_INLINE static std::optional<Expected<T>> TryCastFromAnyView(const TVMFFIAny* src) {
    if (auto opt = TypeTraits<T>::TryCastFromAnyView(src)) {
      return Expected<T>(*std::move(opt));
    }
    if (auto opt_err = TypeTraits<Error>::TryCastFromAnyView(src)) {
      return Expected<T>(*std::move(opt_err));
    }
    return std::nullopt;
  }

  TVM_FFI_INLINE static std::string TypeStr() {
    return "Expected<" + TypeTraits<T>::TypeStr() + ">";
  }

  TVM_FFI_INLINE static std::string TypeSchema() {
    return R"({"type":"Expected","args":[)" + details::TypeSchema<T>::v() +
           R"(,{"type":"ffi.Error"}]})";
  }
};

template <>
struct TypeTraits<Expected<void>> : public TypeTraitsBase {
  TVM_FFI_INLINE static void CopyToAnyView(const Expected<void>& src, TVMFFIAny* result) {
    if (src.is_err()) {
      TypeTraits<Error>::CopyToAnyView(src.error(), result);
    } else {
      TypeTraits<std::nullptr_t>::CopyToAnyView(nullptr, result);
    }
  }

  TVM_FFI_INLINE static void MoveToAny(Expected<void> src, TVMFFIAny* result) {
    if (src.is_err()) {
      TypeTraits<Error>::MoveToAny(std::move(src).error(), result);
    } else {
      TypeTraits<std::nullptr_t>::MoveToAny(nullptr, result);
    }
  }

  TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
    return TypeTraits<std::nullptr_t>::CheckAnyStrict(src) ||
           TypeTraits<Error>::CheckAnyStrict(src);
  }

  TVM_FFI_INLINE static Expected<void> CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
    if (TypeTraits<std::nullptr_t>::CheckAnyStrict(src)) {
      return Expected<void>();
    }
    return TypeTraits<Error>::CopyFromAnyViewAfterCheck(src);
  }

  TVM_FFI_INLINE static Expected<void> MoveFromAnyAfterCheck(TVMFFIAny* src) {
    if (TypeTraits<std::nullptr_t>::CheckAnyStrict(src)) {
      return Expected<void>();
    }
    return TypeTraits<Error>::MoveFromAnyAfterCheck(src);
  }

  TVM_FFI_INLINE static std::optional<Expected<void>> TryCastFromAnyView(const TVMFFIAny* src) {
    if (TypeTraits<std::nullptr_t>::CheckAnyStrict(src)) {
      return Expected<void>();
    }
    if (auto opt_err = TypeTraits<Error>::TryCastFromAnyView(src)) {
      return Expected<void>(*std::move(opt_err));
    }
    return std::nullopt;
  }

  TVM_FFI_INLINE static std::string TypeStr() { return "Expected<void>"; }

  TVM_FFI_INLINE static std::string TypeSchema() {
    return R"({"type":"Expected","args":[)" + TypeTraits<std::nullptr_t>::TypeSchema() +
           R"(,{"type":"ffi.Error"}]})";
  }
};

// check macros for expected land
// RET_ means the macro contains return; UNEXPECTED is a value and the caller writes return.
// While guards preserve an enclosing if/else. Errors record file/line/function only, without
// a stack walk.
namespace details {

class UnexpectedBuilder {
 public:
  UnexpectedBuilder(const char* kind, const char* file, int line, const char* function)
      : kind_(kind), file_(file), line_(line), function_(function) {}

  template <typename T>
  UnexpectedBuilder&& operator<<(T&& value) && {
    stream_ << std::forward<T>(value);
    return std::move(*this);
  }

  UnexpectedBuilder&& operator<<(std::ostream& (*manipulator)(std::ostream&)) && {
    manipulator(stream_);
    return std::move(*this);
  }

  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  operator Unexpected<Error>() && {
    std::ostringstream backtrace;
    backtrace << "  File \"" << file_ << "\", line " << line_ << ", in " << function_ << '\n';
    return Unexpected(Error(kind_, stream_.str(), backtrace.str()));
  }

  template <typename T>
  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
  operator Expected<T>() && {
    // A return expression cannot chain builder -> Unexpected -> Expected conversions.
    return static_cast<Unexpected<Error>>(std::move(*this));
  }

 private:
  const char* kind_;
  const char* file_;
  int line_;
  const char* function_;
  std::ostringstream stream_;
};

}  // namespace details

#define TVM_FFI_UNEXPECTED(ErrorKind) \
  ::tvm::ffi::details::UnexpectedBuilder(#ErrorKind, __FILE__, __LINE__, TVM_FFI_FUNC_SIG)

#define TVM_FFI_RET_CHECK(cond, ErrorKind) \
  while (TVM_FFI_PREDICT_FALSE(!(cond)))   \
  return TVM_FFI_UNEXPECTED(ErrorKind) << "Check failed: (" #cond ") is false: "

#define TVM_FFI_RET_CHECK_BINARY_OP(name, op, x, y, ErrorKind)               \
  while (auto __tvm_ffi_log_err = /* NOLINT(bugprone-reserved-identifier) */ \
         ::tvm::ffi::details::LogCheck##name(x, y))                          \
  return TVM_FFI_UNEXPECTED(ErrorKind)                                       \
         << "Check failed: " << #x " " #op " " #y << (*__tvm_ffi_log_err) << ": "

#define TVM_FFI_RET_CHECK_LT(x, y, ErrorKind) TVM_FFI_RET_CHECK_BINARY_OP(_LT, <, x, y, ErrorKind)
#define TVM_FFI_RET_CHECK_GT(x, y, ErrorKind) TVM_FFI_RET_CHECK_BINARY_OP(_GT, >, x, y, ErrorKind)
#define TVM_FFI_RET_CHECK_LE(x, y, ErrorKind) TVM_FFI_RET_CHECK_BINARY_OP(_LE, <=, x, y, ErrorKind)
#define TVM_FFI_RET_CHECK_GE(x, y, ErrorKind) TVM_FFI_RET_CHECK_BINARY_OP(_GE, >=, x, y, ErrorKind)
#define TVM_FFI_RET_CHECK_EQ(x, y, ErrorKind) TVM_FFI_RET_CHECK_BINARY_OP(_EQ, ==, x, y, ErrorKind)
#define TVM_FFI_RET_CHECK_NE(x, y, ErrorKind) TVM_FFI_RET_CHECK_BINARY_OP(_NE, !=, x, y, ErrorKind)

#define TVM_FFI_RET_ICHECK(x) TVM_FFI_RET_CHECK(x, InternalError)
#define TVM_FFI_RET_ICHECK_LT(x, y) TVM_FFI_RET_CHECK_LT(x, y, InternalError)
#define TVM_FFI_RET_ICHECK_GT(x, y) TVM_FFI_RET_CHECK_GT(x, y, InternalError)
#define TVM_FFI_RET_ICHECK_LE(x, y) TVM_FFI_RET_CHECK_LE(x, y, InternalError)
#define TVM_FFI_RET_ICHECK_GE(x, y) TVM_FFI_RET_CHECK_GE(x, y, InternalError)
#define TVM_FFI_RET_ICHECK_EQ(x, y) TVM_FFI_RET_CHECK_EQ(x, y, InternalError)
#define TVM_FFI_RET_ICHECK_NE(x, y) TVM_FFI_RET_CHECK_NE(x, y, InternalError)

}  // namespace ffi
}  // namespace tvm
#endif  // TVM_FFI_EXPECTED_H_