Program Listing for File error.h

Program Listing for File error.h#

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

  TVM_FFI_THROW(RuntimeError) << "error message";
}
/*
 * 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.
 */

/*
 * \file tvm/ffi/error.h
 * \brief Error handling component.
 */
#ifndef TVM_FFI_ERROR_H_
#define TVM_FFI_ERROR_H_

#include <tvm/ffi/base_details.h>
#include <tvm/ffi/c_api.h>
#include <tvm/ffi/memory.h>
#include <tvm/ffi/object.h>

#include <cstring>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#ifndef TVM_FFI_USE_LIBBACKTRACE
#define TVM_FFI_USE_LIBBACKTRACE 1
#endif

#ifndef TVM_FFI_BACKTRACE_ON_SEGFAULT
#define TVM_FFI_BACKTRACE_ON_SEGFAULT 1
#endif

#ifndef TVM_FFI_ALWAYS_LOG_BEFORE_THROW
#define TVM_FFI_ALWAYS_LOG_BEFORE_THROW 0
#endif

namespace tvm {
namespace ffi {

struct EnvErrorAlreadySet : public std::exception {};

class ErrorObj : public Object, public TVMFFIErrorCell {
 public:
  static constexpr const int32_t _type_index = TypeIndex::kTVMFFIError;
  TVM_FFI_DECLARE_OBJECT_INFO_STATIC("ffi.Error", ErrorObj, Object);
};

namespace details {
class ErrorObjFromStd : public ErrorObj {
 public:
  ErrorObjFromStd(std::string kind, std::string message, std::string backtrace)
      : kind_data_(kind), message_data_(message), backtrace_data_(backtrace) {
    this->kind = TVMFFIByteArray{kind_data_.data(), kind_data_.length()};
    this->message = TVMFFIByteArray{message_data_.data(), message_data_.length()};
    this->backtrace = TVMFFIByteArray{backtrace_data_.data(), backtrace_data_.length()};
    this->update_backtrace = UpdateBacktrace;
  }

 private:
  static void UpdateBacktrace(TVMFFIObjectHandle self, const TVMFFIByteArray* backtrace_str,
                              int32_t update_mode) {
    ErrorObjFromStd* obj = static_cast<ErrorObjFromStd*>(self);
    if (update_mode == kTVMFFIBacktraceUpdateModeReplace) {
      obj->backtrace_data_.resize(backtrace_str->size);
      std::memcpy(obj->backtrace_data_.data(), backtrace_str->data, backtrace_str->size);
      obj->backtrace = TVMFFIByteArray{obj->backtrace_data_.data(), obj->backtrace_data_.length()};
    } else {
      obj->backtrace_data_.append(backtrace_str->data, backtrace_str->size);
      obj->backtrace = TVMFFIByteArray{obj->backtrace_data_.data(), obj->backtrace_data_.length()};
    }
  }

  std::string kind_data_;
  std::string message_data_;
  std::string backtrace_data_;
};
}  // namespace details

class Error : public ObjectRef, public std::exception {
 public:
  Error(std::string kind, std::string message, std::string backtrace) {
    data_ = make_object<details::ErrorObjFromStd>(kind, message, backtrace);
  }

  Error(std::string kind, std::string message, const TVMFFIByteArray* backtrace)
      : Error(kind, message, std::string(backtrace->data, backtrace->size)) {}

  std::string kind() const {
    ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
    return std::string(obj->kind.data, obj->kind.size);
  }

  std::string message() const {
    ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
    return std::string(obj->message.data, obj->message.size);
  }

  std::string backtrace() const {
    ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
    return std::string(obj->backtrace.data, obj->backtrace.size);
  }

  std::string TracebackMostRecentCallLast() const {
    // add placeholder for the first line
    std::vector<int64_t> line_breakers = {-1};
    ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
    for (size_t i = 0; i < obj->backtrace.size; i++) {
      if (obj->backtrace.data[i] == '\n') {
        line_breakers.push_back(i);
      }
    }
    std::string result;
    result.reserve(obj->backtrace.size);
    for (size_t i = line_breakers.size() - 1; i > 0; --i) {
      int64_t line_start = line_breakers[i - 1] + 1;
      int64_t line_end = line_breakers[i];
      if (line_start == line_end) continue;
      result.append(obj->backtrace.data + line_start, line_end - line_start);
      result.append("\n");
    }
    return result;
  }

  void UpdateBacktrace(const TVMFFIByteArray* backtrace_str, int32_t update_mode) {
    ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
    obj->update_backtrace(obj, backtrace_str, update_mode);
  }

  const char* what() const noexcept(true) override {
    thread_local std::string what_data;
    ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
    what_data = (std::string("Traceback (most recent call last):\n") +
                 TracebackMostRecentCallLast() + std::string(obj->kind.data, obj->kind.size) +
                 std::string(": ") + std::string(obj->message.data, obj->message.size) + '\n');
    return what_data.c_str();
  }

  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Error, ObjectRef, ErrorObj);
};

namespace details {

class ErrorBuilder {
 public:
  explicit ErrorBuilder(std::string kind, std::string backtrace, bool log_before_throw)
      : kind_(kind), backtrace_(backtrace), log_before_throw_(log_before_throw) {}

