326 lines
11 KiB
C++
326 lines
11 KiB
C++
/*
|
|
* 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/ir/attrs.h
|
|
* \brief Helpers for attribute objects.
|
|
*
|
|
* This module enables declaration of named attributes
|
|
* which support default value setup and bound checking.
|
|
*
|
|
* \sa AttrsNode
|
|
*/
|
|
#ifndef TVM_IR_ATTRS_H_
|
|
#define TVM_IR_ATTRS_H_
|
|
|
|
#include <tvm/ffi/container/map.h>
|
|
#include <tvm/ffi/extra/structural_equal.h>
|
|
#include <tvm/ffi/extra/structural_hash.h>
|
|
#include <tvm/ffi/function.h>
|
|
#include <tvm/ffi/reflection/registry.h>
|
|
#include <tvm/ir/cow.h>
|
|
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
namespace tvm {
|
|
|
|
/*!
|
|
* \brief Base class of all attribute class
|
|
* \sa Attrs
|
|
*/
|
|
class AttrsNode : public ffi::Object {
|
|
public:
|
|
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
|
|
TVM_FFI_DECLARE_OBJECT_INFO("ir.Attrs", AttrsNode, ffi::Object);
|
|
};
|
|
|
|
/*!
|
|
* \brief Managed reference to AttrsNode.
|
|
* \sa AttrsNode
|
|
*/
|
|
class Attrs : public ffi::ObjectRef {
|
|
public:
|
|
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Attrs, ffi::ObjectRef, AttrsNode);
|
|
};
|
|
|
|
/*!
|
|
* \brief Specialized attribute type that is backed by a map.
|
|
* The DictAttrsNode implements the Attrs behavior,
|
|
* its fields are directly accessible via object.field_name
|
|
* like other normal nodes.
|
|
*/
|
|
class DictAttrsNode : public AttrsNode {
|
|
public:
|
|
/*! \brief internal attrs map */
|
|
ffi::Map<ffi::String, ffi::Any> dict;
|
|
|
|
static void RegisterReflection() {
|
|
namespace rfl = ffi::reflection;
|
|
rfl::ObjectDef<DictAttrsNode>().def_ro("__dict__", &DictAttrsNode::dict);
|
|
}
|
|
|
|
// type info
|
|
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.DictAttrs", DictAttrsNode, AttrsNode);
|
|
};
|
|
|
|
/*!
|
|
* \brief Managed reference to DictAttrsNode
|
|
* \sa DictAttrsNode.
|
|
*
|
|
* \note DictAttrs is NOTNULLABLE: every instance must hold a backing
|
|
* DictAttrsNode. The class enforces this end-to-end by:
|
|
* - the default constructor (no args) allocating an empty backing,
|
|
* - the copy/move ctors and assignments leaving the moved-from
|
|
* instance in a defined-but-empty state rather than null,
|
|
* - the FFI type traits rejecting None at deserialization boundaries
|
|
* (since `_type_is_nullable == false`), and
|
|
* - the FFI lambda for ``ir.IRModule`` explicitly normalizing a
|
|
* missing/None attrs argument to ``DictAttrs()`` before forwarding
|
|
* to the C++ constructor.
|
|
* Callers (including third-party code via templates like ``WithAttr``)
|
|
* can therefore rely on ``attrs->dict`` being safe to dereference
|
|
* without a ``.defined()`` guard.
|
|
*/
|
|
class DictAttrs : public Attrs {
|
|
public:
|
|
/*!
|
|
* \brief Construct a DictAttrs backed by DictAttrsNode.
|
|
*
|
|
* The no-argument form constructs an empty (but always defined) DictAttrs.
|
|
* \param dict The attributes.
|
|
*/
|
|
explicit DictAttrs(ffi::Map<ffi::String, Any> dict = {}) {
|
|
ffi::ObjectPtr<DictAttrsNode> n = ffi::make_object<DictAttrsNode>();
|
|
n->dict = std::move(dict);
|
|
data_ = std::move(n);
|
|
}
|
|
|
|
/*!
|
|
* \brief Move constructor that leaves the source in a defined-but-empty
|
|
* state rather than null, preserving the NOTNULLABLE invariant
|
|
* even after `std::move`.
|
|
*/
|
|
DictAttrs(DictAttrs&& other) noexcept : Attrs(ffi::UnsafeInit{}) {
|
|
data_ = std::move(other.data_);
|
|
other.data_ = ffi::make_object<DictAttrsNode>();
|
|
}
|
|
|
|
/*!
|
|
* \brief Move assignment that leaves the source in a defined-but-empty
|
|
* state rather than null, preserving the NOTNULLABLE invariant
|
|
* even after `std::move`.
|
|
*/
|
|
DictAttrs& operator=(DictAttrs&& other) noexcept {
|
|
if (this != &other) {
|
|
data_ = std::move(other.data_);
|
|
other.data_ = ffi::make_object<DictAttrsNode>();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
// Explicit copy ctor/assign defaults. Declaring the move members above
|
|
// would otherwise suppress the implicit copy members.
|
|
DictAttrs(const DictAttrs& other) = default;
|
|
DictAttrs& operator=(const DictAttrs& other) = default;
|
|
|
|
// Utils for accessing attributes
|
|
/*!
|
|
* \brief Get a function attribute.
|
|
*
|
|
* \param attr_key The attribute key.
|
|
* \param default_value The default value if the key does not exist, defaults to nullptr.
|
|
*
|
|
* \return The result
|
|
*
|
|
* \tparam TOBjectRef the expected object type.
|
|
* \throw Error if the key exists but the value does not match TObjectRef
|
|
*
|
|
* \code
|
|
*
|
|
* void GetAttrExample(const BaseFunc& f) {
|
|
* auto value = f->attrs.GetAttr<int64_t>("AttrKey", 0);
|
|
* }
|
|
*
|
|
* \endcode
|
|
*/
|
|
template <typename TObjectRef>
|
|
ffi::Optional<TObjectRef> GetAttr(
|
|
const std::string& attr_key,
|
|
ffi::Optional<TObjectRef> default_value = ffi::Optional<TObjectRef>(std::nullopt)) const {
|
|
const DictAttrsNode* node = get();
|
|
auto it = node->dict.find(attr_key);
|
|
if (it != node->dict.end()) {
|
|
return (*it).second.cast<TObjectRef>();
|
|
} else {
|
|
return default_value;
|
|
}
|
|
}
|
|
// variant that uses TObjectRef to enable implicit conversion to default value.
|
|
template <typename TObjectRef>
|
|
ffi::Optional<TObjectRef> GetAttr(const std::string& attr_key, TObjectRef default_value) const {
|
|
return GetAttr<TObjectRef>(attr_key, ffi::Optional<TObjectRef>(default_value));
|
|
}
|
|
/*!
|
|
* \brief Check whether the function has an non-zero integer attr.
|
|
*
|
|
* This function can be used to check whether an optional
|
|
* attribute mark(e.g. inline) exists.
|
|
*
|
|
* \param attr_key The key to the attribute.
|
|
* \return The check result.
|
|
*
|
|
* \code
|
|
*
|
|
* void HasNonzeroAttrExample(const BaseFunc& f) {
|
|
* if (f->HasNonzeroAttr(attr::kInline)) {
|
|
* // inline the function.
|
|
* }
|
|
* }
|
|
*
|
|
* \endcode
|
|
*/
|
|
bool HasNonzeroAttr(const std::string& attr_key) const {
|
|
return GetAttr<int64_t>(attr_key, 0).value_or(0) != 0;
|
|
}
|
|
|
|
// Inline-expand TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE here, minus
|
|
// the default copy/move it normally injects (we define our own move members
|
|
// above so the moved-from instance stays defined-but-empty).
|
|
explicit DictAttrs(::tvm::ffi::UnsafeInit tag) : Attrs(tag) {}
|
|
using __PtrType =
|
|
std::conditional_t<DictAttrsNode::_type_mutable, DictAttrsNode*, const DictAttrsNode*>;
|
|
__PtrType operator->() const { return static_cast<__PtrType>(data_.get()); }
|
|
__PtrType get() const { return static_cast<__PtrType>(data_.get()); }
|
|
static constexpr bool _type_is_nullable = false;
|
|
using ContainerType = DictAttrsNode;
|
|
TVM_DEFINE_OBJECT_REF_COW_METHOD(DictAttrsNode);
|
|
};
|
|
|
|
/*!
|
|
* \brief Copy the function or module, but overrides
|
|
* the attribute value key with the value.
|
|
*
|
|
* \param input The thing to annotate (BaseFunc or IRModule)
|
|
* \param attr_key The attribute key.
|
|
* \param attr_value The value attribute value.
|
|
*
|
|
* \tparam TFunc The corresponding function or module type.
|
|
*
|
|
* \returns The new function or module with updated attributes.
|
|
*
|
|
* \note This function performs copy on write optimization for func and module.
|
|
* If we move a uniquely referenced func or module into WithAttr,
|
|
* then no additional copy will be performed.
|
|
*
|
|
* This is also why we make it as a function instead of a member function
|
|
* and why we pass by value in the first argument.
|
|
*
|
|
* \code
|
|
*
|
|
* // Recommended way to trigger copy on write
|
|
* func = WithAttr(std::move(func), "key1", value1);
|
|
* func = WithAttr(std::move(func), "key2", value2);
|
|
*
|
|
* \endcode
|
|
*/
|
|
template <typename TFunc>
|
|
inline TFunc WithAttr(TFunc input, const std::string& attr_key, Any attr_value) {
|
|
using TNode = typename TFunc::ContainerType;
|
|
static_assert(TNode::_type_final, "Can only operate on the leaf nodes");
|
|
TNode* node = input.CopyOnWrite();
|
|
// node->attrs is NOTNULLABLE by contract, but defend against a caller
|
|
// that left a moved-from DictAttrs in place by re-initializing here.
|
|
if (!node->attrs.defined()) node->attrs = DictAttrs();
|
|
node->attrs.CopyOnWrite()->dict.Set(attr_key, std::move(attr_value));
|
|
return input;
|
|
}
|
|
|
|
/*!
|
|
* \brief Copy the function or module, but overrides the attributes with the entries from \p attrs.
|
|
*
|
|
* \param input The thing to annotate (BaseFunc or IRModule)
|
|
* \param attrs Key/values attributes to add to \p input.
|
|
*
|
|
* \tparam TFunc The corresponding function or module type.
|
|
*
|
|
* \returns The new function or module with updated attributes.
|
|
*/
|
|
template <typename TFunc>
|
|
inline TFunc WithAttrs(TFunc input, ffi::Map<ffi::String, Any> attrs) {
|
|
using TNode = typename TFunc::ContainerType;
|
|
static_assert(TNode::_type_final, "Can only operate on the leaf nodes");
|
|
if (attrs.empty()) return input;
|
|
TNode* node = input.CopyOnWrite();
|
|
// node->attrs is NOTNULLABLE by contract, but defend against a caller
|
|
// that left a moved-from DictAttrs in place by re-initializing here.
|
|
if (!node->attrs.defined()) node->attrs = DictAttrs();
|
|
auto* dict_node = node->attrs.CopyOnWrite();
|
|
for (const auto& [k, v] : attrs) {
|
|
dict_node->dict.Set(k, v);
|
|
}
|
|
return input;
|
|
}
|
|
|
|
/*!
|
|
* \brief Copy the function or module, but removes the specified
|
|
* attribute.
|
|
*
|
|
* \param input The thing to annotate (BaseFunc or IRModule)
|
|
* \param attr_key The attribute key.
|
|
*
|
|
* \tparam TFunc The corresponding function or module type.
|
|
*
|
|
* \returns The new function or module with removed attribute.
|
|
*
|
|
* \note This function performs copy on write optimization for func and module.
|
|
* If we move a uniquely referenced func or module into WithoutAttr,
|
|
* then no additional copy will be performed.
|
|
*
|
|
* This is also why we make it as a function instead of a member function
|
|
* and why we pass by value in the first argument.
|
|
*
|
|
* \code
|
|
*
|
|
* // Recommended way to trigger copy on write
|
|
* func = WithoutAttr(std::move(func), "key1");
|
|
* func = WithoutAttr(std::move(func), "key2");
|
|
*
|
|
* \endcode
|
|
*/
|
|
template <typename TFunc>
|
|
inline TFunc WithoutAttr(TFunc input, const std::string& attr_key) {
|
|
using TNode = typename TFunc::ContainerType;
|
|
static_assert(TNode::_type_final, "Can only operate on the leaf nodes");
|
|
TNode* node = input.CopyOnWrite();
|
|
// node->attrs is NOTNULLABLE by contract, but defend against a caller
|
|
// that left a moved-from DictAttrs in place; nothing to erase from an
|
|
// empty dict.
|
|
if (!node->attrs.defined()) {
|
|
node->attrs = DictAttrs();
|
|
return input;
|
|
}
|
|
node->attrs.CopyOnWrite()->dict.erase(attr_key);
|
|
return input;
|
|
}
|
|
|
|
} // namespace tvm
|
|
#endif // TVM_IR_ATTRS_H_
|