blob: 24e268494ae616f975f8d080dedc016e2b18a7b9 [file] [log] [blame]
/*
* 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/runtime/container.h
* \brief Common POD(plain old data) container types.
*/
#ifndef TVM_RUNTIME_CONTAINER_H_
#define TVM_RUNTIME_CONTAINER_H_
#include <dmlc/logging.h>
#include <tvm/runtime/memory.h>
#include <tvm/runtime/object.h>
#include <algorithm>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <string>
#include <unordered_map>
// We use c++14 std::experimental::string_view for optimizing hash computation
// only right now, its usage is limited in this file. Any broader usage of
// std::experiment in our core codebase is discouraged and needs community
// discussion for each use case. Reference for feature test macros of
// string_view:
// https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations
// https://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros
#if defined(__cpp_lib_experimental_string_view) && __cpp_lib_experimental_string_view >= 201411
#define TVM_USE_CXX14_STRING_VIEW_HASH 1
#else
#define TVM_USE_CXX14_STRING_VIEW_HASH 0
#endif
// Tested with clang version 9.0.1 and c++17. It will detect string_view support
// correctly.
#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606
#define TVM_USE_CXX17_STRING_VIEW_HASH 1
#else
#define TVM_USE_CXX17_STRING_VIEW_HASH 0
#endif
#if TVM_USE_CXX17_STRING_VIEW_HASH
#include <string_view>
#elif TVM_USE_CXX14_STRING_VIEW_HASH
#include <experimental/string_view>
#endif
#include <type_traits>
#include <utility>
#include <vector>
namespace llvm {
// String to llvm object compatibility.
class StringRef;
} // namespace llvm
namespace tvm {
namespace runtime {
// Forward declare TVMArgValue
class TVMArgValue;
/*! \brief String-aware ObjectRef equal functor */
struct ObjectHash {
/*!
* \brief Calculate the hash code of an ObjectRef
* \param a The given ObjectRef
* \return Hash code of a, string hash for strings and pointer address otherwise.
*/
size_t operator()(const ObjectRef& a) const;
};
/*! \brief String-aware ObjectRef hash functor */
struct ObjectEqual {
/*!
* \brief Check if the two ObjectRef are equal
* \param a One ObjectRef
* \param b The other ObjectRef
* \return String equality if both are strings, pointer address equality otherwise.
*/
bool operator()(const ObjectRef& a, const ObjectRef& b) const;
};
/*!
* \brief Base template for classes with array like memory layout.
*
* It provides general methods to access the memory. The memory
* layout is ArrayType + [ElemType]. The alignment of ArrayType
* and ElemType is handled by the memory allocator.
*
* \tparam ArrayType The array header type, contains object specific metadata.
* \tparam ElemType The type of objects stored in the array right after
* ArrayType.
*
* \code
* // Example usage of the template to define a simple array wrapper
* class ArrayObj : public InplaceArrayBase<ArrayObj, Elem> {
* public:
* // Wrap EmplaceInit to initialize the elements
* template <typename Iterator>
* void Init(Iterator begin, Iterator end) {
* size_t num_elems = std::distance(begin, end);
* auto it = begin;
* this->size = 0;
* for (size_t i = 0; i < num_elems; ++i) {
* InplaceArrayBase::EmplaceInit(i, *it++);
* this->size++;
* }
* }
* }
*
* void test_function() {
* vector<Elem> fields;
* auto ptr = make_inplace_array_object<ArrayObj, Elem>(fields.size());
* ptr->Init(fields.begin(), fields.end());
*
* // Access the 0th element in the array.
* assert(ptr->operator[](0) == fields[0]);
* }
*
* \endcode
*/
template <typename ArrayType, typename ElemType>
class InplaceArrayBase {
public:
/*!
* \brief Access element at index
* \param idx The index of the element.
* \return Const reference to ElemType at the index.
*/
const ElemType& operator[](size_t idx) const {
size_t size = Self()->GetSize();
CHECK_LT(idx, size) << "Index " << idx << " out of bounds " << size << "\n";
return *(reinterpret_cast<ElemType*>(AddressOf(idx)));
}
/*!
* \brief Access element at index
* \param idx The index of the element.
* \return Reference to ElemType at the index.
*/
ElemType& operator[](size_t idx) {
size_t size = Self()->GetSize();
CHECK_LT(idx, size) << "Index " << idx << " out of bounds " << size << "\n";
return *(reinterpret_cast<ElemType*>(AddressOf(idx)));
}
/*!
* \brief Destroy the Inplace Array Base object
*/
~InplaceArrayBase() {
if (!(std::is_standard_layout<ElemType>::value && std::is_trivial<ElemType>::value)) {
size_t size = Self()->GetSize();
for (size_t i = 0; i < size; ++i) {
ElemType* fp = reinterpret_cast<ElemType*>(AddressOf(i));
fp->ElemType::~ElemType();
}
}
}
protected:
/*!
* \brief Construct a value in place with the arguments.
*
* \tparam Args Type parameters of the arguments.
* \param idx Index of the element.
* \param args Arguments to construct the new value.
*
* \note Please make sure ArrayType::GetSize returns 0 before first call of
* EmplaceInit, and increment GetSize by 1 each time EmplaceInit succeeds.
*/
template <typename... Args>
void EmplaceInit(size_t idx, Args&&... args) {
void* field_ptr = AddressOf(idx);
new (field_ptr) ElemType(std::forward<Args>(args)...);
}
/*!
* \brief Return the self object for the array.
*
* \return Pointer to ArrayType.
*/
inline ArrayType* Self() const {
return static_cast<ArrayType*>(const_cast<InplaceArrayBase*>(this));
}
/*!
* \brief Return the raw pointer to the element at idx.
*
* \param idx The index of the element.
* \return Raw pointer to the element.
*/
void* AddressOf(size_t idx) const {
static_assert(
alignof(ArrayType) % alignof(ElemType) == 0 && sizeof(ArrayType) % alignof(ElemType) == 0,
"The size and alignment of ArrayType should respect "
"ElemType's alignment.");
size_t kDataStart = sizeof(ArrayType);
ArrayType* self = Self();
char* data_start = reinterpret_cast<char*>(self) + kDataStart;
return data_start + idx * sizeof(ElemType);
}
};
/*!
* \brief iterator adapter that adapts TIter to return another type.
* \tparam Converter a struct that contains converting function
* \tparam TIter the content iterator type.
*/
template <typename Converter, typename TIter>
class IterAdapter {
public:
using difference_type = typename std::iterator_traits<TIter>::difference_type;
using value_type = typename Converter::ResultType;
using pointer = typename Converter::ResultType*;
using reference = typename Converter::ResultType&;
using iterator_category = typename std::iterator_traits<TIter>::iterator_category;
explicit IterAdapter(TIter iter) : iter_(iter) {}
IterAdapter& operator++() {
++iter_;
return *this;
}
IterAdapter& operator--() {
--iter_;
return *this;
}
IterAdapter operator++(int) {
IterAdapter copy = *this;
++iter_;
return copy;
}
IterAdapter operator--(int) {
IterAdapter copy = *this;
--iter_;
return copy;
}
IterAdapter operator+(difference_type offset) const { return IterAdapter(iter_ + offset); }
IterAdapter operator-(difference_type offset) const { return IterAdapter(iter_ - offset); }
template <typename T = IterAdapter>
typename std::enable_if<std::is_same<iterator_category, std::random_access_iterator_tag>::value,
typename T::difference_type>::type inline
operator-(const IterAdapter& rhs) const {
return iter_ - rhs.iter_;
}
bool operator==(IterAdapter other) const { return iter_ == other.iter_; }
bool operator!=(IterAdapter other) const { return !(*this == other); }
const value_type operator*() const { return Converter::convert(*iter_); }
private:
TIter iter_;
};
/*!
* \brief iterator adapter that adapts TIter to return another type.
* \tparam Converter a struct that contains converting function
* \tparam TIter the content iterator type.
*/
template <typename Converter, typename TIter>
class ReverseIterAdapter {
public:
using difference_type = typename std::iterator_traits<TIter>::difference_type;
using value_type = typename Converter::ResultType;
using pointer = typename Converter::ResultType*;
using reference = typename Converter::ResultType&; // NOLINT(*)
using iterator_category = typename std::iterator_traits<TIter>::iterator_category;
explicit ReverseIterAdapter(TIter iter) : iter_(iter) {}
ReverseIterAdapter& operator++() {
--iter_;
return *this;
}
ReverseIterAdapter& operator--() {
++iter_;
return *this;
}
ReverseIterAdapter& operator++(int) {
ReverseIterAdapter copy = *this;
--iter_;
return copy;
}
ReverseIterAdapter& operator--(int) {
ReverseIterAdapter copy = *this;
++iter_;
return copy;
}
ReverseIterAdapter operator+(difference_type offset) const {
return ReverseIterAdapter(iter_ - offset);
}
template <typename T = ReverseIterAdapter>
typename std::enable_if<std::is_same<iterator_category, std::random_access_iterator_tag>::value,
typename T::difference_type>::type inline
operator-(const ReverseIterAdapter& rhs) const {
return rhs.iter_ - iter_;
}
bool operator==(ReverseIterAdapter other) const { return iter_ == other.iter_; }
bool operator!=(ReverseIterAdapter other) const { return !(*this == other); }
const value_type operator*() const { return Converter::convert(*iter_); }
private:
TIter iter_;
};
/*! \brief array node content in array */
class ArrayNode : public Object, public InplaceArrayBase<ArrayNode, ObjectRef> {
public:
/*! \return The size of the array */
size_t size() const { return this->size_; }
/*!
* \brief Read i-th element from array.
* \param i The index
* \return the i-th element.
*/
const ObjectRef at(int64_t i) const { return this->operator[](i); }
/*! \return begin constant iterator */
const ObjectRef* begin() const { return static_cast<ObjectRef*>(InplaceArrayBase::AddressOf(0)); }
/*! \return end constant iterator */
const ObjectRef* end() const { return begin() + size_; }
/*! \brief Release reference to all the elements */
void clear() { ShrinkBy(size_); }
/*!
* \brief Set i-th element of the array in-place
* \param i The index
* \param item The value to be set
*/
void SetItem(int64_t i, ObjectRef item) { this->operator[](i) = std::move(item); }
/*!
* \brief Constructs a container and copy from another
* \param cap The capacity of the container
* \param from Source of the copy
* \return Ref-counted ArrayNode requested
*/
static ObjectPtr<ArrayNode> CopyFrom(int64_t cap, ArrayNode* from) {
int64_t size = from->size_;
CHECK_GE(cap, size) << "ValueError: not enough capacity";
ObjectPtr<ArrayNode> p = ArrayNode::Empty(cap);
ObjectRef* write = p->MutableBegin();
ObjectRef* read = from->MutableBegin();
// To ensure exception safety, size is only incremented after the initialization succeeds
for (int64_t& i = p->size_ = 0; i < size; ++i) {
new (write++) ObjectRef(*read++);
}
return p;
}
/*!
* \brief Constructs a container and move from another
* \param cap The capacity of the container
* \param from Source of the move
* \return Ref-counted ArrayNode requested
*/
static ObjectPtr<ArrayNode> MoveFrom(int64_t cap, ArrayNode* from) {
int64_t size = from->size_;
CHECK_GE(cap, size) << "ValueError: not enough capacity";
ObjectPtr<ArrayNode> p = ArrayNode::Empty(cap);
ObjectRef* write = p->MutableBegin();
ObjectRef* read = from->MutableBegin();
// To ensure exception safety, size is only incremented after the initialization succeeds
for (int64_t& i = p->size_ = 0; i < size; ++i) {
new (write++) ObjectRef(std::move(*read++));
}
from->size_ = 0;
return p;
}
/*!
* \brief Constructs a container with n elements. Each element is a copy of val
* \param n The size of the container
* \param val The init value
* \return Ref-counted ArrayNode requested
*/
static ObjectPtr<ArrayNode> CreateRepeated(int64_t n, const ObjectRef& val) {
ObjectPtr<ArrayNode> p = ArrayNode::Empty(n);
ObjectRef* itr = p->MutableBegin();
for (int64_t& i = p->size_ = 0; i < n; ++i) {
new (itr++) ObjectRef(val);
}
return p;
}
static constexpr const uint32_t _type_index = TypeIndex::kRuntimeArray;
static constexpr const char* _type_key = "Array";
TVM_DECLARE_FINAL_OBJECT_INFO(ArrayNode, Object);
private:
/*! \return Size of initialized memory, used by InplaceArrayBase. */
size_t GetSize() const { return this->size_; }
/*! \return begin mutable iterator */
ObjectRef* MutableBegin() const {
return static_cast<ObjectRef*>(InplaceArrayBase::AddressOf(0));
}
/*! \return end mutable iterator */
ObjectRef* MutableEnd() const { return MutableBegin() + size_; }
/*!
* \brief Create an ArrayNode with the given capacity.
* \param n Required capacity
* \return Ref-counted ArrayNode requested
*/
static ObjectPtr<ArrayNode> Empty(int64_t n = kInitSize) {
CHECK_GE(n, 0);
ObjectPtr<ArrayNode> p = make_inplace_array_object<ArrayNode, ObjectRef>(n);
p->capacity_ = n;
p->size_ = 0;
return p;
}
/*!
* \brief Inplace-initialize the elements starting idx from [first, last)
* \param idx The starting point
* \param first Begin of iterator
* \param last End of iterator
* \tparam IterType The type of iterator
* \return Self
*/
template <typename IterType>
ArrayNode* InitRange(int64_t idx, IterType first, IterType last) {
ObjectRef* itr = MutableBegin() + idx;
for (; first != last; ++first) {
ObjectRef ref = *first;
new (itr++) ObjectRef(std::move(ref));
}
return this;
}
/*!
* \brief Move elements from right to left, requires src_begin > dst
* \param dst Destination
* \param src_begin The start point of copy (inclusive)
* \param src_end The end point of copy (exclusive)
* \return Self
*/
ArrayNode* MoveElementsLeft(int64_t dst, int64_t src_begin, int64_t src_end) {
ObjectRef* from = MutableBegin() + src_begin;
ObjectRef* to = MutableBegin() + dst;
while (src_begin++ != src_end) {
*to++ = std::move(*from++);
}
return this;
}
/*!
* \brief Move elements from left to right, requires src_begin < dst
* \param dst Destination
* \param src_begin The start point of move (inclusive)
* \param src_end The end point of move (exclusive)
* \return Self
*/
ArrayNode* MoveElementsRight(int64_t dst, int64_t src_begin, int64_t src_end) {
ObjectRef* from = MutableBegin() + src_end;
ObjectRef* to = MutableBegin() + (src_end - src_begin + dst);
while (src_begin++ != src_end) {
*--to = std::move(*--from);
}
return this;
}
/*!
* \brief Enlarges the size of the array
* \param delta Size enlarged, should be positive
* \param val Default value
* \return Self
*/
ArrayNode* EnlargeBy(int64_t delta, const ObjectRef& val = ObjectRef(nullptr)) {
ObjectRef* itr = MutableEnd();
while (delta-- > 0) {
new (itr++) ObjectRef(val);
++size_;
}
return this;
}
/*!
* \brief Shrinks the size of the array
* \param delta Size shrinked, should be positive
* \return Self
*/
ArrayNode* ShrinkBy(int64_t delta) {
ObjectRef* itr = MutableEnd();
while (delta-- > 0) {
(--itr)->ObjectRef::~ObjectRef();
--size_;
}
return this;
}
/*! \brief Number of elements used */
int64_t size_;
/*! \brief Number of elements allocated */
int64_t capacity_;
/*! \brief Initial size of ArrayNode */
static constexpr int64_t kInitSize = 4;
/*! \brief Expansion factor of the Array */
static constexpr int64_t kIncFactor = 2;
// CRTP parent class
friend InplaceArrayBase<ArrayNode, ObjectRef>;
// Reference class
template <typename, typename>
friend class Array;
// To specialize make_object<ArrayNode>
friend ObjectPtr<ArrayNode> make_object<>();
};
/*!
* \brief Array, container representing a contigious sequence of ObjectRefs.
*
* Array implements in-place copy-on-write semantics.
*
* As in typical copy-on-write, a method which would typically mutate the array
* instead opaquely copies the underlying container, and then acts on its copy.
*
* If the array has reference count equal to one, we directly update the
* container in place without copying. This is optimization is sound because
* when the reference count is equal to one this reference is guranteed to be
* the sole pointer to the container.
*
*
* operator[] only provides const access, use Set to mutate the content.
* \tparam T The content ObjectRef type.
*/
template <typename T,
typename = typename std::enable_if<std::is_base_of<ObjectRef, T>::value>::type>
class Array : public ObjectRef {
public:
using value_type = T;
// constructors
/*!
* \brief default constructor
*/
Array() { data_ = ArrayNode::Empty(); }
/*!
* \brief move constructor
* \param other source
*/
Array(Array<T>&& other) : ObjectRef() { // NOLINT(*)
data_ = std::move(other.data_);
}
/*!
* \brief copy constructor
* \param other source
*/
Array(const Array<T>& other) : ObjectRef() { // NOLINT(*)
data_ = other.data_;
}
/*!
* \brief constructor from pointer
* \param n the container pointer
*/
explicit Array(ObjectPtr<Object> n) : ObjectRef(n) {}
/*!
* \brief Constructor from iterator
* \param first begin of iterator
* \param last end of iterator
* \tparam IterType The type of iterator
*/
template <typename IterType>
Array(IterType first, IterType last) {
Assign(first, last);
}
/*!
* \brief constructor from initializer list
* \param init The initializer list
*/
Array(std::initializer_list<T> init) { // NOLINT(*)
Assign(init.begin(), init.end());
}
/*!
* \brief constructor from vector
* \param init The vector
*/
Array(const std::vector<T>& init) { // NOLINT(*)
Assign(init.begin(), init.end());
}
/*!
* \brief Constructs a container with n elements. Each element is a copy of val
* \param n The size of the container
* \param val The init value
*/
explicit Array(const size_t n, const T& val) { data_ = ArrayNode::CreateRepeated(n, val); }
/*!
* \brief move assign operator
* \param other The source of assignment
* \return reference to self.
*/
Array<T>& operator=(Array<T>&& other) {
data_ = std::move(other.data_);
return *this;
}
/*!
* \brief copy assign operator
* \param other The source of assignment
* \return reference to self.
*/
Array<T>& operator=(const Array<T>& other) {
data_ = other.data_;
return *this;
}
public:
// iterators
struct ValueConverter {
using ResultType = T;
static T convert(const ObjectRef& n) { return DowncastNoCheck<T>(n); }
};
using iterator = IterAdapter<ValueConverter, const ObjectRef*>;
using reverse_iterator = ReverseIterAdapter<ValueConverter, const ObjectRef*>;
/*! \return begin iterator */
iterator begin() const { return iterator(GetArrayNode()->begin()); }
/*! \return end iterator */
iterator end() const { return iterator(GetArrayNode()->end()); }
/*! \return rbegin iterator */
reverse_iterator rbegin() const {
// ArrayNode::end() is never nullptr
return reverse_iterator(GetArrayNode()->end() - 1);
}
/*! \return rend iterator */
reverse_iterator rend() const {
// ArrayNode::begin() is never nullptr
return reverse_iterator(GetArrayNode()->begin() - 1);
}
public:
// const methods in std::vector
/*!
* \brief Immutably read i-th element from array.
* \param i The index
* \return the i-th element.
*/
const T operator[](int64_t i) const {
ArrayNode* p = GetArrayNode();
CHECK(p != nullptr) << "ValueError: cannot index a null array";
CHECK(0 <= i && i < p->size_) << "IndexError: indexing " << i << " on an array of size "
<< p->size_;
return DowncastNoCheck<T>(*(p->begin() + i));
}
/*! \return The size of the array */
size_t size() const {
ArrayNode* p = GetArrayNode();
return p == nullptr ? 0 : GetArrayNode()->size_;
}
/*! \return The capacity of the array */
size_t capacity() const {
ArrayNode* p = GetArrayNode();
return p == nullptr ? 0 : GetArrayNode()->capacity_;
}
/*! \return Whether array is empty */
bool empty() const { return size() == 0; }
/*! \return The first element of the array */
const T front() const {
ArrayNode* p = GetArrayNode();
CHECK(p != nullptr) << "ValueError: cannot index a null array";
CHECK_GT(p->size_, 0) << "IndexError: cannot index an empty array";
return DowncastNoCheck<T>(*(p->begin()));
}
/*! \return The last element of the array */
const T back() const {
ArrayNode* p = GetArrayNode();
CHECK(p != nullptr) << "ValueError: cannot index a null array";
CHECK_GT(p->size_, 0) << "IndexError: cannot index an empty array";
return DowncastNoCheck<T>(*(p->end() - 1));
}
public:
// mutation in std::vector, implements copy-on-write
/*!
* \brief push a new item to the back of the list
* \param item The item to be pushed.
*/
void push_back(const T& item) {
ArrayNode* p = CopyOnWrite(1);
p->EmplaceInit(p->size_++, item);
}
/*!
* \brief Insert an element into the given position
* \param position An iterator pointing to the insertion point
* \param val The element to insert
*/
void insert(iterator position, const T& val) {
CHECK(data_ != nullptr) << "ValueError: cannot insert a null array";
int64_t idx = std::distance(begin(), position);
int64_t size = GetArrayNode()->size_;
auto addr = CopyOnWrite(1) //
->EnlargeBy(1) //
->MoveElementsRight(idx + 1, idx, size) //
->MutableBegin();
new (addr + idx) ObjectRef(val);
}
/*!
* \brief Insert a range of elements into the given position
* \param position An iterator pointing to the insertion point
* \param first The begin iterator of the range
* \param last The end iterator of the range
*/
template <typename IterType>
void insert(iterator position, IterType first, IterType last) {
if (first == last) {
return;
}
CHECK(data_ != nullptr) << "ValueError: cannot insert a null array";
int64_t idx = std::distance(begin(), position);
int64_t size = GetArrayNode()->size_;
int64_t numel = std::distance(first, last);
CopyOnWrite(numel)
->EnlargeBy(numel)
->MoveElementsRight(idx + numel, idx, size)
->InitRange(idx, first, last);
}
/*! \brief Remove the last item of the list */
void pop_back() {
CHECK(data_ != nullptr) << "ValueError: cannot pop_back because array is null";
int64_t size = GetArrayNode()->size_;
CHECK_GT(size, 0) << "ValueError: cannot pop_back because array is empty";
CopyOnWrite()->ShrinkBy(1);
}
/*!
* \brief Erase an element on the given position
* \param position An iterator pointing to the element to be erased
*/
void erase(iterator position) {
CHECK(data_ != nullptr) << "ValueError: cannot erase a null array";
int64_t st = std::distance(begin(), position);
int64_t size = GetArrayNode()->size_;
CHECK(0 <= st && st < size) << "ValueError: cannot erase at index " << st
<< ", because Array size is " << size;
CopyOnWrite() //
->MoveElementsLeft(st, st + 1, size) //
->ShrinkBy(1);
}
/*!
* \brief Erase a given range of elements
* \param first The begin iterator of the range
* \param last The end iterator of the range
*/
void erase(iterator first, iterator last) {
if (first == last) {
return;
}
CHECK(data_ != nullptr) << "ValueError: cannot erase a null array";
int64_t size = GetArrayNode()->size_;
int64_t st = std::distance(begin(), first);
int64_t ed = std::distance(begin(), last);
CHECK_LT(st, ed) << "ValueError: cannot erase array in range [" << st << ", " << ed << ")";
CHECK(0 <= st && st <= size && 0 <= ed && ed <= size)
<< "ValueError: cannot erase array in range [" << st << ", " << ed << ")"
<< ", because array size is " << size;
CopyOnWrite() //
->MoveElementsLeft(st, ed, size) //
->ShrinkBy(ed - st);
}
/*!
* \brief Resize the array.
* \param n The new size.
*/
void resize(int64_t n) {
CHECK_GE(n, 0) << "ValueError: cannot resize an Array to negative size";
if (data_ == nullptr) {
SwitchContainer(n);
return;
}
int64_t size = GetArrayNode()->size_;
if (size < n) {
CopyOnWrite(n - size)->EnlargeBy(n - size);
} else if (size > n) {
CopyOnWrite()->ShrinkBy(size - n);
}
}
/*!
* \brief Make sure the list has the capacity of at least n
* \param n lower bound of the capacity
*/
void reserve(int64_t n) {
if (data_ == nullptr || n > GetArrayNode()->capacity_) {
SwitchContainer(n);
}
}
/*! \brief Release reference to all the elements */
void clear() {
if (data_ != nullptr) {
ArrayNode* p = CopyOnWrite();
p->clear();
}
}
public:
// Array's own methods
/*!
* \brief set i-th element of the array.
* \param i The index
* \param value The value to be setted.
*/
void Set(int64_t i, T value) {
ArrayNode* p = this->CopyOnWrite();
CHECK(0 <= i && i < p->size_) << "IndexError: indexing " << i << " on an array of size "
<< p->size_;
*(p->MutableBegin() + i) = std::move(value);
}
/*! \return The underlying ArrayNode */
ArrayNode* GetArrayNode() const { return static_cast<ArrayNode*>(data_.get()); }
/*!
* \brief Helper function to apply fmutate to mutate an array.
* \param fmutate The transformation function T -> T.
* \tparam F the type of the mutation function.
* \note This function performs copy on write optimization.
*/
template <typename F>
void MutateByApply(F fmutate) {
if (data_ == nullptr) {
return;
}
struct StackFrame {
ArrayNode* p;
ObjectRef* itr;
int64_t i;
int64_t size;
};
std::unique_ptr<StackFrame> s = std::make_unique<StackFrame>();
s->p = GetArrayNode();
s->itr = s->p->MutableBegin();
s->i = 0;
s->size = s->p->size_;
if (!data_.unique()) {
// Loop invariant: keeps iterating when
// 1) data is not unique
// 2) no elements are actually mutated yet
for (; s->i < s->size; ++s->i, ++s->itr) {
T new_elem = fmutate(DowncastNoCheck<T>(*s->itr));
// do nothing when there is no mutation
if (new_elem.same_as(*s->itr)) {
continue;
}
// loop invariant breaks when the first real mutation happens
// we copy the elements into a new unique array
ObjectPtr<ArrayNode> copy = ArrayNode::CopyFrom(s->p->capacity_, s->p);
s->itr = copy->MutableBegin() + (s->i++);
*s->itr++ = std::move(new_elem);
data_ = std::move(copy);
// make sure `data_` is unique and break
break;
}
}
// when execution comes to this line, it is guaranteed that either
// 1) i == size
// or 2) data_.unique() is true
for (; s->i < s->size; ++s->i, ++s->itr) {
*s->itr = std::move(fmutate(std::move(DowncastNoCheck<T>(std::move(*s->itr)))));
}
}
/*!
* \brief reset the array to content from iterator.
* \param first begin of iterator
* \param last end of iterator
* \tparam IterType The type of iterator
*/
template <typename IterType>
void Assign(IterType first, IterType last) {
int64_t cap = std::distance(first, last);
CHECK_GE(cap, 0) << "ValueError: cannot construct an Array of negative size";
ArrayNode* p = GetArrayNode();
if (p != nullptr && data_.unique() && p->capacity_ >= cap) {
// do not have to make new space
p->clear();
} else {
// create new space
data_ = ArrayNode::Empty(cap);
p = GetArrayNode();
}
// To ensure exception safety, size is only incremented after the initialization succeeds
ObjectRef* itr = p->MutableBegin();
for (int64_t& i = p->size_ = 0; i < cap; ++i, ++first, ++itr) {
new (itr) ObjectRef(*first);
}
}
/*!
* \brief Copy on write semantics
* Do nothing if current handle is the unique copy of the array.
* Otherwise make a new copy of the array to ensure the current handle
* hold a unique copy.
*
* \return Handle to the internal node container(which ganrantees to be unique)
*/
ArrayNode* CopyOnWrite() {
if (data_ == nullptr) {
return SwitchContainer(ArrayNode::kInitSize);
}
if (!data_.unique()) {
return SwitchContainer(capacity());
}
return static_cast<ArrayNode*>(data_.get());
}
/*! \brief specify container node */
using ContainerType = ArrayNode;
private:
/*!
* \brief Implement copy-on-write semantics, and ensures capacity is enough for extra elements.
* \param reserve_extra Number of extra slots needed
* \return ArrayNode pointer to the unique copy
*/
ArrayNode* CopyOnWrite(int64_t reserve_extra) {
ArrayNode* p = GetArrayNode();
if (p == nullptr) {
// necessary to get around the constexpr address issue before c++17
const int64_t kInitSize = ArrayNode::kInitSize;
return SwitchContainer(std::max(kInitSize, reserve_extra));
}
if (p->capacity_ >= p->size_ + reserve_extra) {
return CopyOnWrite();
}
int64_t cap = p->capacity_ * ArrayNode::kIncFactor;
cap = std::max(cap, p->size_ + reserve_extra);
return SwitchContainer(cap);
}
/*!
* \brief Move or copy the ArrayNode to new address with the given capacity
* \param capacity The capacity requirement of the new address
*/
ArrayNode* SwitchContainer(int64_t capacity) {
if (data_ == nullptr) {
data_ = ArrayNode::Empty(capacity);
} else if (data_.unique()) {
data_ = ArrayNode::MoveFrom(capacity, GetArrayNode());
} else {
data_ = ArrayNode::CopyFrom(capacity, GetArrayNode());
}
return static_cast<ArrayNode*>(data_.get());
}
};
/*!
* \brief Concat two Arrays.
* \param lhs first Array to be concatenated.
* \param rhs second Array to be concatenated.
* \return The concatenated Array. Original Arrays are kept unchanged.
*/
template <typename T,
typename = typename std::enable_if<std::is_base_of<ObjectRef, T>::value>::type>
inline Array<T> Concat(Array<T> lhs, const Array<T>& rhs) {
for (const auto& x : rhs) {
lhs.push_back(x);
}
return std::move(lhs);
}
// Specialize make_object<ArrayNode> to make sure it is correct.
template <>
inline ObjectPtr<ArrayNode> make_object() {
return ArrayNode::Empty();
}
/*! \brief An object representing a structure or enumeration. */
class ADTObj : public Object, public InplaceArrayBase<ADTObj, ObjectRef> {
public:
/*! \brief The tag representing the constructor used. */
int32_t tag;
/*! \brief Number of fields in the ADT object. */
uint32_t size;
// The fields of the structure follows directly in memory.
static constexpr const uint32_t _type_index = TypeIndex::kRuntimeADT;
static constexpr const char* _type_key = "runtime.ADT";
TVM_DECLARE_FINAL_OBJECT_INFO(ADTObj, Object);
private:
/*!
* \return The number of elements in the array.
*/
size_t GetSize() const { return size; }
/*!
* \brief Initialize the elements in the array.
*
* \tparam Iterator Iterator type of the array.
* \param begin The begin iterator.
* \param end The end iterator.
*/
template <typename Iterator>
void Init(Iterator begin, Iterator end) {
size_t num_elems = std::distance(begin, end);
this->size = 0;
auto it = begin;
for (size_t i = 0; i < num_elems; ++i) {
InplaceArrayBase::EmplaceInit(i, *it++);
// Only increment size after the initialization succeeds
this->size++;
}
}
friend class ADT;
friend InplaceArrayBase<ADTObj, ObjectRef>;
};
/*! \brief reference to algebraic data type objects. */
class ADT : public ObjectRef {
public:
/*!
* \brief construct an ADT object reference.
* \param tag The tag of the ADT object.
* \param fields The fields of the ADT object.
* \return The constructed ADT object reference.
*/
ADT(int32_t tag, std::vector<ObjectRef> fields) : ADT(tag, fields.begin(), fields.end()){};
/*!
* \brief construct an ADT object reference.
* \param tag The tag of the ADT object.
* \param begin The begin iterator to the start of the fields array.
* \param end The end iterator to the end of the fields array.
* \return The constructed ADT object reference.
*/
template <typename Iterator>
ADT(int32_t tag, Iterator begin, Iterator end) {
size_t num_elems = std::distance(begin, end);
auto ptr = make_inplace_array_object<ADTObj, ObjectRef>(num_elems);
ptr->tag = tag;
ptr->Init(begin, end);
data_ = std::move(ptr);
}
/*!
* \brief construct an ADT object reference.
* \param tag The tag of the ADT object.
* \param init The initializer list of fields.
* \return The constructed ADT object reference.
*/
ADT(int32_t tag, std::initializer_list<ObjectRef> init) : ADT(tag, init.begin(), init.end()){};
/*!
* \brief Access element at index.
*
* \param idx The array index
* \return const ObjectRef
*/
const ObjectRef& operator[](size_t idx) const { return operator->()->operator[](idx); }
/*!
* \brief Return the ADT tag.
*/
int32_t tag() const { return operator->()->tag; }
/*!
* \brief Return the number of fields.
*/
size_t size() const { return operator->()->size; }
/*!
* \brief Construct a tuple object.
*
* \tparam Args Type params of tuple feilds.
* \param args Tuple fields.
* \return ADT The tuple object reference.
*/
template <typename... Args>
static ADT Tuple(Args&&... args) {
return ADT(0, std::forward<Args>(args)...);
}
TVM_DEFINE_OBJECT_REF_METHODS(ADT, ObjectRef, ADTObj);
};
/*! \brief An object representing string. It's POD type. */
class StringObj : public Object {
public:
/*! \brief The pointer to string data. */
const char* data;
/*! \brief The length of the string object. */
uint64_t size;
static constexpr const uint32_t _type_index = TypeIndex::kRuntimeString;
static constexpr const char* _type_key = "runtime.String";
TVM_DECLARE_FINAL_OBJECT_INFO(StringObj, Object);
private:
/*! \brief String object which is moved from std::string container. */
class FromStd;
friend class String;
};
/*!
* \brief Reference to string objects.
*
* \code
*
* // Example to create runtime String reference object from std::string
* std::string s = "hello world";
*
* // You can create the reference from existing std::string
* String ref{std::move(s)};
*
* // You can rebind the reference to another string.
* ref = std::string{"hello world2"};
*
* // You can use the reference as hash map key
* std::unordered_map<String, int32_t> m;
* m[ref] = 1;
*
* // You can compare the reference object with other string objects
* assert(ref == "hello world", true);
*
* // You can convert the reference to std::string again
* string s2 = (string)ref;
*
* \endcode
*/
class String : public ObjectRef {
public:
/*!
* \brief Construct an empty string.
*/
String() : String(std::string()) {}
/*!
* \brief Construct a new String object
*
* \param other The moved/copied std::string object
*
* \note If user passes const reference, it will trigger copy. If it's rvalue,
* it will be moved into other.
*/
String(std::string other); // NOLINT(*)
/*!
* \brief Construct a new String object
*
* \param other a char array.
*/
String(const char* other) // NOLINT(*)
: String(std::string(other)) {}
/*!
* \brief Change the value the reference object points to.
*
* \param other The value for the new String
*
*/
inline String& operator=(std::string other);
/*!
* \brief Change the value the reference object points to.
*
* \param other The value for the new String
*/
inline String& operator=(const char* other);
/*!
* \brief Compares this String object to other
*
* \param other The String to compare with.
*
* \return zero if both char sequences compare equal. negative if this appear
* before other, positive otherwise.
*/
int compare(const String& other) const {
return memncmp(data(), other.data(), size(), other.size());
}
/*!
* \brief Compares this String object to other
*
* \param other The string to compare with.
*
* \return zero if both char sequences compare equal. negative if this appear
* before other, positive otherwise.
*/
int compare(const std::string& other) const {
return memncmp(data(), other.data(), size(), other.size());
}
/*!
* \brief Compares this to other
*
* \param other The character array to compare with.
*
* \return zero if both char sequences compare equal. negative if this appear
* before other, positive otherwise.
*/
int compare(const char* other) const {
return memncmp(data(), other, size(), std::strlen(other));
}
/*!
* \brief Returns a pointer to the char array in the string.
*
* \return const char*
*/
const char* c_str() const { return get()->data; }
/*!
* \brief Return the length of the string
*
* \return size_t string length
*/
size_t size() const {
const auto* ptr = get();
return ptr->size;
}
/*!
* \brief Return the length of the string
*
* \return size_t string length
*/
size_t length() const { return size(); }
/*!
* \brief Retun if the string is empty
*
* \return true if empty, false otherwise.
*/
bool empty() const { return size() == 0; }
/*!
* \brief Return the data pointer
*
* \return const char* data pointer
*/
const char* data() const { return get()->data; }
/*!
* \brief Convert String to an std::string object
*
* \return std::string
*/
operator std::string() const { return std::string{get()->data, size()}; }
// LLVM compatibility function, implemented in src/target/llvm/llvm_common.h
/*!
* \brief Convert String to an llvm::StringRef object
*
* \return llvm::StringRef
*/
inline operator llvm::StringRef() const;
/*!
* \brief Check if a TVMArgValue can be converted to String, i.e. it can be std::string or String
* \param val The value to be checked
* \return A boolean indicating if val can be converted to String
*/
inline static bool CanConvertFrom(const TVMArgValue& val);
/*!
* \brief Hash the binary bytes
* \param data The data pointer
* \param size The size of the bytes.
* \return the hash value.
*/
static size_t HashBytes(const char* data, size_t size) {
// This function falls back to string copy with c++11 compiler and is
// recommended to be compiled with c++14
#if TVM_USE_CXX17_STRING_VIEW_HASH
return std::hash<std::string_view>()(std::string_view(data, size));
#elif TVM_USE_CXX14_STRING_VIEW_HASH
return std::hash<std::experimental::string_view>()(std::experimental::string_view(data, size));
#else
return std::hash<std::string>()(std::string(data, size));
#endif
}
TVM_DEFINE_NOTNULLABLE_OBJECT_REF_METHODS(String, ObjectRef, StringObj);
private:
/*!
* \brief Compare two char sequence
*
* \param lhs Pointers to the char array to compare
* \param rhs Pointers to the char array to compare
* \param lhs_count Length of the char array to compare
* \param rhs_count Length of the char array to compare
* \return int zero if both char sequences compare equal. negative if this
* appear before other, positive otherwise.
*/
static int memncmp(const char* lhs, const char* rhs, size_t lhs_count, size_t rhs_count);
/*!
* \brief Concatenate two char sequences
*
* \param lhs Pointers to the lhs char array
* \param lhs_size The size of the lhs char array
* \param rhs Pointers to the rhs char array
* \param rhs_size The size of the rhs char array
*
* \return The concatenated char sequence
*/
static String Concat(const char* lhs, size_t lhs_size, const char* rhs, size_t rhs_size) {
std::string ret(lhs, lhs_size);
ret.append(rhs, rhs_size);
return String(ret);
}
// Overload + operator
friend String operator+(const String& lhs, const String& rhs);
friend String operator+(const String& lhs, const std::string& rhs);
friend String operator+(const std::string& lhs, const String& rhs);
friend String operator+(const String& lhs, const char* rhs);
friend String operator+(const char* lhs, const String& rhs);
friend struct tvm::runtime::ObjectEqual;
};
/*! \brief An object representing string moved from std::string. */
class StringObj::FromStd : public StringObj {
public:
/*!
* \brief Construct a new FromStd object
*
* \param other The moved/copied std::string object
*
* \note If user passes const reference, it will trigger copy. If it's rvalue,
* it will be moved into other.
*/
explicit FromStd(std::string other) : data_container{other} {}
private:
/*! \brief Container that holds the memory. */
std::string data_container;
friend class String;
};
inline String::String(std::string other) {
auto ptr = make_object<StringObj::FromStd>(std::move(other));
ptr->size = ptr->data_container.size();
ptr->data = ptr->data_container.data();
data_ = std::move(ptr);
}
inline String& String::operator=(std::string other) {
String replace{std::move(other)};
data_.swap(replace.data_);
return *this;
}
inline String& String::operator=(const char* other) { return operator=(std::string(other)); }
inline String operator+(const String& lhs, const String& rhs) {
size_t lhs_size = lhs.size();
size_t rhs_size = rhs.size();
return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size);
}
inline String operator+(const String& lhs, const std::string& rhs) {
size_t lhs_size = lhs.size();
size_t rhs_size = rhs.size();
return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size);
}
inline String operator+(const std::string& lhs, const String& rhs) {
size_t lhs_size = lhs.size();
size_t rhs_size = rhs.size();
return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size);
}
inline String operator+(const char* lhs, const String& rhs) {
size_t lhs_size = std::strlen(lhs);
size_t rhs_size = rhs.size();
return String::Concat(lhs, lhs_size, rhs.data(), rhs_size);
}
inline String operator+(const String& lhs, const char* rhs) {
size_t lhs_size = lhs.size();
size_t rhs_size = std::strlen(rhs);
return String::Concat(lhs.data(), lhs_size, rhs, rhs_size);
}
// Overload < operator
inline bool operator<(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) < 0; }
inline bool operator<(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) > 0; }
inline bool operator<(const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; }
inline bool operator<(const String& lhs, const char* rhs) { return lhs.compare(rhs) < 0; }
inline bool operator<(const char* lhs, const String& rhs) { return rhs.compare(lhs) > 0; }
// Overload > operator
inline bool operator>(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) > 0; }
inline bool operator>(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) < 0; }
inline bool operator>(const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; }
inline bool operator>(const String& lhs, const char* rhs) { return lhs.compare(rhs) > 0; }
inline bool operator>(const char* lhs, const String& rhs) { return rhs.compare(lhs) < 0; }
// Overload <= operator
inline bool operator<=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) <= 0; }
inline bool operator<=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) >= 0; }
inline bool operator<=(const String& lhs, const String& rhs) { return lhs.compare(rhs) <= 0; }
inline bool operator<=(const String& lhs, const char* rhs) { return lhs.compare(rhs) <= 0; }
inline bool operator<=(const char* lhs, const String& rhs) { return rhs.compare(lhs) >= 0; }
// Overload >= operator
inline bool operator>=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) >= 0; }
inline bool operator>=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) <= 0; }
inline bool operator>=(const String& lhs, const String& rhs) { return lhs.compare(rhs) >= 0; }
inline bool operator>=(const String& lhs, const char* rhs) { return lhs.compare(rhs) >= 0; }
inline bool operator>=(const char* lhs, const String& rhs) { return rhs.compare(rhs) <= 0; }
// Overload == operator
inline bool operator==(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) == 0; }
inline bool operator==(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) == 0; }
inline bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; }
inline bool operator==(const String& lhs, const char* rhs) { return lhs.compare(rhs) == 0; }
inline bool operator==(const char* lhs, const String& rhs) { return rhs.compare(lhs) == 0; }
// Overload != operator
inline bool operator!=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) != 0; }
inline bool operator!=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) != 0; }
inline bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; }
inline bool operator!=(const String& lhs, const char* rhs) { return lhs.compare(rhs) != 0; }
inline bool operator!=(const char* lhs, const String& rhs) { return rhs.compare(lhs) != 0; }
inline std::ostream& operator<<(std::ostream& out, const String& input) {
out.write(input.data(), input.size());
return out;
}
inline int String::memncmp(const char* lhs, const char* rhs, size_t lhs_count, size_t rhs_count) {
if (lhs == rhs && lhs_count == rhs_count) return 0;
for (size_t i = 0; i < lhs_count && i < rhs_count; ++i) {
if (lhs[i] < rhs[i]) return -1;
if (lhs[i] > rhs[i]) return 1;
}
if (lhs_count < rhs_count) {
return -1;
} else if (lhs_count > rhs_count) {
return 1;
} else {
return 0;
}
}
inline size_t ObjectHash::operator()(const ObjectRef& a) const {
if (const auto* str = a.as<StringObj>()) {
return String::HashBytes(str->data, str->size);
}
return ObjectPtrHash()(a);
}
inline bool ObjectEqual::operator()(const ObjectRef& a, const ObjectRef& b) const {
if (a.same_as(b)) {
return true;
}
if (const auto* str_a = a.as<StringObj>()) {
if (const auto* str_b = b.as<StringObj>()) {
return String::memncmp(str_a->data, str_b->data, str_a->size, str_b->size) == 0;
}
}
return false;
}
/*! \brief Helper to represent nullptr for optional. */
struct NullOptType {};
/*!
* \brief Optional container that to represent to a Nullable variant of T.
* \tparam T The original ObjectRef.
*
* \code
*
* Optional<String> opt0 = nullptr;
* Optional<String> opt1 = String("xyz");
* CHECK(opt0 == nullptr);
* CHECK(opt1 == "xyz");
*
* \endcode
*/
template <typename T>
class Optional : public ObjectRef {
public:
using ContainerType = typename T::ContainerType;
static_assert(std::is_base_of<ObjectRef, T>::value, "Optional is only defined for ObjectRef.");
// default constructors.
Optional() = default;
Optional(const Optional<T>&) = default;
Optional(Optional<T>&&) = default;
Optional<T>& operator=(const Optional<T>&) = default;
Optional<T>& operator=(Optional<T>&&) = default;
/*!
* \brief Construct from an ObjectPtr
* whose type already matches the ContainerType.
* \param ptr
*/
explicit Optional(ObjectPtr<Object> ptr) : ObjectRef(ptr) {}
/*! \brief Nullopt handling */
Optional(NullOptType) {} // NOLINT(*)
// nullptr handling.
// disallow implicit conversion as 0 can be implicitly converted to nullptr_t
explicit Optional(std::nullptr_t) {}
Optional<T>& operator=(std::nullptr_t) {
data_ = nullptr;
return *this;
}
// normal value handling.
Optional(T other) // NOLINT(*)
: ObjectRef(std::move(other)) {}
Optional<T>& operator=(T other) {
ObjectRef::operator=(std::move(other));
return *this;
}
// delete the int constructor
// since Optional<Integer>(0) is ambiguious
// 0 can be implicitly casted to nullptr_t
explicit Optional(int val) = delete;
Optional<T>& operator=(int val) = delete;
/*!
* \return A not-null container value in the optional.
* \note This function performs not-null checking.
*/
T value() const {
CHECK(data_ != nullptr);
return T(data_);
}
/*!
* \return The contained value if the Optional is not null
* otherwise return the default_value.
*/
T value_or(T default_value) const { return data_ != nullptr ? T(data_) : default_value; }
/*! \return Whether the container is not nullptr.*/
explicit operator bool() const { return *this != nullptr; }
// operator overloadings
bool operator==(std::nullptr_t) const { return data_ == nullptr; }
bool operator!=(std::nullptr_t) const { return data_ != nullptr; }
auto operator==(const Optional<T>& other) const {
// support case where sub-class returns a symbolic ref type.
using RetType = decltype(value() == other.value());
if (same_as(other)) return RetType(true);
if (*this != nullptr && other != nullptr) {
return value() == other.value();
} else {
// one of them is nullptr.
return RetType(false);
}
}
auto operator!=(const Optional<T>& other) const {
// support case where sub-class returns a symbolic ref type.
using RetType = decltype(value() != other.value());
if (same_as(other)) return RetType(false);
if (*this != nullptr && other != nullptr) {
return value() != other.value();
} else {
// one of them is nullptr.
return RetType(true);
}
}
auto operator==(const T& other) const {
using RetType = decltype(value() == other);
if (same_as(other)) return RetType(true);
if (*this != nullptr) return value() == other;
return RetType(false);
}
auto operator!=(const T& other) const { return !(*this == other); }
template <typename U>
auto operator==(const U& other) const {
using RetType = decltype(value() == other);
if (*this == nullptr) return RetType(false);
return value() == other;
}
template <typename U>
auto operator!=(const U& other) const {
using RetType = decltype(value() != other);
if (*this == nullptr) return RetType(true);
return value() != other;
}
static constexpr bool _type_is_nullable = true;
};
/*!
* \brief An object representing a closure. This object is used by both the
* Relay VM and interpreter.
*/
class ClosureObj : public Object {
public:
static constexpr const uint32_t _type_index = TypeIndex::kRuntimeClosure;
static constexpr const char* _type_key = "runtime.Closure";
TVM_DECLARE_BASE_OBJECT_INFO(ClosureObj, Object);
};
/*! \brief reference to closure. */
class Closure : public ObjectRef {
public:
TVM_DEFINE_OBJECT_REF_METHODS(Closure, ObjectRef, ClosureObj);
};
} // namespace runtime
// expose the functions to the root namespace.
using runtime::Optional;
using runtime::String;
constexpr runtime::NullOptType NullOpt{};
} // namespace tvm
namespace std {
template <>
struct hash<::tvm::runtime::String> {
std::size_t operator()(const ::tvm::runtime::String& str) const {
return ::tvm::runtime::String::HashBytes(str.data(), str.size());
}
};
} // namespace std
#endif // TVM_RUNTIME_CONTAINER_H_