blob: 8003a89cbf0bbd09c3c05eeaf22636de2ff64cd0 [file] [log] [blame]
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/core/tag.h>
#include <executorch/runtime/platform/assert.h>
namespace executorch {
namespace runtime {
struct EValue;
namespace internal {
// Tensor gets proper reference treatment because its expensive to copy in aten
// mode, all other types are just copied.
template <typename T>
struct evalue_to_const_ref_overload_return {
using type = T;
};
template <>
struct evalue_to_const_ref_overload_return<executorch::aten::Tensor> {
using type = const executorch::aten::Tensor&;
};
template <typename T>
struct evalue_to_ref_overload_return {
using type = T;
};
template <>
struct evalue_to_ref_overload_return<executorch::aten::Tensor> {
using type = executorch::aten::Tensor&;
};
} // namespace internal
/*
* Helper class used to correlate EValues in the executor table, with the
* unwrapped list of the proper type. Because values in the runtime's values
* table can change during execution, we cannot statically allocate list of
* objects at deserialization. Imagine the serialized list says index 0 in the
* value table is element 2 in the list, but during execution the value in
* element 2 changes (in the case of tensor this means the TensorImpl* stored in
* the tensor changes). To solve this instead they must be created dynamically
* whenever they are used.
*/
template <typename T>
class BoxedEvalueList {
public:
BoxedEvalueList() = default;
/*
* Wrapped_vals is a list of pointers into the values table of the runtime
* whose destinations correlate with the elements of the list, unwrapped_vals
* is a container of the same size whose serves as memory to construct the
* unwrapped vals.
*/
BoxedEvalueList(EValue** wrapped_vals, T* unwrapped_vals, int size)
: wrapped_vals_(wrapped_vals, size), unwrapped_vals_(unwrapped_vals) {}
/*
* Constructs and returns the list of T specified by the EValue pointers
*/
executorch::aten::ArrayRef<T> get() const;
private:
// Source of truth for the list
executorch::aten::ArrayRef<EValue*> wrapped_vals_;
// Same size as wrapped_vals
mutable T* unwrapped_vals_;
};
template <>
executorch::aten::ArrayRef<executorch::aten::optional<executorch::aten::Tensor>>
BoxedEvalueList<executorch::aten::optional<executorch::aten::Tensor>>::get()
const;
// Aggregate typing system similar to IValue only slimmed down with less
// functionality, no dependencies on atomic, and fewer supported types to better
// suit embedded systems (ie no intrusive ptr)
struct EValue {
union Payload {
// When in ATen mode at::Tensor is not trivially copyable, this nested union
// lets us handle tensor as a special case while leaving the rest of the
// fields in a simple state instead of requiring a switch on tag everywhere.
union TriviallyCopyablePayload {
TriviallyCopyablePayload() : as_int(0) {}
// Scalar supported through these 3 types
int64_t as_int;
double as_double;
bool as_bool;
// TODO(jakeszwe): convert back to pointers to optimize size of this
// struct
executorch::aten::ArrayRef<char> as_string;
executorch::aten::ArrayRef<double> as_double_list;
executorch::aten::ArrayRef<bool> as_bool_list;
BoxedEvalueList<int64_t> as_int_list;
BoxedEvalueList<executorch::aten::Tensor> as_tensor_list;
BoxedEvalueList<executorch::aten::optional<executorch::aten::Tensor>>
as_list_optional_tensor;
} copyable_union;
// Since a Tensor just holds a TensorImpl*, there's no value to use Tensor*
// here.
executorch::aten::Tensor as_tensor;
Payload() {}
~Payload() {}
};
// Data storage and type tag
Payload payload;
Tag tag;
// Basic ctors and assignments
EValue(const EValue& rhs) : EValue(rhs.payload, rhs.tag) {}
EValue(EValue&& rhs) noexcept : tag(rhs.tag) {
moveFrom(std::move(rhs));
}
EValue& operator=(EValue&& rhs) & noexcept {
if (&rhs == this) {
return *this;
}
destroy();
moveFrom(std::move(rhs));
return *this;
}
EValue& operator=(EValue const& rhs) & {
// Define copy assignment through copy ctor and move assignment
*this = EValue(rhs);
return *this;
}
~EValue() {
destroy();
}
/****** None Type ******/
EValue() : tag(Tag::None) {
payload.copyable_union.as_int = 0;
}
bool isNone() const {
return tag == Tag::None;
}
/****** Int Type ******/
/*implicit*/ EValue(int64_t i) : tag(Tag::Int) {
payload.copyable_union.as_int = i;
}
bool isInt() const {
return tag == Tag::Int;
}
int64_t toInt() const {
ET_CHECK_MSG(isInt(), "EValue is not an int.");
return payload.copyable_union.as_int;
}
/****** Double Type ******/
/*implicit*/ EValue(double d) : tag(Tag::Double) {
payload.copyable_union.as_double = d;
}
bool isDouble() const {
return tag == Tag::Double;
}
double toDouble() const {
ET_CHECK_MSG(isDouble(), "EValue is not a Double.");
return payload.copyable_union.as_double;
}
/****** Bool Type ******/
/*implicit*/ EValue(bool b) : tag(Tag::Bool) {
payload.copyable_union.as_bool = b;
}
bool isBool() const {
return tag == Tag::Bool;
}
bool toBool() const {
ET_CHECK_MSG(isBool(), "EValue is not a Bool.");
return payload.copyable_union.as_bool;
}
/****** Scalar Type ******/
/// Construct an EValue using the implicit value of a Scalar.
/*implicit*/ EValue(executorch::aten::Scalar s) {
if (s.isIntegral(false)) {
tag = Tag::Int;
payload.copyable_union.as_int = s.to<int64_t>();
} else if (s.isFloatingPoint()) {
tag = Tag::Double;
payload.copyable_union.as_double = s.to<double>();
} else if (s.isBoolean()) {
tag = Tag::Bool;
payload.copyable_union.as_bool = s.to<bool>();
} else {
ET_CHECK_MSG(false, "Scalar passed to EValue is not initialized.");
}
}
bool isScalar() const {
return tag == Tag::Int || tag == Tag::Double || tag == Tag::Bool;
}
executorch::aten::Scalar toScalar() const {
// Convert from implicit value to Scalar using implicit constructors.
if (isDouble()) {
return toDouble();
} else if (isInt()) {
return toInt();
} else if (isBool()) {
return toBool();
} else {
ET_CHECK_MSG(false, "EValue is not a Scalar.");
}
}
/****** Tensor Type ******/
/*implicit*/ EValue(executorch::aten::Tensor t) : tag(Tag::Tensor) {
// When built in aten mode, at::Tensor has a non trivial constructor
// destructor, so regular assignment to a union field is UB. Instead we must
// go through placement new (which causes a refcount bump).
new (&payload.as_tensor) executorch::aten::Tensor(t);
}
// Template constructor that allows construction from types that can be
// dereferenced to produce a type that EValue can be implicitly constructed
// from.
template <typename T>
/*implicit*/ EValue(
T&& value,
typename std::enable_if<std::is_convertible<
decltype(*std::forward<T>(value)),
EValue>::value>::type* = 0) {
ET_CHECK_MSG(value != nullptr, "Pointer is null.");
// Note that this ctor does not initialize this->tag directly; it is set by
// moving in the new value.
moveFrom(*std::forward<T>(value));
}
// Delete constructor for raw pointers to ensure they cannot be used.
template <typename T>
explicit EValue(T* value) = delete;
bool isTensor() const {
return tag == Tag::Tensor;
}
executorch::aten::Tensor toTensor() && {
ET_CHECK_MSG(isTensor(), "EValue is not a Tensor.");
auto res = std::move(payload.as_tensor);
clearToNone();
return res;
}
executorch::aten::Tensor& toTensor() & {
ET_CHECK_MSG(isTensor(), "EValue is not a Tensor.");
return payload.as_tensor;
}
const executorch::aten::Tensor& toTensor() const& {
ET_CHECK_MSG(isTensor(), "EValue is not a Tensor.");
return payload.as_tensor;
}
/****** String Type ******/
/*implicit*/ EValue(const char* s, size_t size) : tag(Tag::String) {
payload.copyable_union.as_string =
executorch::aten::ArrayRef<char>(s, size);
}
bool isString() const {
return tag == Tag::String;
}
executorch::aten::string_view toString() const {
ET_CHECK_MSG(isString(), "EValue is not a String.");
return executorch::aten::string_view(
payload.copyable_union.as_string.data(),
payload.copyable_union.as_string.size());
}
/****** Int List Type ******/
/*implicit*/ EValue(BoxedEvalueList<int64_t> i) : tag(Tag::ListInt) {
payload.copyable_union.as_int_list = i;
}
bool isIntList() const {
return tag == Tag::ListInt;
}
executorch::aten::ArrayRef<int64_t> toIntList() const {
ET_CHECK_MSG(isIntList(), "EValue is not an Int List.");
return payload.copyable_union.as_int_list.get();
}
/****** Bool List Type ******/
/*implicit*/ EValue(executorch::aten::ArrayRef<bool> b) : tag(Tag::ListBool) {
payload.copyable_union.as_bool_list = b;
}
bool isBoolList() const {
return tag == Tag::ListBool;
}
executorch::aten::ArrayRef<bool> toBoolList() const {
ET_CHECK_MSG(isBoolList(), "EValue is not a Bool List.");
return payload.copyable_union.as_bool_list;
}
/****** Double List Type ******/
/*implicit*/ EValue(executorch::aten::ArrayRef<double> d)
: tag(Tag::ListDouble) {
payload.copyable_union.as_double_list = d;
}
bool isDoubleList() const {
return tag == Tag::ListDouble;
}
executorch::aten::ArrayRef<double> toDoubleList() const {
ET_CHECK_MSG(isDoubleList(), "EValue is not a Double List.");
return payload.copyable_union.as_double_list;
}
/****** Tensor List Type ******/
/*implicit*/ EValue(BoxedEvalueList<executorch::aten::Tensor> t)
: tag(Tag::ListTensor) {
payload.copyable_union.as_tensor_list = t;
}
bool isTensorList() const {
return tag == Tag::ListTensor;
}
executorch::aten::ArrayRef<executorch::aten::Tensor> toTensorList() const {
ET_CHECK_MSG(isTensorList(), "EValue is not a Tensor List.");
return payload.copyable_union.as_tensor_list.get();
}
/****** List Optional Tensor Type ******/
/*implicit*/ EValue(
BoxedEvalueList<executorch::aten::optional<executorch::aten::Tensor>> t)
: tag(Tag::ListOptionalTensor) {
payload.copyable_union.as_list_optional_tensor = t;
}
bool isListOptionalTensor() const {
return tag == Tag::ListOptionalTensor;
}
executorch::aten::ArrayRef<
executorch::aten::optional<executorch::aten::Tensor>>
toListOptionalTensor() const {
return payload.copyable_union.as_list_optional_tensor.get();
}
/****** ScalarType Type ******/
executorch::aten::ScalarType toScalarType() const {
ET_CHECK_MSG(isInt(), "EValue is not a ScalarType.");
return static_cast<executorch::aten::ScalarType>(
payload.copyable_union.as_int);
}
/****** MemoryFormat Type ******/
executorch::aten::MemoryFormat toMemoryFormat() const {
ET_CHECK_MSG(isInt(), "EValue is not a MemoryFormat.");
return static_cast<executorch::aten::MemoryFormat>(
payload.copyable_union.as_int);
}
/****** Layout Type ******/
executorch::aten::Layout toLayout() const {
ET_CHECK_MSG(isInt(), "EValue is not a Layout.");
return static_cast<executorch::aten::Layout>(payload.copyable_union.as_int);
}
/****** Device Type ******/
executorch::aten::Device toDevice() const {
ET_CHECK_MSG(isInt(), "EValue is not a Device.");
return executorch::aten::Device(
static_cast<executorch::aten::DeviceType>(
payload.copyable_union.as_int),
-1);
}
template <typename T>
T to() &&;
template <typename T>
typename internal::evalue_to_const_ref_overload_return<T>::type to() const&;
template <typename T>
typename internal::evalue_to_ref_overload_return<T>::type to() &;
/**
* Converts the EValue to an optional object that can represent both T and
* an uninitialized state.
*/
template <typename T>
inline executorch::aten::optional<T> toOptional() const {
if (this->isNone()) {
return executorch::aten::nullopt;
}
return this->to<T>();
}
private:
// Pre cond: the payload value has had its destructor called
void clearToNone() noexcept {
payload.copyable_union.as_int = 0;
tag = Tag::None;
}
// Shared move logic
void moveFrom(EValue&& rhs) noexcept {
if (rhs.isTensor()) {
new (&payload.as_tensor)
executorch::aten::Tensor(std::move(rhs.payload.as_tensor));
rhs.payload.as_tensor.~Tensor();
} else {
payload.copyable_union = rhs.payload.copyable_union;
}
tag = rhs.tag;
rhs.clearToNone();
}
// Destructs stored tensor if there is one
void destroy() {
// Necessary for ATen tensor to refcount decrement the intrusive_ptr to
// tensorimpl that got a refcount increment when we placed it in the evalue,
// no-op if executorch tensor #ifdef could have a
// minor performance bump for a code maintainability hit
if (isTensor()) {
payload.as_tensor.~Tensor();
} else if (isTensorList()) {
for (auto& tensor : toTensorList()) {
tensor.~Tensor();
}
} else if (isListOptionalTensor()) {
for (auto& optional_tensor : toListOptionalTensor()) {
optional_tensor.~optional();
}
}
}
EValue(const Payload& p, Tag t) : tag(t) {
if (isTensor()) {
new (&payload.as_tensor) executorch::aten::Tensor(p.as_tensor);
} else {
payload.copyable_union = p.copyable_union;
}
}
};
#define EVALUE_DEFINE_TO(T, method_name) \
template <> \
inline T EValue::to<T>()&& { \
return static_cast<T>(std::move(*this).method_name()); \
} \
template <> \
inline ::executorch::runtime::internal::evalue_to_const_ref_overload_return< \
T>::type \
EValue::to<T>() const& { \
typedef ::executorch::runtime::internal:: \
evalue_to_const_ref_overload_return<T>::type return_type; \
return static_cast<return_type>(this->method_name()); \
} \
template <> \
inline ::executorch::runtime::internal::evalue_to_ref_overload_return< \
T>::type \
EValue::to<T>()& { \
typedef ::executorch::runtime::internal::evalue_to_ref_overload_return< \
T>::type return_type; \
return static_cast<return_type>(this->method_name()); \
}
EVALUE_DEFINE_TO(executorch::aten::Scalar, toScalar)
EVALUE_DEFINE_TO(int64_t, toInt)
EVALUE_DEFINE_TO(bool, toBool)
EVALUE_DEFINE_TO(double, toDouble)
EVALUE_DEFINE_TO(executorch::aten::string_view, toString)
EVALUE_DEFINE_TO(executorch::aten::ScalarType, toScalarType)
EVALUE_DEFINE_TO(executorch::aten::MemoryFormat, toMemoryFormat)
EVALUE_DEFINE_TO(executorch::aten::Layout, toLayout)
EVALUE_DEFINE_TO(executorch::aten::Device, toDevice)
// Tensor and Optional Tensor
EVALUE_DEFINE_TO(
executorch::aten::optional<executorch::aten::Tensor>,
toOptional<executorch::aten::Tensor>)
EVALUE_DEFINE_TO(executorch::aten::Tensor, toTensor)
// IntList and Optional IntList
EVALUE_DEFINE_TO(executorch::aten::ArrayRef<int64_t>, toIntList)
EVALUE_DEFINE_TO(
executorch::aten::optional<executorch::aten::ArrayRef<int64_t>>,
toOptional<executorch::aten::ArrayRef<int64_t>>)
// DoubleList and Optional DoubleList
EVALUE_DEFINE_TO(executorch::aten::ArrayRef<double>, toDoubleList)
EVALUE_DEFINE_TO(
executorch::aten::optional<executorch::aten::ArrayRef<double>>,
toOptional<executorch::aten::ArrayRef<double>>)
// BoolList and Optional BoolList
EVALUE_DEFINE_TO(executorch::aten::ArrayRef<bool>, toBoolList)
EVALUE_DEFINE_TO(
executorch::aten::optional<executorch::aten::ArrayRef<bool>>,
toOptional<executorch::aten::ArrayRef<bool>>)
// TensorList and Optional TensorList
EVALUE_DEFINE_TO(
executorch::aten::ArrayRef<executorch::aten::Tensor>,
toTensorList)
EVALUE_DEFINE_TO(
executorch::aten::optional<
executorch::aten::ArrayRef<executorch::aten::Tensor>>,
toOptional<executorch::aten::ArrayRef<executorch::aten::Tensor>>)
// List of Optional Tensor
EVALUE_DEFINE_TO(
executorch::aten::ArrayRef<
executorch::aten::optional<executorch::aten::Tensor>>,
toListOptionalTensor)
#undef EVALUE_DEFINE_TO
template <typename T>
executorch::aten::ArrayRef<T> BoxedEvalueList<T>::get() const {
for (typename executorch::aten::ArrayRef<T>::size_type i = 0;
i < wrapped_vals_.size();
i++) {
ET_CHECK(wrapped_vals_[i] != nullptr);
unwrapped_vals_[i] = wrapped_vals_[i]->template to<T>();
}
return executorch::aten::ArrayRef<T>{unwrapped_vals_, wrapped_vals_.size()};
}
} // namespace runtime
} // namespace executorch
namespace torch {
namespace executor {
// TODO(T197294990): Remove these deprecated aliases once all users have moved
// to the new `::executorch` namespaces.
using ::executorch::runtime::BoxedEvalueList;
using ::executorch::runtime::EValue;
} // namespace executor
} // namespace torch