blob: 515ba8b7402b65bddbe7f46eeadf45802bcd09a8 [file] [edit]
/*
* 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 <ctype.h>
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <variant>
// NB: This is a local, pytree FunctionRef and not from the ExecuTorch runtime.
#include <executorch/extension/pytree/function_ref.h>
namespace executorch {
namespace extension {
namespace pytree {
inline void pytree_assert(bool must_be_true) {
assert(must_be_true);
}
#ifdef _MSC_VER
#define EXECUTORCH_ALWAYS_INLINE __forceinline
#elif defined(__GNUC__)
#define EXECUTORCH_ALWAYS_INLINE inline __attribute__((__always_inline__))
#else
#define EXECUTORCH_ALWAYS_INLINE inline
#endif
[[noreturn]] EXECUTORCH_ALWAYS_INLINE void pytree_unreachable() {
assert(false);
#if defined(__GNUC__)
__builtin_unreachable();
#elif defined(_MSC_VER)
__assume(0);
#else
while (!0)
;
#endif
}
enum class Kind : uint8_t { List, Tuple, NamedTuple, Dict, Leaf, Custom, None };
using KeyStr = std::string;
using KeyInt = int32_t;
struct Key {
enum class Kind : uint8_t { None, Int, Str } kind_;
private:
std::variant<std::monostate, KeyInt, KeyStr> repr_;
public:
Key() {}
/*implicit*/ Key(KeyInt key) : repr_(key) {}
/*implicit*/ Key(KeyStr key) : repr_(std::move(key)) {}
Kind kind() const {
return static_cast<Kind>(repr_.index());
}
KeyInt as_int() const {
return std::get<KeyInt>(repr_);
}
operator KeyInt() const {
return as_int();
}
const KeyStr& as_str() const {
return std::get<KeyStr>(repr_);
}
operator const KeyStr&() const {
return as_str();
}
bool operator==(const Key& rhs) const {
return repr_ == rhs.repr_;
}
bool operator!=(const Key& rhs) const {
return !operator==(rhs);
}
};
struct Empty {};
template <typename T, typename Aux = Empty>
struct ContainerHandle;
template <typename T, typename Aux = Empty>
struct Container final : public Aux {
using handle_type = ContainerHandle<T, Aux>;
using leaf_type = T;
Kind kind = Kind::None;
size_t size = 0;
leaf_type* leaf = nullptr;
std::unique_ptr<handle_type[]> items;
std::unique_ptr<Key[]> keys;
std::string custom_type;
// internal only field to keep associated to every node meta info
mutable size_t leaves_num = 0u;
/*implicit*/ Container(Kind kind, size_t size = 0u)
: kind(kind),
size(size),
items(std::unique_ptr<handle_type[]>(new handle_type[size])) {
if (kind == Kind::Dict) {
keys = std::unique_ptr<Key[]>(new Key[size]);
}
}
/*implicit*/ Container(leaf_type* leaf)
: kind(Kind::Leaf), size(0u), leaf(leaf), leaves_num(1u) {}
Container(const Container&) = delete;
Container& operator=(const Container&) = delete;
};
template <typename T, typename Aux>
struct ContainerHandle {
using container_type = Container<T, Aux>;
using leaf_type = T;
std::unique_ptr<container_type> handle;
ContainerHandle() {}
template <typename... Args>
ContainerHandle(Args... args)
: handle(std::make_unique<container_type>(std::forward<Args>(args)...)) {}
/*implicit*/ ContainerHandle(container_type* c) : handle(c) {}
/*implicit*/ ContainerHandle(std::unique_ptr<container_type> c)
: handle(std::move(c)) {}
void set_leaf(leaf_type* leaf) {
pytree_assert(handle->kind == Kind::Leaf);
handle->leaf = leaf;
}
operator leaf_type() const {
pytree_assert(handle->kind == Kind::Leaf);
return *handle->leaf;
}
const leaf_type& leaf() const {
pytree_assert(handle->kind == Kind::Leaf);
return *handle->leaf;
}
leaf_type& leaf() {
pytree_assert(handle->kind == Kind::Leaf);
return *handle->leaf;
}
const leaf_type* leaf_ptr() const {
pytree_assert(handle->kind == Kind::Leaf);
return handle->leaf;
}
leaf_type* leaf_ptr() {
pytree_assert(handle->kind == Kind::Leaf);
return handle->leaf;
}
const ContainerHandle& operator[](size_t idx) const {
pytree_assert(idx < handle->size);
return handle->items[idx];
}
ContainerHandle& operator[](size_t idx) {
pytree_assert(idx < handle->size);
return handle->items[idx];
}
bool contains(const KeyStr& lookup_key) const {
pytree_assert(isDict());
for (size_t i = 0; i < handle->size; ++i) {
if (handle->keys[i] == lookup_key) {
return true;
}
}
return false;
}
const ContainerHandle& at(const Key& lookup_key) const {
pytree_assert(isDict());
for (size_t i = 0; i < handle->size; ++i) {
if (handle->keys[i] == lookup_key) {
return handle->items[i];
}
}
pytree_unreachable();
}
const ContainerHandle& at(const KeyInt& lookup_key) const {
return at(Key(lookup_key));
}
const ContainerHandle& at(const KeyStr& lookup_key) const {
return at(Key(lookup_key));
}
const Key& key(size_t idx) const {
pytree_assert(isDict());
return handle->keys[idx];
}
Key& key(size_t idx) {
pytree_assert(isDict());
return handle->keys[idx];
}
size_t size() const {
return handle->size;
}
size_t leaves_num() const {
return handle->leaves_num;
}
bool isDict() const {
return handle->kind == Kind::Dict;
}
bool isList() const {
return handle->kind == Kind::List;
}
bool isNamedTuple() const {
return handle->kind == Kind::NamedTuple;
}
bool isTuple() const {
return handle->kind == Kind::Tuple;
}
bool isLeaf() const {
return handle->kind == Kind::Leaf;
}
Kind kind() const {
return handle->kind;
}
// Checks only structure, no leaves comparison
bool operator==(const ContainerHandle& rhs) {
const Kind knd = kind();
if (knd != rhs.kind()) {
return false;
}
if (knd == Kind::Leaf) {
return true;
}
const size_t _size = size();
if (_size != rhs.size()) {
return false;
}
for (size_t i = 0; i < _size; ++i) {
if (knd == Kind::Dict && (key(i) != rhs.key(i))) {
return false;
}
if (operator[](i) != rhs[i]) {
return false;
}
}
return true;
}
bool operator!=(const ContainerHandle& rhs) {
return !operator==(rhs);
}
};
struct TreeSpecLeaf {};
template <typename Aux>
using TreeSpec = ContainerHandle<TreeSpecLeaf, Aux>;
template <typename Aux>
using TreeSpecContainer = Container<TreeSpecLeaf, Aux>;
using StrTreeSpec = std::string;
// Expects refresh_leaves_num() was called after the last modification
template <typename T, typename U, typename Aux>
ContainerHandle<U, Aux> clone(const ContainerHandle<T, Aux>& node, U* leaves) {
if (node.isLeaf()) {
return ContainerHandle<U, Aux>(leaves);
}
ContainerHandle<U, Aux> ret(node.kind(), node.size());
size_t leaves_offset = 0;
size_t size = node.size();
for (size_t i = 0; i < size; ++i) {
ret[i] = clone(node[i], leaves + leaves_offset);
leaves_offset += node[i].leaves_num();
}
if (node.isDict()) {
ret.handle->keys = std::unique_ptr<Key[]>(new Key[size]);
for (size_t i = 0; i < size; ++i) {
ret.handle->keys[i] = node.handle->keys[i];
}
}
return ret;
}
template <typename T, typename Aux>
void traverse(
ContainerHandle<T, Aux>& node,
FunctionRef<void(ContainerHandle<T, Aux>&)> func) {
for (size_t i = 0; i < node.size(); ++i) {
traverse(node[i], func);
}
func(node);
}
template <typename T, typename Aux>
void traverse(
const ContainerHandle<T, Aux>& node,
FunctionRef<void(const ContainerHandle<T, Aux>&)> func) {
for (size_t i = 0; i < node.size(); ++i) {
traverse(node[i], func);
}
func(node);
}
struct Config final {
static constexpr char kTuple = 'T';
static constexpr char kNamedTuple = 'N';
static constexpr char kList = 'L';
static constexpr char kDict = 'D';
static constexpr char kCustom = 'C';
static constexpr char kLeaf = '$';
static constexpr char kNodeDataBegin = '(';
static constexpr char kNodeDataEnd = ')';
static constexpr char kDictStrKeyQuote = '\'';
static constexpr char kDictKeyValueSep = ':';
static constexpr char kChildrenSep = ',';
static constexpr char kChildrenDataSep = '#';
};
template <typename Aux>
StrTreeSpec to_str_internal(const TreeSpec<Aux>& spec) {
std::string s;
switch (spec.kind()) {
case Kind::List:
s.push_back(Config::kList);
break;
case Kind::NamedTuple:
s.push_back(Config::kNamedTuple);
break;
case Kind::Tuple:
s.push_back(Config::kTuple);
break;
case Kind::Dict:
s.push_back(Config::kDict);
break;
case Kind::Leaf:
s.push_back(Config::kLeaf);
return s;
case Kind::Custom:
s.push_back(Config::kCustom);
s.push_back('(');
s.append(spec.handle->custom_type);
s.push_back(')');
break;
case Kind::None:
return s;
}
const size_t size = spec.size();
s.append(std::to_string(size));
for (size_t i = 0; i < size; ++i) {
s.push_back(Config::kChildrenDataSep);
s.append(std::to_string(spec[i].leaves_num()));
}
s.push_back(Config::kNodeDataBegin);
if (spec.kind() == Kind::Dict) {
for (size_t i = 0; i < size; ++i) {
if (i) {
s.push_back(Config::kChildrenSep);
}
const auto& key = spec.key(i);
if (key.kind() == Key::Kind::Int) {
s.append(std::to_string(key.as_int()));
} else if (key.kind() == Key::Kind::Str) {
s.push_back(Config::kDictStrKeyQuote);
s.append(key.as_str());
s.push_back(Config::kDictStrKeyQuote);
} else {
pytree_unreachable();
}
s.push_back(Config::kDictKeyValueSep);
s.append(to_str_internal(spec[i]));
}
} else {
for (size_t i = 0; i < size; ++i) {
if (i) {
s.push_back(Config::kChildrenSep);
}
s.append(to_str_internal(spec[i]));
}
}
s.push_back(Config::kNodeDataEnd);
return s;
}
template <typename T>
struct arr {
explicit arr(const size_t n) : data_(std::unique_ptr<T[]>(new T[n])), n_(n) {}
T& operator[](size_t idx) {
return data_[idx];
}
const T& operator[](size_t idx) const {
return data_[idx];
}
inline T* data() {
return data_.get();
}
inline size_t size() const {
return n_;
}
private:
std::unique_ptr<T[]> data_;
size_t n_;
};
inline size_t read_number(const StrTreeSpec& spec, size_t& read_idx) {
size_t num = 0;
while (isdigit(spec[read_idx])) {
num = 10 * num + (spec[read_idx] - '0');
read_idx++;
}
return num;
}
inline arr<size_t> read_node_layout(const StrTreeSpec& spec, size_t& read_idx) {
const size_t child_num = read_number(spec, read_idx);
arr<size_t> ret(child_num);
size_t child_idx = 0;
while (spec[read_idx] == Config::kChildrenDataSep) {
++read_idx;
ret[child_idx++] = read_number(spec, read_idx);
}
return ret;
}
template <typename Aux>
TreeSpec<Aux> from_str_internal(
const StrTreeSpec& spec,
size_t read_idx,
const arr<size_t>& spec_data) {
const auto kind_char = spec[read_idx];
switch (kind_char) {
case Config::kTuple:
case Config::kNamedTuple:
case Config::kList: {
Kind kind = Kind::List;
std::string custom_type;
if (Config::kNamedTuple == kind_char) {
kind = Kind::NamedTuple;
} else if (Config::kTuple == kind_char) {
kind = Kind::Tuple;
} else if (Config::kCustom == kind_char) {
kind = Kind::Custom;
read_idx++;
assert(spec[read_idx] == '(');
auto type_str_end = spec_data[read_idx];
read_idx++;
custom_type = spec.substr(read_idx, type_str_end - read_idx);
assert(false);
}
read_idx++;
auto layout = read_node_layout(spec, read_idx);
const auto size = layout.size();
auto c = std::make_unique<TreeSpecContainer<Aux>>(kind, size);
if (Kind::Custom == kind) {
c->custom_type = std::move(custom_type);
}
size_t child_idx = 0;
size_t leaves_offset = 0;
if (size > 0) {
while (spec[read_idx] != Config::kNodeDataEnd) {
// NOLINTNEXTLINE
auto next_delim_idx = spec_data[read_idx];
read_idx++;
c->items[child_idx] =
from_str_internal<Aux>(spec, read_idx, spec_data);
read_idx = next_delim_idx;
leaves_offset += layout[child_idx++];
}
} else {
read_idx++;
}
c->leaves_num = leaves_offset;
return TreeSpec<Aux>(std::move(c));
}
case Config::kDict: {
read_idx++;
auto layout = read_node_layout(spec, read_idx);
const auto size = layout.size();
auto c = std::make_unique<TreeSpecContainer<Aux>>(Kind::Dict, size);
size_t child_idx = 0;
size_t leaves_offset = 0;
if (size > 0) {
while (spec[read_idx] != Config::kNodeDataEnd) {
// NOLINTNEXTLINE
auto next_delim_idx = spec_data[read_idx];
read_idx++;
if (spec[read_idx] == Config::kDictStrKeyQuote) {
auto key_delim_idx = spec_data[read_idx];
read_idx++;
const size_t key_len = key_delim_idx - read_idx;
// NOLINTNEXTLINE
c->keys[child_idx] = spec.substr(read_idx, key_len);
read_idx = key_delim_idx + 2;
} else {
pytree_assert(isdigit(spec[read_idx]));
size_t key = read_number(spec, read_idx);
c->keys[child_idx] = KeyInt(key);
read_idx += 1;
}
c->items[child_idx] =
from_str_internal<Aux>(spec, read_idx, spec_data);
read_idx = next_delim_idx;
leaves_offset += layout[child_idx++];
}
} else {
read_idx++;
}
c->leaves_num = leaves_offset;
return TreeSpec<Aux>(std::move(c));
}
case Config::kLeaf:
return new TreeSpecContainer<Aux>(nullptr);
}
pytree_unreachable();
return new TreeSpecContainer<Aux>(Kind::None);
}
template <typename T>
struct stack final {
constexpr static const size_t SIZE = 8;
size_t size_ = 0;
T data[SIZE];
void push(T&& item) {
pytree_assert(size_ < SIZE);
data[size_++] = std::move(item);
}
T pop() {
pytree_assert(size_ > 0);
return data[--size_];
}
T& top() {
pytree_assert(size_ > 0);
return data[size_ - 1];
}
size_t size() {
return size_;
}
};
inline arr<size_t> pre_parse(const StrTreeSpec& spec) {
stack<std::pair<size_t, size_t>> stack;
size_t i = 0;
const size_t size = spec.size();
arr<size_t> ret(size);
while (i < size) {
const auto c = spec[i];
switch (c) {
case Config::kNodeDataBegin: {
stack.push({i, i});
break;
}
case Config::kNodeDataEnd: {
auto& item = stack.top();
size_t last_sep_idx = item.second;
ret[last_sep_idx] = i;
stack.pop();
break;
}
case Config::kDictStrKeyQuote: {
size_t idx = i;
i++;
while (spec[i] != Config::kDictStrKeyQuote) {
i++;
}
ret[idx] = i;
ret[i] = idx;
break;
}
case Config::kChildrenSep: {
auto& item = stack.top();
size_t last_sep_idx = item.second;
ret[last_sep_idx] = i;
item.second = i;
break;
}
}
i++;
}
return ret;
}
template <typename Aux = Empty>
TreeSpec<Aux> from_str(const StrTreeSpec& spec) {
return from_str_internal<Aux>(spec, 0u, pre_parse(spec));
}
template <typename Aux>
StrTreeSpec to_str(const TreeSpec<Aux>& spec) {
if (spec.leaves_num() == 0) {
refresh_leaves_num(spec);
}
return to_str_internal(spec);
}
template <typename Aux>
StrTreeSpec to_str(const TreeSpec<Aux>& spec);
template <typename T, typename Aux>
ContainerHandle<T, Aux> unflatten(const TreeSpec<Aux>& spec, T* leaves) {
if (spec.leaves_num() == 0) {
refresh_leaves_num(spec);
}
return clone(spec, leaves);
}
template <typename T, typename Aux = Empty>
ContainerHandle<T, Aux> unflatten(const StrTreeSpec& spec, T* leaves) {
return unflatten(from_str<Aux>(spec), leaves);
}
template <typename T, typename Aux>
void flatten_internal(const ContainerHandle<T, Aux>& tree, const T** leaves) {
using tree_t = decltype(tree);
size_t leaves_idx = 0;
auto func = [&](tree_t node) {
if (node.isLeaf()) {
leaves[leaves_idx++] = node.leaf_ptr();
}
};
traverse(tree, FunctionRef<void(tree_t&)>{func});
}
template <typename T, typename Aux>
void flatten_internal(ContainerHandle<T, Aux>& tree, T** leaves) {
using tree_t = decltype(tree);
size_t leaves_idx = 0;
auto func = [&](tree_t node) {
if (node.isLeaf()) {
leaves[leaves_idx++] = node.leaf_ptr();
}
};
traverse(tree, FunctionRef<void(tree_t&)>{func});
}
template <typename T, typename Aux>
size_t refresh_leaves_num(const ContainerHandle<T, Aux>& node) {
if (node.isLeaf()) {
node.handle->leaves_num = 1;
return 1;
}
size_t n = 0;
for (size_t i = 0; i < node.size(); ++i) {
n += refresh_leaves_num(node[i]);
}
node.handle->leaves_num = n;
return n;
}
template <typename T, typename Aux>
std::pair<arr<const T*>, std::unique_ptr<TreeSpec<Aux>>> flatten(
const ContainerHandle<T, Aux>& tree) {
refresh_leaves_num(tree);
const size_t n = tree.leaves_num();
arr<T*> leaves(n);
flatten_internal(tree, leaves.data());
auto spec_leaves = std::make_unique<TreeSpecLeaf[]>(n);
return {
std::move(leaves),
std::make_unique<TreeSpec<Aux>>(clone(tree, spec_leaves.get()))};
}
// Duplication of logic for non const ContainerHandle
template <typename T, typename Aux>
std::pair<arr<T*>, std::unique_ptr<TreeSpec<Aux>>> flatten(
ContainerHandle<T, Aux>& tree) {
refresh_leaves_num(tree);
const size_t n = tree.leaves_num();
arr<T*> leaves(n);
flatten_internal(tree, leaves.data());
auto spec_leaves = std::make_unique<TreeSpecLeaf[]>(n);
return {
std::move(leaves),
std::make_unique<TreeSpec<Aux>>(clone(tree, spec_leaves.get()))};
}
} // namespace pytree
} // namespace extension
} // namespace executorch
namespace torch {
namespace executor {
namespace pytree {
// TODO(T197294990): Remove these deprecated aliases once all users have moved
// to the new `::executorch` namespaces.
using ::executorch::extension::pytree::Empty;
using ::executorch::extension::pytree::from_str;
using ::executorch::extension::pytree::TreeSpec;
} // namespace pytree
} // namespace executor
} // namespace torch