Program Listing for File op.h#
↰ Return to documentation for file (tvm/ir/op.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_IR_OP_H_
#define TVM_IR_OP_H_
#include <tvm/ffi/container/list.h>
#include <tvm/ffi/expected.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/native_function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/env_func.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/type.h>
#include <string>
#include <type_traits>
#include <utility>
namespace tvm {
template <typename>
class OpAttrMap;
class ArgumentInfoObj : public ffi::Object {
public:
ffi::String name;
ffi::String doc;
static void RegisterReflection() {
namespace refl = ffi::reflection;
refl::ObjectDef<ArgumentInfoObj>()
.def_ro("name", &ArgumentInfoObj::name)
.def_ro("doc", &ArgumentInfoObj::doc);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.ArgumentInfo", ArgumentInfoObj, ffi::Object);
};
class ArgumentInfo : public ffi::ObjectRef {
public:
explicit ArgumentInfo(ffi::UnsafeInit tag, ffi::ObjectPtr<ArgumentInfoObj> n)
: ffi::ObjectRef(tag) {
data_ = std::move(n);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(ArgumentInfo, ffi::ObjectRef, ArgumentInfoObj);
};
class OpObj : public ExprObj {
private:
// Dense process-local index into attribute columns; never serialized.
uint32_t index_{0};
public:
ffi::String name;
ffi::String doc;
ffi::Array<ArgumentInfo> args_info;
ffi::Array<ArgumentInfo> ty_args_info;
ffi::Optional<ArgumentInfo> var_args_info;
ffi::Optional<ArgumentInfo> var_ty_args_info;
ffi::String attrs_type_key;
uint32_t attrs_type_index{0};
ffi::Any validator;
bool validator_is_custom{false};
TVM_TIRX_DLL static void RegisterReflection();
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindUniqueInstance;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.Op", OpObj, ExprObj);
private:
friend class OpRegistry;
friend class OpDef;
friend class Op;
template <typename>
friend class OpAttrMap;
};
class Op : public Expr {
public:
explicit Op(ffi::ObjectPtr<OpObj> n) : Expr(std::move(n)) {
TVM_FFI_CHECK(defined(), ValueError) << "Op expects a defined OpObj";
}
template <typename ValueType>
static OpAttrMap<ValueType> GetAttrMap(const ffi::String& attr_name);
template <typename ValueType>
static OpAttrMap<ValueType> GetAttrMap(ffi::reflection::AttrKey<ValueType> key);
TVM_TIRX_DLL static bool HasAttrMap(const ffi::String& attr_name);
template <typename ValueType>
static bool HasAttrMap(ffi::reflection::AttrKey<ValueType> key) {
return HasAttrMap(key.name);
}
TVM_TIRX_DLL static Op Get(const ffi::String& op_name);
TVM_TIRX_DLL static ffi::Array<ffi::String> ListNames();
TVM_FFI_INLINE void Validate(const CallObj* call) const {
const auto& validator = get()->validator;
if (validator != nullptr) {
if (TVM_FFI_PREDICT_FALSE(!call)) {
ThrowInvalidCall(get());
}
using View = ffi::reflection::NativeFunctionView<void(const CallObj*)>;
// set_validator stores only an owning NativeFunction with this signature.
ffi::detail::AnyUnsafe::CopyFromAnyViewAfterCheck<View>(validator).CallExpected(call).value();
}
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Op, Expr, OpObj);
private:
friend class OpDef;
TVM_TIRX_DLL static void Expose(const ffi::String& op_name);
TVM_TIRX_DLL static void ThrowInvalidCall(const OpObj* op);
TVM_TIRX_DLL static ffi::List<ffi::Any> GetAttrColumn(const ffi::String& attr_name);
};
namespace op_attr {
inline constexpr ffi::reflection::AttrKey<
ffi::reflection::NativeFunctionView<Type(const CallObj* call)>>
kInferType{"FInferType"};
inline constexpr ffi::reflection::AttrKey<Type> kFixedReturnType{"TFixedReturnType"};
} // namespace op_attr
// Compatibility names; new callers can deduce the value type from the key.
using FInferType = decltype(op_attr::kInferType)::ValueType;
using TFixedReturnType = decltype(op_attr::kFixedReturnType)::ValueType;
namespace sig {
struct SignatureTrait {
static constexpr size_t kArgsCount = 0;
static constexpr size_t kTyArgsCount = 0;
static constexpr size_t kVarArgsCount = 0;
static constexpr size_t kVarTyArgsCount = 0;
static constexpr size_t kCallAttrsCount = 0;
};
struct ValidateState {
const CallObj* call;
const OpObj* op;
size_t args_index = 0;
size_t ty_args_index = 0;
};
namespace detail {
template <typename T>
inline bool ReportTypeMismatch(ffi::Expected<void>* out, const ValidateState* state, bool type_arg,
size_t index, ffi::AnyView actual) {
const auto& fixed = type_arg ? state->op->ty_args_info : state->op->args_info;
const auto& tail = type_arg ? state->op->var_ty_args_info : state->op->var_args_info;
std::string name;
if (index < fixed.size()) {
name = fixed[index]->name;
} else if (tail.has_value()) {
name = tail.value()->name;
}
std::string name_clause = name.empty() ? "" : " (`" + name + "`)";
TVMFFIAny raw = actual.CopyToTVMFFIAny();
*out = TVM_FFI_UNEXPECTED(TypeError)
<< "Op `" << state->op->name << "`: `" << (type_arg ? "Call.ty_args[" : "Call.args[")
<< index << "]`" << name_clause << " expected `" << ffi::TypeTraits<T>::TypeStr()
<< "`, got `" << ffi::TypeTraits<T>::GetMismatchTypeInfo(&raw) << "`.";
return false;
}
} // namespace detail
template <typename T = Expr>
struct arg : SignatureTrait {
static_assert(std::is_base_of_v<Expr, T>, "arg<T> expects an expression view");
static constexpr size_t kArgsCount = 1;
ffi::String name;
ffi::String doc;
explicit arg(ffi::String name, ffi::String doc = "")
: name(std::move(name)), doc(std::move(doc)) {}
void CollectMetadata(OpObj* op) const {
auto info = ffi::make_object<ArgumentInfoObj>();
info->name = name;
info->doc = doc;
op->args_info.push_back(ArgumentInfo(ffi::UnsafeInit{}, std::move(info)));
}
static bool Validate(ValidateState* state, ffi::Expected<void>* out) {
if constexpr (!std::is_same_v<T, Expr>) {
ffi::AnyView value(state->call->args.GetArrayObj()->begin()[state->args_index]);
if (TVM_FFI_PREDICT_FALSE(value == nullptr ||
!ffi::detail::AnyUnsafe::CheckAnyViewStrict<T>(value))) {
return detail::ReportTypeMismatch<T>(out, state, false, state->args_index, value);
}
}
++state->args_index;
return true;
}
};
template <typename T = Expr>
struct var_args : SignatureTrait {
static_assert(std::is_base_of_v<Expr, T>, "var_args<T> expects an expression view");
static constexpr size_t kVarArgsCount = 1;
ffi::String name;
ffi::String doc;
explicit var_args(ffi::String name, ffi::String doc = "")
: name(std::move(name)), doc(std::move(doc)) {}
void CollectMetadata(OpObj* op) const {
auto info = ffi::make_object<ArgumentInfoObj>();
info->name = name;
info->doc = doc;
op->var_args_info = ArgumentInfo(ffi::UnsafeInit{}, std::move(info));
}
static bool Validate(ValidateState* state, ffi::Expected<void>* out) {
if constexpr (!std::is_same_v<T, Expr>) {
const ffi::Any* values = state->call->args.GetArrayObj()->begin();
for (; state->args_index != state->call->args.size(); ++state->args_index) {
ffi::AnyView value(values[state->args_index]);
if (TVM_FFI_PREDICT_FALSE(value == nullptr ||
!ffi::detail::AnyUnsafe::CheckAnyViewStrict<T>(value))) {
return detail::ReportTypeMismatch<T>(out, state, false, state->args_index, value);
}
}
} else {
state->args_index = state->call->args.size();
}
return true;
}
};
template <typename T = Type>
struct ty_arg : SignatureTrait {
static_assert(std::is_base_of_v<Type, T>, "ty_arg<T> expects a type view");
static constexpr size_t kTyArgsCount = 1;
ffi::String name;
ffi::String doc;
explicit ty_arg(ffi::String name, ffi::String doc = "")
: name(std::move(name)), doc(std::move(doc)) {}
void CollectMetadata(OpObj* op) const {
auto info = ffi::make_object<ArgumentInfoObj>();
info->name = name;
info->doc = doc;
op->ty_args_info.push_back(ArgumentInfo(ffi::UnsafeInit{}, std::move(info)));
}
static bool Validate(ValidateState* state, ffi::Expected<void>* out) {
if constexpr (!std::is_same_v<T, Type>) {
ffi::AnyView value(state->call->ty_args.GetArrayObj()->begin()[state->ty_args_index]);
if (TVM_FFI_PREDICT_FALSE(value == nullptr ||
!ffi::detail::AnyUnsafe::CheckAnyViewStrict<T>(value))) {
return detail::ReportTypeMismatch<T>(out, state, true, state->ty_args_index, value);
}
}
++state->ty_args_index;
return true;
}
};
template <typename T = Type>
struct var_ty_args : SignatureTrait {
static_assert(std::is_base_of_v<Type, T>, "var_ty_args<T> expects a type view");
static constexpr size_t kVarTyArgsCount = 1;
ffi::String name;
ffi::String doc;
explicit var_ty_args(ffi::String name, ffi::String doc = "")
: name(std::move(name)), doc(std::move(doc)) {}
void CollectMetadata(OpObj* op) const {
auto info = ffi::make_object<ArgumentInfoObj>();
info->name = name;
info->doc = doc;
op->var_ty_args_info = ArgumentInfo(ffi::UnsafeInit{}, std::move(info));
}
static bool Validate(ValidateState* state, ffi::Expected<void>* out) {
if constexpr (!std::is_same_v<T, Type>) {
const ffi::Any* values = state->call->ty_args.GetArrayObj()->begin();
for (; state->ty_args_index != state->call->ty_args.size(); ++state->ty_args_index) {
ffi::AnyView value(values[state->ty_args_index]);
if (TVM_FFI_PREDICT_FALSE(value == nullptr ||
!ffi::detail::AnyUnsafe::CheckAnyViewStrict<T>(value))) {
return detail::ReportTypeMismatch<T>(out, state, true, state->ty_args_index, value);
}
}
} else {
state->ty_args_index = state->call->ty_args.size();
}
return true;
}
};
template <typename T>
struct call_attrs : SignatureTrait {
static_assert(std::is_base_of_v<AttrsObj, T>, "call_attrs<T> expects an attribute node type");
static constexpr size_t kCallAttrsCount = 1;
void CollectMetadata(OpObj* op) const {
uint32_t index = T::RuntimeTypeIndex();
op->attrs_type_key = T::_type_key;
op->attrs_type_index = index;
}
static bool Validate(ValidateState* state, ffi::Expected<void>* out) {
ffi::AnyView attrs(state->call->attrs);
if (TVM_FFI_PREDICT_FALSE(!state->call->attrs.has_value() ||
!ffi::detail::AnyUnsafe::CheckAnyViewStrict<const T*>(attrs))) {
TVMFFIAny raw = attrs.CopyToTVMFFIAny();
std::string actual = state->call->attrs.has_value()
? "`" + ffi::TypeTraits<const T*>::GetMismatchTypeInfo(&raw) + "`"
: "None";
*out = TVM_FFI_UNEXPECTED(TypeError)
<< "Op `" << state->op->name << "`: Call.attrs expected `"
<< ffi::TypeTraits<const T*>::TypeStr() << "`, got " << actual;
return false;
}
return true;
}
};
} // namespace sig
class OpDef {
public:
TVM_TIRX_DLL explicit OpDef(const ffi::String& name);
TVM_TIRX_DLL OpDef(const ffi::String& name, const ffi::String& doc);
OpDef(const OpDef&) = delete;
OpDef& operator=(const OpDef&) = delete;
OpDef(OpDef&&) = delete;
OpDef& operator=(OpDef&&) = delete;
Op op() const { return op_; }
inline void expose() const { Op::Expose(op_->name); }
TVM_TIRX_DLL OpDef& add_arg(const ffi::String& name, const ffi::String& doc);
template <typename AttrsType>
OpDef& attrs_type() {
uint32_t index = AttrsType::RuntimeTypeIndex();
get()->attrs_type_key = AttrsType::_type_key;
get()->attrs_type_index = index;
return *this;
}
TVM_TIRX_DLL OpDef& set_attrs_type_key(const ffi::String& key);
TVM_TIRX_DLL OpDef& add_ty_arg(const ffi::String& name, const ffi::String& doc);
template <typename... Specs>
OpDef& signature(const Specs&... specs) {
auto updated = ffi::make_object<OpObj>();
(ApplySignatureTrait(updated.get(), specs), ...);
if (op_->validator == nullptr) {
using View = ffi::reflection::NativeFunctionView<void(const CallObj*)>;
set_validator(View::FromNative<&ValidateSignature<Specs...>>());
get()->validator_is_custom = false;
}
OpObj* target = get();
target->args_info = std::move(updated->args_info);
target->ty_args_info = std::move(updated->ty_args_info);
target->var_args_info = std::move(updated->var_args_info);
target->var_ty_args_info = std::move(updated->var_ty_args_info);
target->attrs_type_key = std::move(updated->attrs_type_key);
target->attrs_type_index = updated->attrs_type_index;
return *this;
}
template <typename ValueType>
OpDef& set_attr(const ffi::String& attr_name, const ValueType& value, bool override = false) {
UpdateAttr(attr_name, ffi::Any(value), override);
return *this;
}
template <typename ValueType, typename Value,
std::enable_if_t<(std::is_convertible_v<Value&&, ValueType> &&
((!std::is_same_v<std::decay_t<Value>, ffi::Any> &&
!std::is_same_v<std::decay_t<Value>, ffi::AnyView>) ||
std::is_same_v<ValueType, ffi::Any> ||
std::is_same_v<ValueType, ffi::AnyView>)),
int> = 0>
OpDef& set_attr(ffi::reflection::AttrKey<ValueType> key, Value&& value, bool override = false) {
ValueType typed_value(std::forward<Value>(value));
return set_attr(key.name, typed_value, override);
}
template <auto Fn, typename ValueType>
OpDef& set_native_fn_attr(ffi::reflection::AttrKey<ValueType> key, bool override = false) {
return set_attr(key, ValueType::template FromNative<Fn>(), override);
}
OpDef& set_validator(ffi::reflection::NativeFunctionView<void(const CallObj*)> validator,
bool override = false) {
TVM_FFI_CHECK(override || op_->validator == nullptr, ValueError)
<< "Validator of " << op_->name << " is already registered";
get()->validator = ffi::reflection::NativeFunction<void(const CallObj*)>::From(validator);
get()->validator_is_custom = true;
return *this;
}
TVM_TIRX_DLL OpDef& reset_attr(const ffi::String& attr_name);
template <typename ValueType>
OpDef& reset_attr(ffi::reflection::AttrKey<ValueType> key) {
return reset_attr(key.name);
}
private:
template <typename Spec>
static void ApplySignatureTrait(OpObj* op, const Spec& spec) {
static_assert(std::is_base_of_v<sig::SignatureTrait, Spec>, "Unknown signature descriptor");
spec.CollectMetadata(op);
}
template <typename... Specs>
TVM_FFI_INLINE static ffi::Expected<void> ValidateSignature(const CallObj* call) noexcept {
static_assert((std::is_base_of_v<sig::SignatureTrait, Specs> && ...),
"Unknown signature descriptor");
constexpr size_t n_args = (size_t{0} + ... + Specs::kArgsCount);
constexpr size_t n_ty_args = (size_t{0} + ... + Specs::kTyArgsCount);
constexpr size_t n_var_args = (size_t{0} + ... + Specs::kVarArgsCount);
constexpr size_t n_var_ty_args = (size_t{0} + ... + Specs::kVarTyArgsCount);
constexpr size_t n_attrs = (size_t{0} + ... + Specs::kCallAttrsCount);
static_assert(n_var_args <= 1 && n_var_ty_args <= 1 && n_attrs <= 1,
"Duplicate signature tail or call_attrs");
const OpObj* op = call ? call->op.as<OpObj>() : nullptr;
if (TVM_FFI_PREDICT_FALSE(!op)) {
return TVM_FFI_UNEXPECTED(TypeError) << "Invalid Op Call";
}
if (TVM_FFI_PREDICT_FALSE(!call->args.defined() || !call->ty_args.defined())) {
return TVM_FFI_UNEXPECTED(TypeError) << "Op `" << op->name << "`: invalid Call";
}
ffi::Expected<void> result;
auto* out = &result;
if (TVM_FFI_PREDICT_FALSE(n_var_args ? call->args.size() < n_args
: call->args.size() != n_args)) {
*out = TVM_FFI_UNEXPECTED(TypeError)
<< "Op `" << op->name << "`: Call.args expected " << (n_var_args ? "at least " : "")
<< n_args << (n_args == 1 ? " argument" : " arguments") << ", got "
<< call->args.size();
return result;
}
if (TVM_FFI_PREDICT_FALSE(n_var_ty_args ? call->ty_args.size() < n_ty_args
: call->ty_args.size() != n_ty_args)) {
*out = TVM_FFI_UNEXPECTED(TypeError)
<< "Op `" << op->name << "`: Call.ty_args expected "
<< (n_var_ty_args ? "at least " : "") << n_ty_args
<< (n_ty_args == 1 ? " type argument" : " type arguments") << ", got "
<< call->ty_args.size();
return result;
}
if constexpr (sizeof...(Specs) != 0) {
sig::ValidateState state{call, op};
if (TVM_FFI_PREDICT_FALSE(!(Specs::Validate(&state, out) && ...))) {
return result;
}
}
return result;
}
OpObj* get() { return const_cast<OpObj*>(op_.operator->()); }
TVM_TIRX_DLL void UpdateAttr(const ffi::String& attr_name, ffi::Any value, bool override);
Op op_;
};
template <typename ValueType>
class OpAttrMap {
public:
int count(const Op& op) const {
return op->index_ < column_.size() && column_[op->index_] != nullptr;
}
ValueType operator[](const Op& op) const {
TVM_FFI_ICHECK(count(op)) << "Attribute " << attr_name_ << " has not been registered for "
<< op->name;
if constexpr (std::is_same_v<ValueType, ffi::Any>) {
return column_[op->index_];
} else {
return column_[op->index_].template cast<ValueType>();
}
}
ValueType get(const Op& op, ValueType def_value) const {
return count(op) ? (*this)[op] : def_value;
}
ValueType get(const Expr& expr, ValueType def_value) const {
TVM_FFI_ICHECK(expr.defined());
if (const auto* op = expr.as<OpObj>()) {
return get(ffi::GetRef<Op>(op), def_value);
}
return def_value;
}
private:
friend class Op;
OpAttrMap(ffi::List<ffi::Any> column, ffi::String attr_name)
: column_(std::move(column)), attr_name_(std::move(attr_name)) {}
ffi::List<ffi::Any> column_;
ffi::String attr_name_;
};
template <typename ValueType>
OpAttrMap<ValueType> Op::GetAttrMap(const ffi::String& attr_name) {
return OpAttrMap<ValueType>(GetAttrColumn(attr_name), attr_name);
}
template <typename ValueType>
OpAttrMap<ValueType> Op::GetAttrMap(ffi::reflection::AttrKey<ValueType> key) {
return GetAttrMap<ValueType>(key.name);
}
} // namespace tvm
#endif // TVM_IR_OP_H_