  explicit ErrorBuilder(std::string kind, const TVMFFIByteArray* backtrace, bool log_before_throw)
      : ErrorBuilder(kind, std::string(backtrace->data, backtrace->size), log_before_throw) {}

// MSVC disable warning in error builder as it is exepected
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4722)
#endif
  // avoid inline to reduce binary size, error throw path do not need to be fast
  [[noreturn]] ~ErrorBuilder() noexcept(false) {
    ::tvm::ffi::Error error(std::move(kind_), stream_.str(), std::move(backtrace_));
    if (log_before_throw_) {
      std::cerr << error.what();
    }
    throw error;
  }
#ifdef _MSC_VER
#pragma warning(pop)
#endif

  std::ostringstream& stream() { return stream_; }

 protected:
  std::string kind_;
  std::ostringstream stream_;
  std::string backtrace_;
  bool log_before_throw_;
};

}  // namespace details

#define TVM_FFI_THROW(ErrorKind)                                                              \
  ::tvm::ffi::details::ErrorBuilder(#ErrorKind,                                               \
                                    TVMFFIBacktrace(__FILE__, __LINE__, TVM_FFI_FUNC_SIG, 0), \
                                    TVM_FFI_ALWAYS_LOG_BEFORE_THROW)                          \
      .stream()

#define TVM_FFI_LOG_AND_THROW(ErrorKind)                                          \
  ::tvm::ffi::details::ErrorBuilder(                                              \
      #ErrorKind, TVMFFIBacktrace(__FILE__, __LINE__, TVM_FFI_FUNC_SIG, 0), true) \
      .stream()

// Glog style checks with TVM_FFI prefix
// NOTE: we explicitly avoid glog style generic macros (LOG/CHECK) in tvm ffi
// to avoid potential conflict of downstream users who might have their own GLOG style macros
namespace details {

template <typename X, typename Y>
TVM_FFI_INLINE std::unique_ptr<std::string> LogCheckFormat(const X& x, const Y& y) {
  std::ostringstream os;
  os << " (" << x << " vs. " << y << ") ";  // CHECK_XX(x, y) requires x and y can be serialized to
                                            // string. Use CHECK(x OP y) otherwise.
  return std::make_unique<std::string>(os.str());
}

#define TVM_FFI_CHECK_FUNC(name, op)                                                   \
  template <typename X, typename Y>                                                    \
  TVM_FFI_INLINE std::unique_ptr<std::string> LogCheck##name(const X& x, const Y& y) { \
    if (x op y) return nullptr;                                                        \
    return LogCheckFormat(x, y);                                                       \
  }                                                                                    \
  TVM_FFI_INLINE std::unique_ptr<std::string> LogCheck##name(int x, int y) {           \
    return LogCheck##name<int, int>(x, y);                                             \
  }

// Inline _Pragma in macros does not work reliably on old version of MSVC and
// GCC. We wrap all comparisons in a function so that we can use #pragma to
// silence bad comparison warnings.
#if defined(__GNUC__) || defined(__clang__)  // GCC and Clang
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"
#elif defined(_MSC_VER)  // MSVC
#pragma warning(push)
#pragma warning(disable : 4389)  // '==' : signed/unsigned mismatch
#endif

TVM_FFI_CHECK_FUNC(_LT, <)
TVM_FFI_CHECK_FUNC(_GT, >)
TVM_FFI_CHECK_FUNC(_LE, <=)
TVM_FFI_CHECK_FUNC(_GE, >=)
TVM_FFI_CHECK_FUNC(_EQ, ==)
TVM_FFI_CHECK_FUNC(_NE, !=)

#if defined(__GNUC__) || defined(__clang__)  // GCC and Clang
#pragma GCC diagnostic pop
#elif defined(_MSC_VER)  // MSVC
#pragma warning(pop)
#endif
}  // namespace details

#define TVM_FFI_ICHECK_BINARY_OP(name, op, x, y)                        \
  if (auto __tvm__log__err = ::tvm::ffi::details::LogCheck##name(x, y)) \
  TVM_FFI_THROW(InternalError) << "Check failed: " << #x " " #op " " #y << *__tvm__log__err << ": "

#define TVM_FFI_ICHECK(x) \
  if (!(x)) TVM_FFI_THROW(InternalError) << "Check failed: (" #x << ") is false: "

#define TVM_FFI_CHECK(cond, ErrorKind) \
  if (!(cond)) TVM_FFI_THROW(ErrorKind) << "Check failed: (" #cond << ") is false: "

#define TVM_FFI_ICHECK_LT(x, y) TVM_FFI_ICHECK_BINARY_OP(_LT, <, x, y)
#define TVM_FFI_ICHECK_GT(x, y) TVM_FFI_ICHECK_BINARY_OP(_GT, >, x, y)
#define TVM_FFI_ICHECK_LE(x, y) TVM_FFI_ICHECK_BINARY_OP(_LE, <=, x, y)
#define TVM_FFI_ICHECK_GE(x, y) TVM_FFI_ICHECK_BINARY_OP(_GE, >=, x, y)
#define TVM_FFI_ICHECK_EQ(x, y) TVM_FFI_ICHECK_BINARY_OP(_EQ, ==, x, y)
#define TVM_FFI_ICHECK_NE(x, y) TVM_FFI_ICHECK_BINARY_OP(_NE, !=, x, y)
#define TVM_FFI_ICHECK_NOTNULL(x)                                                 \
  ((x) == nullptr ? TVM_FFI_THROW(InternalError) << "Check not null: " #x << ' ', \
   (x)            : (x))  // NOLINT(*)
}  // namespace ffi
}  // namespace tvm
#endif  // TVM_FFI_ERROR_H_