/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "vm/dict.h"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "vm/stack.hpp"
#include "common/bitstring.h"
#include "td/utils/Random.h"
#include "td/utils/bits.h"
namespace vm {
/*
 * 
 *  DictionaryBase : basic (common) dictionary manipulation
 * 
 */
DictionaryBase::DictionaryBase(Ref _root, int _n, bool validate)
    : root(std::move(_root)), root_cell(), key_bits(_n), flags(f_root_cached) {
  if (validate) {
    force_validate();
  }
}
DictionaryBase::DictionaryBase(const CellSlice& root_cs, int _n, bool validate)
    : root(), root_cell(), key_bits(_n), flags(0) {
  int f = (int)root_cs.prefetch_ulong(1);
  if (f < 0) {
    flags |= f_invalid;
  } else if (f > 0) {
    if (root_cs.size_refs()) {
      root_cell = root_cs.prefetch_ref();
    } else {
      flags |= f_invalid;
    }
  }
  if (validate) {
    force_validate();
  }
}
DictionaryBase::DictionaryBase(DictAdvance, CellSlice& root_cs, int _n, bool validate)
    : root(), root_cell(), key_bits(_n), flags(0) {
  int f = (int)root_cs.prefetch_ulong(1);
  if (!f) {
    root_cs.advance(1);
  } else if (f > 0 && root_cs.size_refs()) {
    root_cs.advance(1);
    root_cell = root_cs.fetch_ref();
  } else {
    flags |= f_invalid;
  }
  if (validate) {
    force_validate();
  }
}
DictionaryBase::DictionaryBase(Ref| cell, int _n, bool validate)
    : root(), root_cell(std::move(cell)), key_bits(_n), flags(0) {
  if (validate) {
    force_validate();
  }
}
DictionaryBase::DictionaryBase(int _n, bool validate) : root(), root_cell(), key_bits(_n), flags(0) {
  if (validate) {
    force_validate();
  }
}
DictionaryBase::DictionaryBase(DictNonEmpty, Ref _root, int _n, bool validate)
    : root(), root_cell(), key_bits(_n), flags(0) {
  if (_root.is_null() || !init_root_for_nonempty(*_root)) {  // empty ?
    invalidate();                                            // invalidate
  }
  if (validate) {
    force_validate();
  }
}
DictionaryBase::DictionaryBase(DictNonEmpty, const CellSlice& _root, int _n, bool validate)
    : root(), root_cell(), key_bits(_n), flags(0) {
  if (!init_root_for_nonempty(_root)) {
    invalidate();
  }
  if (validate) {
    force_validate();
  }
}
bool DictionaryBase::init_root_for_nonempty(const CellSlice& cs) {
  vm::CellBuilder cb;
  return cb.append_cellslice_bool(cs) && cb.finalize_to(root_cell);
}
Ref | DictionaryBase::construct_root_from(const CellSlice& root_node_cs) {
  vm::CellBuilder cb;
  if (cb.append_cellslice_bool(root_node_cs)) {
    return cb.finalize();
  } else {
    return {};
  }
}
void DictionaryBase::force_validate() {
  if (!is_valid() && !validate()) {
    throw VmError{Excno::dict_err, "invalid dictionary"};
  }
}
bool DictionaryBase::validate() {
  if (is_valid()) {
    return true;
  }
  if (flags & f_invalid) {
    return false;
  }
  if (key_bits < 0 || key_bits > max_key_bits) {
    return invalidate();
  }
  if (flags & f_root_cached) {
    if (root.is_null() || root->size() != 1) {
      return invalidate();
    }
    bool non_empty = root->prefetch_ulong(1);
    if (root->size_refs() != (non_empty ? 1u : 0u)) {
      return invalidate();
    }
    if (root_cell.not_null()) {
      return invalidate();
    }
    if (non_empty) {
      root_cell = root->prefetch_ref();
    }
  } else if (root.not_null()) {
    return invalidate();
  }
  flags |= f_valid;
  return true;
}
Ref DictionaryBase::get_root() const {
  if (!(flags & f_root_cached) && !compute_root()) {
    return {};
  }
  return root;
}
Ref DictionaryBase::extract_root() && {
  if (!(flags & f_root_cached) && !compute_root()) {
    return {};
  }
  flags = f_invalid;
  return std::move(root);
}
bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) && {
  if (!is_valid()) {
    return false;
  }
  flags = f_invalid;
  return cb.store_maybe_ref(std::move(root_cell));
}
bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) const & {
  return is_valid() && cb.store_maybe_ref(root_cell);
}
bool DictionaryBase::compute_root() const {
  if (!is_valid()) {
    return false;
  }
  if (root_cell.is_null()) {
    root = get_empty_dictionary();
    flags |= f_root_cached;
    return true;
  }
  CellBuilder cb;
  cb.store_long(1, 1);
  cb.store_ref(root_cell);
  root = Ref{true, cb.finalize()};
  flags |= f_root_cached;
  return true;
}
Ref DictionaryBase::get_empty_dictionary() {
  static Ref empty_dict{new_empty_dictionary()};
  return empty_dict;
}
Ref DictionaryBase::new_empty_dictionary() {
  CellBuilder cb;  // Builder
  cb.store_long(0, 1);
  return Ref{true, cb.finalize()};
}
Ref | DictionaryFixed::finish_create_leaf(CellBuilder& cb, const CellSlice& value) const {
  if (!cb.append_cellslice_bool(value)) {
    throw VmError{Excno::dict_err, "cannot store new value into a dictionary leaf cell"};
  }
  return cb.finalize();
}
Ref | DictionaryFixed::finish_create_fork(CellBuilder& cb, Ref | c1, Ref | c2, int n) const {
  assert(n > 0);
  if (!(cb.store_ref_bool(std::move(c1)) && cb.store_ref_bool(std::move(c2)))) {
    throw VmError{Excno::dict_err, "cannot store branch references into a dictionary fork cell"};
  }
  return cb.finalize();
}
bool DictionaryFixed::check_fork_raw(Ref cs_ref, int n) const {
  if (cs_ref.is_null()) {
    return false;
  }
  Ref | c1, c2;
  CellSlice& cs = cs_ref.write();
  return cs.fetch_ref_to(c1) && cs.fetch_ref_to(c2) && check_fork(cs, std::move(c1), std::move(c2), n);
}
/*
 * 
 *  Label parser (HmLabel n ~l) for all dictionary types
 *
 */
namespace dict {
LabelParser::LabelParser(Ref cs, int max_label_len, int auto_validate) : remainder(), l_offs(0), l_same(0) {
  if (!parse_label(cs.write(), max_label_len)) {
    l_offs = 0;
  } else {
    s_bits = (l_same ? 0 : l_bits);
    remainder = std::move(cs);
  }
  if (auto_validate) {
    if (auto_validate > 2) {
      validate_ext(max_label_len);
    } else if (auto_validate == 2) {
      validate_simple(max_label_len);
    } else {
      validate();
    }
  }
}
LabelParser::LabelParser(Ref | cell, int max_label_len, int auto_validate) : remainder(), l_offs(0), l_same(0) {
  Ref cs = load_cell_slice_ref(std::move(cell));
  if (!parse_label(cs.unique_write(), max_label_len)) {
    l_offs = 0;
  } else {
    s_bits = (l_same ? 0 : l_bits);
    remainder = std::move(cs);
  }
  if (auto_validate) {
    if (auto_validate > 2) {
      validate_ext(max_label_len);
    } else if (auto_validate == 2) {
      validate_simple(max_label_len);
    } else {
      validate();
    }
  }
}
bool LabelParser::parse_label(CellSlice& cs, int max_label_len) {
  int ltype = (int)cs.prefetch_ulong(2);
  // std::cerr << "parse_label of type " << ltype << " and maximal length " << max_label_len << " in ";
  // cs.dump_hex(std::cerr, 0, true);
  switch (ltype) {
    case 0: {
      l_bits = 0;
      l_offs = 2;
      cs.advance(2);
      return true;
    }
    case 1: {
      cs.advance(1);
      l_bits = cs.count_leading(1);
      // std::cerr << "unary-encoded l_bits = " << l_bits << ", have " << cs.size() << std::endl;
      if (l_bits > max_label_len || !cs.have(2 * l_bits + 1)) {
        return false;
      }
      l_offs = l_bits + 2;
      cs.advance(l_bits + 1);
      return true;
    }
    case 2: {
      int len_bits = 32 - td::count_leading_zeroes32(max_label_len);
      cs.advance(2);
      l_bits = (int)cs.fetch_ulong(len_bits);
      if (l_bits < 0 || l_bits > max_label_len) {
        return false;
      }
      l_offs = len_bits + 2;
      return cs.have(l_bits);
    }
    case 3: {
      int len_bits = 32 - td::count_leading_zeroes32(max_label_len);
      // std::cerr << "len_bits = " << len_bits << ", have " << cs.size() << std::endl;
      if (!cs.have(3 + len_bits)) {
        return false;
      }
      l_same = (int)cs.fetch_ulong(3);
      l_bits = (int)cs.fetch_ulong(len_bits);
      // std::cerr << "l_bits = " << l_bits << ", l_same = " << l_same << std::endl;
      if (l_bits < 0 || l_bits > max_label_len) {
        return false;
      }
      l_offs = -1;
      return true;
    }
    default:
      return false;
  }
}
void LabelParser::validate() const {
  if (!is_valid()) {
    throw VmError{Excno::cell_und, "error while parsing a dictionary node label"};
  }
}
void LabelParser::validate_ext(int n) const {
  validate();
  if (l_bits > n) {
    throw VmError{Excno::dict_err, "invalid dictionary node"};
  } else if (l_bits < n && (remainder->size() != s_bits || remainder->size_refs() != 2)) {
    throw VmError{Excno::dict_err, "invalid dictionary fork node"};
  }
}
void LabelParser::validate_simple(int n) const {
  validate();
  if (l_bits > n) {
    throw VmError{Excno::dict_err, "invalid dictionary node"};
  } else if (l_bits < n && (remainder->size() < s_bits || remainder->size_refs() < 2)) {
    throw VmError{Excno::dict_err, "invalid dictionary fork node"};
  }
}
bool LabelParser::is_prefix_of(td::ConstBitPtr key, int len) const {
  if (l_bits > len) {
    return false;
  } else if (!l_same) {
    //std::cerr << "key is " << key.to_hex(len) << "; len = " << len << "; label_bits = " << l_bits << "; remainder = ";
    //remainder->dump_hex(std::cerr, 0, true);
    return remainder->has_prefix(key, l_bits);
  } else {
    return td::bitstring::bits_memscan(key, l_bits, l_same & 1) == (unsigned)l_bits;
  }
}
bool LabelParser::has_prefix(td::ConstBitPtr key, int len) const {
  return len >= 0 && len <= l_bits && common_prefix_len(key, len) == len;
}
int LabelParser::common_prefix_len(td::ConstBitPtr key, int len) const {
  if (!l_same) {
    //std::cerr << "key is " << key.to_hex(len) << "; len = " << len << "; label_bits = " << l_bits << "; remainder = ";
    //remainder->dump_hex(std::cerr, 0, true);
    return remainder->common_prefix_len(key, std::min(l_bits, len));
  } else {
    return (int)td::bitstring::bits_memscan(key, std::min(l_bits, len), l_same & 1);
  }
}
int LabelParser::extract_label_to(td::BitPtr to) {
  if (!l_same) {
    to.copy_from(remainder->data_bits(), l_bits);
    remainder.write().advance(l_bits);
  } else {
    to.fill(l_same & 1, l_bits);
  }
  return l_bits;
}
int LabelParser::copy_label_prefix_to(td::BitPtr to, int max_len) const {
  if (max_len <= 0) {
    return max_len;
  }
  int sz = std::min(max_len, l_bits);
  if (!l_same) {
    to.copy_from(remainder->data_bits(), sz);
  } else {
    to.fill(l_same & 1, sz);
  }
  return sz;
}
}  // namespace dict
/*
 * 
 *   Usual Dictionary
 * 
 */
using dict::LabelParser;
BitSlice DictionaryFixed::integer_key(td::RefInt256 x, unsigned n, bool sgnd, unsigned char buffer[128], bool quiet) {
  if (x.not_null() && x->fits_bits(n, sgnd)) {
    if (buffer) {
      if (x->export_bits(buffer, 0, n, sgnd)) {
        return BitSlice{{}, buffer, 0, n};
      }
    } else {
      Ref bs{true, n};
      if (x->export_bits(bs.unique_write().reserve_bitslice(n), sgnd)) {
        return static_cast(*bs);
      }
    }
  }
  if (!quiet) {
    throw VmError{Excno::range_chk, "dictionary index out of bounds"};
  }
  return {};
}
bool DictionaryFixed::integer_key_simple(td::RefInt256 x, unsigned n, bool sgnd, td::BitPtr buffer, bool quiet) {
  if (x.not_null() && x->fits_bits(n, sgnd) && x->export_bits(buffer, n, sgnd)) {
    return true;
  }
  if (!quiet) {
    throw VmError{Excno::range_chk, "dictionary index out of bounds"};
  }
  return false;
}
Ref | Dictionary::extract_value_ref(Ref cs) {
  if (cs.is_null()) {
    return {};
  } else if (!cs->size() && cs->size_refs() == 1) {
    return cs->prefetch_ref();
  } else {
    throw VmError{Excno::dict_err, "dictionary value does not consist of exactly one reference"};
  }
}
Ref DictionaryFixed::lookup(td::ConstBitPtr key, int key_len) {
  force_validate();
  if (key_len != get_key_bits() || is_empty()) {
    return {};
  }
  //std::cerr << "dictionary lookup for key = " << key.to_hex(key_len) << std::endl;
  Ref | cell = get_root_cell();
  int n = key_len;
  while (true) {
    LabelParser label{std::move(cell), n, label_mode()};
    if (!label.is_prefix_of(key, n)) {
      //std::cerr << "(not a prefix)\n";
      return {};
    }
    n -= label.l_bits;
    if (n <= 0) {
      assert(!n);
      label.skip_label();
      return std::move(label.remainder);
    }
    key += label.l_bits;
    bool sw = *key++;
    //std::cerr << "key bit at position " << key_bits - n << " equals " << sw << std::endl;
    --n;
    cell = label.remainder->prefetch_ref(sw);
  }
}
Ref | Dictionary::lookup_ref(td::ConstBitPtr key, int key_len) {
  return extract_value_ref(lookup(key, key_len));
}
bool DictionaryFixed::has_common_prefix(td::ConstBitPtr prefix, int prefix_len) {
  force_validate();
  if (is_empty() || prefix_len <= 0) {
    return true;
  }
  if (prefix_len > get_key_bits()) {
    return false;
  }
  LabelParser label{get_root_cell(), get_key_bits(), label_mode()};
  return label.has_prefix(prefix, prefix_len);
}
int DictionaryFixed::get_common_prefix(td::BitPtr buffer, unsigned buffer_len) {
  force_validate();
  if (is_empty()) {
    return 0;
  }
  LabelParser label{get_root_cell(), get_key_bits(), label_mode()};
  return label.copy_label_prefix_to(buffer, (int)buffer_len);
}
bool DictionaryFixed::key_exists(td::ConstBitPtr key, int key_len) {
  return lookup(key, key_len).not_null();
}
bool DictionaryFixed::int_key_exists(long long key) {
  force_validate();
  int l = get_key_bits();
  if (is_empty() || l > 64) {
    return false;
  }
  if (l < 64) {
    long long m = (1LL << (l - 1));
    if (key < -m || key >= m) {
      return false;
    }
  }
  td::BitArray<64> a;
  a.bits().store_int(key, l);
  return key_exists(a.cbits(), l);
}
bool DictionaryFixed::uint_key_exists(unsigned long long key) {
  force_validate();
  int l = get_key_bits();
  if (is_empty() || l > 64) {
    return false;
  }
  if (l < 64 && key >= (1ULL << l)) {
    return false;
  }
  td::BitArray<64> a;
  a.bits().store_uint(key, l);
  return key_exists(a.cbits(), l);
}
namespace {
void append_dict_label_same(CellBuilder& cb, bool same, int len, int max_len) {
  int k = 32 - td::count_leading_zeroes32(max_len);
  assert(len >= 0 && len <= max_len && max_len <= 1023);
  // options: mode '0', requires 2n+2 bits (always for n=0)
  // mode '10', requires 2+k+n bits (only for n<=1)
  // mode '11', requires 3+k bits (for n>=2, k<2n-1)
  if (len > 1 && k < 2 * len - 1) {
    // mode '11'
    cb.store_long(6 + same, 3).store_long(len, k);
  } else if (k < len) {
    // mode '10'
    cb.store_long(2, 2).store_long(len, k).store_long(-static_cast(same), len);
  } else {
    // mode '0'
    cb.store_long(0, 1).store_long(-2, len + 1).store_long(-static_cast(same), len);
  }
}
void append_dict_label(CellBuilder& cb, td::ConstBitPtr label, int len, int max_len) {
  assert(len <= max_len && max_len <= 1023);
  if (len > 0 && (int)td::bitstring::bits_memscan(label, len, *label) == len) {
    return append_dict_label_same(cb, *label, len, max_len);
  }
  int k = 32 - td::count_leading_zeroes32(max_len);
  // two options: mode '0', requires 2n+2 bits
  // mode '10', requires 2+k+n bits
  if (k < len) {
    cb.store_long(2, 2).store_long(len, k);
  } else {
    cb.store_long(0, 1).store_long(-2, len + 1);
  }
  if ((int)cb.remaining_bits() < len) {
    throw VmError{Excno::cell_ov, "cannot store a label into a dictionary cell"};
  }
  cb.store_bits(label, len);
}
std::pair [, bool> dict_set(Ref]| dict, td::ConstBitPtr key, int n,
                                    const Dictionary::store_value_func_t& store_val, Dictionary::SetMode mode) {
  //std::cerr << "dictionary modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
  if (dict.is_null()) {
    // the dictionary is very empty
    if (mode == Dictionary::SetMode::Replace) {
      return std::make_pair [, bool>({}, false);
    }
    // create an one-element dictionary
    CellBuilder cb;
    append_dict_label(cb, key, n, n);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    return std::make_pair(cb.finalize(), true);
  }
  LabelParser label{std::move(dict), n};
  label.validate();
  int pfx_len = label.common_prefix_len(key, n);
  assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
  if (pfx_len < label.l_bits) {
    // have to insert a new node (fork) inside the current edge
    if (mode == Dictionary::SetMode::Replace) {
      // key not found, return unchanged dictionary
      return std::make_pair(Ref]| {}, false);
    }
    // first, create the edge + new leaf cell
    int m = n - pfx_len - 1;
    CellBuilder cb;
    append_dict_label(cb, key + (pfx_len + 1), m, m);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    Ref | c1 = cb.finalize();  // new leaf cell corresponding to `key`
    //cb.reset();
    // create the lower portion of the old edge
    int t = label.l_bits - pfx_len - 1;
    auto cs = std::move(label.remainder);
    if (label.l_same) {
      append_dict_label_same(cb, label.l_same & 1, t, m);
    } else {
      cs.write().advance(pfx_len + 1);
      append_dict_label(cb, cs->data_bits(), t, m);
      cs.unique_write().advance(t);
    }
    // now cs is the old payload of the edge, either a value or two subdictionary references
    if (!cell_builder_add_slice_bool(cb, *cs)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
    }
    Ref | c2 = cb.finalize();  // the other child of the new fork
    // cb.reset();
    append_dict_label(cb, key, pfx_len, n);
    bool sw_bit = key[pfx_len];
    if (sw_bit) {
      c1.swap(c2);
    }
    cb.store_ref(std::move(c1)).store_ref(std::move(c2));
    return std::make_pair(cb.finalize(), true);
  }
  if (label.l_bits == n) {
    // the edge leads to a leaf node
    // this leaf node already contains a value for the key wanted
    if (mode == Dictionary::SetMode::Add) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    // replace the value of the only element of the dictionary
    CellBuilder cb;
    append_dict_label(cb, key, n, n);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    return std::make_pair(cb.finalize(), true);
  }
  // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  label.remainder.clear();
  if (key[label.l_bits]) {
    // insert key into the right child (c2)
    auto res = dict_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
    if (!res.second) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    c2 = std::move(res.first);
  } else {
    // insert key into the left child (c1)
    auto res = dict_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
    if (!res.second) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    c1 = std::move(res.first);
  }
  // create a new label with the same content
  CellBuilder cb;
  append_dict_label(cb, key, label.l_bits, n);
  cb.store_ref(std::move(c1)).store_ref(std::move(c2));
  return std::make_pair(cb.finalize(), true);
}
std::tuple [, Ref]| , bool> dict_lookup_set(Ref | dict, td::ConstBitPtr key, int n,
                                                            const Dictionary::store_value_func_t& store_val,
                                                            Dictionary::SetMode mode) {
  //std::cerr << "dictionary lookup/modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
  if (dict.is_null()) {
    // the dictionary is very empty
    if (mode == Dictionary::SetMode::Replace) {
      return std::make_tuple [, Ref]| , bool>({}, {}, false);
    }
    // create an one-element dictionary
    CellBuilder cb;
    append_dict_label(cb, key, n, n);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    return std::make_tuple [, Ref]| , bool>({}, cb.finalize(), true);
  }
  LabelParser label{std::move(dict), n};
  int pfx_len = label.common_prefix_len(key, n);
  assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
  if (pfx_len < label.l_bits) {
    // have to insert a new node (fork) inside the current edge
    if (mode == Dictionary::SetMode::Replace) {
      // key not found, return unchanged dictionary
      return std::make_tuple [, Ref]| , bool>({}, {}, false);
    }
    // first, create the edge + new leaf cell
    int m = n - pfx_len - 1;
    CellBuilder cb;
    append_dict_label(cb, key + (pfx_len + 1), m, m);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    Ref | c1 = cb.finalize();  // new leaf cell corresponding to `key`
    //cb.reset();
    // create the lower portion of the old edge
    int t = label.l_bits - pfx_len - 1;
    auto cs = std::move(label.remainder);
    if (label.l_same) {
      append_dict_label_same(cb, label.l_same & 1, t, m);
    } else {
      cs.write().advance(pfx_len + 1);
      append_dict_label(cb, cs->data_bits(), t, m);
      cs.unique_write().fetch_bits(t);
    }
    // now cs is the old payload of the edge, either a value or two subdictionary references
    if (!cell_builder_add_slice_bool(cb, *cs)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
    }
    Ref | c2 = cb.finalize();  // the other child of the new fork
    //cb.reset();
    append_dict_label(cb, key, pfx_len, n);
    bool sw_bit = key[pfx_len];
    if (sw_bit) {
      c1.swap(c2);
    }
    cb.store_ref(std::move(c1)).store_ref(std::move(c2));
    return std::make_tuple [, Ref]| , bool>({}, cb.finalize(), true);
  }
  if (label.l_bits == n) {
    // the edge leads to a leaf node
    // this leaf node already contains a value for the key wanted
    auto old_val = std::move(label.remainder);
    old_val.write().advance(label.s_bits);
    if (mode == Dictionary::SetMode::Add) {
      // return unchanged dictionary
      return std::make_tuple [, Ref]| , bool>(std::move(old_val), {}, false);
    }
    // replace the value of the only element of the dictionary
    CellBuilder cb;
    append_dict_label(cb, key, n, n);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    return std::make_tuple(std::move(old_val), cb.finalize(), true);
  }
  // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  Ref old_val;
  label.remainder.clear();
  if (key[label.l_bits]) {
    // insert key into the right child (c2)
    auto res = dict_lookup_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
    old_val = std::get [>(res);
    if (!std::get(res)) {
      // return unchanged dictionary
      return std::make_tuple][, Ref]| , bool>(std::move(old_val), {}, false);
    }
    c2 = std::get [>(std::move(res));
  } else {
    // insert key into the left child (c1)
    auto res = dict_lookup_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
    old_val = std::get][>(res);
    if (!std::get(res)) {
      // return unchanged dictionary
      return std::make_tuple(std::move(old_val), Ref]| {}, false);
    }
    c1 = std::get [>(std::move(res));
  }
  // create a new label with the same content
  CellBuilder cb;
  append_dict_label(cb, key, label.l_bits, n);
  cb.store_ref(std::move(c1)).store_ref(std::move(c2));
  return std::make_tuple][, Ref]| , bool>(std::move(old_val), cb.finalize(), true);
}
std::pair [, bool> pfx_dict_set(Ref]| dict, td::ConstBitPtr key, int m, int n,
                                        const PrefixDictionary::store_value_func_t& store_val,
                                        Dictionary::SetMode mode) {
  // std::cerr << "up to " << n << "-bit prefix code dictionary modification for " << m << "-bit key = " << key.to_hex(m) << std::endl;
  if (m > n) {
    return std::make_pair(Ref | {}, false);
  }
  if (dict.is_null()) {
    // the dictionary is very empty
    if (mode == Dictionary::SetMode::Replace) {
      return std::make_pair(Ref | {}, false);
    }
    // create an one-element dictionary
    CellBuilder cb;
    append_dict_label(cb, key, m, n);
    cb.store_long(0, 1);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    return std::make_pair(cb.finalize(), true);
  }
  LabelParser label{std::move(dict), n, 1};
  int l = label.common_prefix_len(key, m);
  assert(l >= 0 && l <= label.l_bits && label.l_bits <= n && l <= m && m <= n);
  if (l < label.l_bits) {
    // have to insert a new node (fork) inside the current edge
    if (l == m || mode == Dictionary::SetMode::Replace) {
      // key not found, return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    // first, create the edge + new leaf cell
    int q = l + 1;
    CellBuilder cb;
    append_dict_label(cb, key + q, m - q, n - q);
    cb.store_long(0, 1);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a prefix dictionary cell"};
    }
    Ref | c1 = cb.finalize();  // new leaf cell corresponding to `key`
    // cb.reset(); // contained in finalize()
    // create the lower portion of the old edge
    int t = label.l_bits - q;
    auto cs = std::move(label.remainder);
    if (label.l_same) {
      append_dict_label_same(cb, label.l_same & 1, t, n - q);
    } else {
      cs.write().advance(l + 1);
      append_dict_label(cb, cs->data_bits(), t, n - q);
      cs.unique_write().advance(t);
    }
    // now cs is the old payload of the edge, either a value or two subdictionary references
    if (!cell_builder_add_slice_bool(cb, *cs)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
    }
    Ref | c2 = cb.finalize();  // the other child of the new fork
    //cb.reset();
    append_dict_label(cb, key, l, n);
    bool sw_bit = key[l];
    if (sw_bit) {
      c1.swap(c2);
    }
    cb.store_long(1, 1).store_ref(c1).store_ref(c2);
    return std::make_pair [, bool>(cb.finalize(), true);
  }
  assert(label.l_bits == l);
  label.skip_label();
  if (!label.remainder->have(1)) {
    throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
  }
  if (!label.remainder.unique_write().fetch_ulong(1)) {
    // the edge leads to a leaf node
    if (l != m || mode == Dictionary::SetMode::Add) {
      // return unchanged dictionary
      return std::make_pair][, bool>({}, false);
    }
    // this leaf node already contains a value for the key wanted
    // replace the value of the only element of the dictionary
    CellBuilder cb;
    append_dict_label(cb, key, m, n);
    cb.store_long(0, 1);
    if (!store_val(cb)) {
      throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
    }
    return std::make_pair][, bool>(cb.finalize(), true);
  }
  // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
  if (label.remainder->size() || label.remainder->size_refs() != 2) {
    throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
  }
  if (m == l) {
    // cannot insert a value into a fork
    return std::make_pair(Ref]| {}, false);
  }
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  label.remainder.clear();
  if (key[l++]) {
    // insert key into the right child (c2)
    auto res = pfx_dict_set(std::move(c2), key + l, m - l, n - l, store_val, mode);
    if (!res.second) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    c2 = std::move(res.first);
  } else {
    // insert key into the left child (c1)
    auto res = pfx_dict_set(std::move(c1), key + l, m - l, n - l, store_val, mode);
    if (!res.second) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    c1 = std::move(res.first);
  }
  // create a new label with the same content
  CellBuilder cb;
  append_dict_label(cb, key, l - 1, n);
  cb.store_long(1, 1).store_ref(std::move(c1)).store_ref(std::move(c2));
  return std::make_pair(cb.finalize(), true);
}
std::pair [, Ref]| > pfx_dict_lookup_delete(Ref | dict, td::ConstBitPtr key, int m, int n) {
  //std::cerr << "up to " << n << "-bit prefix dictionary delete for " << m << "-bit key = " << key.to_hex(m) << std::endl;
  if (dict.is_null()) {
    // the dictionary is very empty
    return std::make_pair(Ref{}, Ref | {});
  }
  LabelParser label{std::move(dict), n, 1};
  int l = label.common_prefix_len(key, m);
  assert(l >= 0 && l <= label.l_bits && l <= m && m <= n && label.l_bits <= n);
  if (l < label.l_bits) {
    // key not found
    return std::make_pair(Ref{}, Ref | {});
  }
  assert(label.l_bits == l);
  label.skip_label();
  if (!label.remainder->have(1)) {
    throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
  }
  if (!label.remainder.unique_write().fetch_ulong(1)) {
    // the edge leads to a leaf node
    if (l < m) {
      // key not found
      return std::make_pair(Ref{}, Ref | {});
    }
    // this leaf node contains the value for the key wanted
    return std::make_pair(std::move(label.remainder), Ref | {});
  }
  // main case: the edge leads to a fork, have to delete the key either from the right or from the left subtree
  if (label.remainder->size() || label.remainder->size_refs() != 2) {
    throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
  }
  if (l == m) {
    // the fork itself cannot correspond to a key, key not found
    return std::make_pair(Ref{}, Ref | {});
  }
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  Ref old_val;
  label.remainder.clear();
  bool sw_bit = key[l++];
  if (sw_bit) {
    // delete key from the right child (c2)
    auto res = pfx_dict_lookup_delete(std::move(c2), key + l, m - l, n - l);
    if (res.first.is_null()) {
      // return unchanged dictionary
      return std::make_pair(Ref{}, Ref | {});
    }
    old_val = std::move(res.first);
    c2 = std::move(res.second);
  } else {
    // delete key from the left child (c1)
    auto res = pfx_dict_lookup_delete(std::move(c1), key + l, m - l, n - l);
    if (res.first.is_null()) {
      // return unchanged dictionary
      return std::make_pair(Ref{}, Ref | {});
    }
    old_val = std::move(res.first);
    c1 = std::move(res.second);
  }
  if (c1.not_null() && c2.not_null()) {
    // create a new label with the same content leading to a fork with modified children
    CellBuilder cb;
    append_dict_label(cb, key, label.l_bits, n);
    cb.store_long(1, 1).store_ref(std::move(c1)).store_ref(std::move(c2));
    return std::make_pair(std::move(old_val), cb.finalize());
  }
  // have to merge current edge with the edge leading to c1 or c2
  if (!sw_bit) {
    c1.swap(c2);
  }
  assert(c1.not_null() && c2.is_null());
  unsigned char buffer[Dictionary::max_key_bytes];
  td::BitPtr bw{buffer};
  bw.concat(key, label.l_bits);
  bw.concat_same(!sw_bit, 1);
  LabelParser label2{std::move(c1), n - l, 1};
  bw += label2.extract_label_to(bw);
  assert(bw.offs >= 0 && bw.offs <= Dictionary::max_key_bits);
  CellBuilder cb;
  append_dict_label(cb, td::ConstBitPtr{buffer}, bw.offs, n);
  if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
    throw VmError{Excno::cell_ov, "cannot change label of an old prefix code dictionary cell while merging edges"};
  }
  label2.remainder.clear();
  return std::make_pair(std::move(old_val), cb.finalize());
}
Ref | dict_map(Ref | dict, td::BitPtr key_buffer, int n, int total_key_len,
                   const Dictionary::map_func_t& map_func) {
  if (dict.is_null()) {
    // dictionary is empty
    return dict;
  }
  LabelParser label{std::move(dict), n};
  int l = label.l_bits;
  label.extract_label_to(key_buffer);
  if (l == n) {
    // leaf node, value left in label.remainder
    CellBuilder cb;
    append_dict_label(cb, key_buffer, l, n);
    if (!map_func(cb, std::move(label.remainder), key_buffer + n - total_key_len, total_key_len)) {
      return {};  // leaf to be omitted from the result altogether
    }
    return cb.finalize();
  }
  assert(l >= 0 && l < n);
  // a fork with two children, c1 and c2
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  key_buffer += l + 1;
  key_buffer[-1] = 0;
  // recursive map applied to both children
  c1 = dict_map(std::move(c1), key_buffer, n - l - 1, total_key_len, map_func);
  key_buffer[-1] = 1;
  c2 = dict_map(std::move(c2), key_buffer, n - l - 1, total_key_len, map_func);
  if (c1.is_null() && c2.is_null()) {
    return {};  // both children have become empty
  }
  if (c1.is_null() || c2.is_null()) {
    if (c1.is_null()) {
      c1 = std::move(c2);
      // notice that the label of c2 is still in key_buffer
    } else {
      // recover label of c1
      key_buffer[-1] = 0;
    }
    // one of children is empty, have to combine current edge with the root edge of c1
    LabelParser label1{std::move(c1), n - l - 1};
    label1.extract_label_to(key_buffer);
    CellBuilder cb;
    key_buffer -= l + 1;
    // store combined label for the new edge
    append_dict_label(cb, key_buffer, l + 1 + label1.l_bits, n);
    // store payload
    if (!cell_builder_add_slice_bool(cb, *label1.remainder)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
    }
    return cb.finalize();
  }
  // main case: both children c1 and c2 remain non-empty
  key_buffer -= l + 1;
  CellBuilder cb;
  append_dict_label(cb, key_buffer, l, n);
  return cb.store_ref(std::move(c1)).store_ref(std::move(c2)).finalize();
}
}  // namespace
bool Dictionary::set_gen(td::ConstBitPtr key, int key_len, const std::function& store_val,
                         SetMode mode) {
  force_validate();
  if (key_len != get_key_bits()) {
    return false;
  }
  auto res = dict_set(get_root_cell(), key, key_len, store_val, mode);
  if (res.second) {
    set_root_cell(std::move(res.first));
  }
  return res.second;
}
bool Dictionary::set(td::ConstBitPtr key, int key_len, Ref value, SetMode mode) {
  return set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); }, mode);
}
bool Dictionary::set_ref(td::ConstBitPtr key, int key_len, Ref | val_ref, SetMode mode) {
  return set_gen(key, key_len, [val_ref](CellBuilder& cb) { return cb.store_ref_bool(val_ref); }, mode);
}
bool Dictionary::set_builder(td::ConstBitPtr key, int key_len, Ref val_b, SetMode mode) {
  return set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
bool Dictionary::set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& val_b, SetMode mode) {
  return set_gen(key, key_len, [&val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
Ref Dictionary::lookup_set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val,
                                          SetMode mode) {
  force_validate();
  if (key_len != get_key_bits()) {
    return {};
  }
  auto res = dict_lookup_set(get_root_cell(), key, key_len, store_val, mode);
  if (std::get(res)) {
    set_root_cell(std::get [>(res));
  }
  return std::get][>(std::move(res));
}
Ref Dictionary::lookup_set(td::ConstBitPtr key, int key_len, Ref value, SetMode mode) {
  return lookup_set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); },
                        mode);
}
Ref]| Dictionary::lookup_set_ref(td::ConstBitPtr key, int key_len, Ref | val_ref, SetMode mode) {
  return extract_value_ref(
      lookup_set_gen(key, key_len, [val_ref](CellBuilder& cb) { return cb.store_ref_bool(val_ref); }, mode));
}
Ref Dictionary::lookup_set_builder(td::ConstBitPtr key, int key_len, Ref val_b, SetMode mode) {
  return lookup_set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
std::pair [, Ref]| > DictionaryFixed::dict_lookup_delete(Ref | dict, td::ConstBitPtr key,
                                                                         int n) const {
  // std::cerr << "dictionary delete for " << n << "-bit key = " << key.to_hex(n) << std::endl;
  if (dict.is_null()) {
    // the dictionary is very empty
    return std::make_pair(Ref{}, Ref | {});
  }
  LabelParser label{std::move(dict), n, label_mode()};
  int pfx_len = label.common_prefix_len(key, n);
  assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
  if (pfx_len < label.l_bits) {
    // key not found
    return std::make_pair(Ref{}, Ref | {});
  }
  if (label.l_bits == n) {
    // the edge leads to a leaf node
    // this leaf node contains the value for the key wanted
    label.skip_label();
    return std::make_pair(std::move(label.remainder), Ref | {});
  }
  // main case: the edge leads to a fork, have to delete the key either from the right or from the left subtree
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  Ref old_val;
  label.remainder.clear();
  bool sw_bit = key[label.l_bits];
  if (sw_bit) {
    // delete key from the right child (c2)
    auto res = dict_lookup_delete(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1);
    if (res.first.is_null()) {
      // return unchanged dictionary
      return std::make_pair(Ref{}, Ref | {});
    }
    old_val = std::move(res.first);
    c2 = std::move(res.second);
  } else {
    // delete key from the left child (c1)
    auto res = dict_lookup_delete(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1);
    if (res.first.is_null()) {
      // return unchanged dictionary
      return std::make_pair(Ref{}, Ref | {});
    }
    old_val = std::move(res.first);
    c1 = std::move(res.second);
  }
  if (c1.not_null() && c2.not_null()) {
    // create a new label with the same content leading to a fork with modified children
    CellBuilder cb;
    append_dict_label(cb, key, label.l_bits, n);
    return std::make_pair(std::move(old_val), finish_create_fork(cb, std::move(c1), std::move(c2), n - label.l_bits));
  }
  // have to merge current edge with the edge leading to c1 or c2
  if (!sw_bit) {
    c1.swap(c2);
  }
  assert(c1.not_null() && c2.is_null());
  unsigned char buffer[Dictionary::max_key_bytes];
  td::BitPtr bw{buffer};
  bw.concat(key, label.l_bits);
  bw.concat_same(!sw_bit, 1);
  LabelParser label2{std::move(c1), n - label.l_bits - 1, label_mode()};
  bw += label2.extract_label_to(bw);
  assert(bw.offs >= 0 && bw.offs <= Dictionary::max_key_bits);
  CellBuilder cb;
  append_dict_label(cb, td::ConstBitPtr{buffer}, bw.offs, n);
  if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
    throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
  }
  label2.remainder.clear();
  return std::make_pair(std::move(old_val), cb.finalize());
}
Ref DictionaryFixed::lookup_delete(td::ConstBitPtr key, int key_len) {
  force_validate();
  if (key_len != get_key_bits()) {
    return {};
  }
  auto res = dict_lookup_delete(get_root_cell(), key, key_len);
  if (res.first.not_null()) {
    set_root_cell(std::move(res.second));
  }
  return std::move(res.first);
}
Ref | Dictionary::lookup_delete_ref(td::ConstBitPtr key, int key_len) {
  return extract_value_ref(lookup_delete(key, key_len));
}
Ref DictionaryFixed::dict_lookup_minmax(Ref | dict, td::BitPtr key_buffer, int n, int mode) const {
  if (dict.is_null()) {
    return {};
  }
  while (1) {
    LabelParser label{std::move(dict), n, label_mode()};
    int l = label.extract_label_to(key_buffer);
    assert(l >= 0 && l <= n);
    key_buffer += l;
    n -= l;
    if (!n) {
      return std::move(label.remainder);
    }
    if (l) {
      mode >>= 1;
    }
    bool bit = mode & 1;
    dict = label.remainder->prefetch_ref(bit);
    *key_buffer++ = bit;
    --n;
    mode >>= 1;
  }
}
Ref DictionaryFixed::dict_lookup_nearest(Ref | dict, td::BitPtr key_buffer, int n, bool allow_eq,
                                                    int mode) const {
  if (dict.is_null()) {
    return {};
  }
  LabelParser label{dict, n, label_mode()};
  int pfx_len = label.common_prefix_len(key_buffer, n);
  assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
  if (pfx_len < label.l_bits) {
    if (key_buffer[pfx_len] == ((mode >> static_cast(pfx_len != 0)) & 1)) {
      return {};
    } else {
      return dict_lookup_minmax(std::move(dict), key_buffer, n, ~mode);
    }
  }
  dict.clear();
  if (label.l_bits) {
    mode >>= 1;
  }
  key_buffer += label.l_bits;
  n -= label.l_bits;
  if (!n) {
    if (!allow_eq) {
      return {};
    }
    label.skip_label();
    return std::move(label.remainder);
  }
  bool bit = *key_buffer++;
  auto res = dict_lookup_nearest(label.remainder->prefetch_ref(bit), key_buffer, n - 1, allow_eq, mode >> 1);
  if (res.not_null() || bit == (mode & 1)) {
    return res;
  }
  key_buffer[-1] = mode & 1;
  dict = label.remainder->prefetch_ref(mode & 1);
  label.remainder.clear();
  return dict_lookup_minmax(std::move(dict), key_buffer, n - 1, ~mode >> 1);
}
Ref DictionaryFixed::lookup_nearest_key(td::BitPtr key_buffer, int key_len, bool fetch_next, bool allow_eq,
                                                   bool invert_first) {
  force_validate();
  if (key_len != get_key_bits()) {
    return {};
  }
  return dict_lookup_nearest(get_root_cell(), key_buffer, key_len, allow_eq,
                             (-static_cast(fetch_next)) ^ static_cast(invert_first));
}
Ref DictionaryFixed::get_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
  force_validate();
  if (key_len != get_key_bits()) {
    return {};
  }
  return dict_lookup_minmax(get_root_cell(), key_buffer, key_len,
                            (-static_cast(fetch_max)) ^ static_cast(invert_first));
}
Ref | Dictionary::get_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
  return extract_value_ref(get_minmax_key(key_buffer, key_len, fetch_max, invert_first));
}
Ref DictionaryFixed::extract_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max,
                                                   bool invert_first) {
  force_validate();
  if (key_len != get_key_bits()) {
    return {};
  }
  auto val = dict_lookup_minmax(get_root_cell(), key_buffer, key_len, -(fetch_max ? 1 : 0) ^ (invert_first ? 1 : 0));
  if (val.is_null()) {
    return {};
  }
  auto res = dict_lookup_delete(get_root_cell(), key_buffer, key_len);
  assert(res.first.not_null());
  set_root_cell(std::move(res.second));
  return val;
}
Ref | Dictionary::extract_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
  return extract_value_ref(extract_minmax_key(key_buffer, key_len, fetch_max, invert_first));
}
/*
 *
 *   ITERATORS (to be moved into a separate file)
 *
 */
bool DictIterator::prevalidate(int mode) {
  if (key_bits_ <= 0 || key_bits_ > Dictionary::max_key_bits) {
    reset();
    flags_ &= ~f_valid;
    key_bits_ = 0;
    return false;
  } else {
    if (mode >= 0) {
      order_ = -(mode & 1) ^ ((mode >> 1) & 1);
    }
    flags_ |= f_valid;
    return true;
  }
}
bool DictIterator::bind(const DictionaryFixed& dict, int do_rewind) {
  if (!is_valid() || !is_bound_to(dict)) {
    return false;
  }
  dict_ = &dict;
  label_mode_ = dict.label_mode();
  return !do_rewind || rewind(do_rewind < 0);
}
bool DictIterator::rebind_to(const DictionaryFixed& dict, int do_rewind) {
  reset();
  dict_ = &dict;
  label_mode_ = dict.label_mode();
  root_ = dict.get_root_cell();
  key_bits_ = dict.get_key_bits();
  flags_ &= 3;
  return prevalidate() && (!do_rewind || rewind(do_rewind < 0));
}
int DictIterator::compare_keys(td::ConstBitPtr a, td::ConstBitPtr b) const {
  if (!key_bits_) {
    return 0;
  }
  int c = (int)*a - (int)*b;
  if (c) {
    return (order_ & 1) ? -c : c;
  }
  c = a.compare(b, key_bits_);
  return order_ >= 0 ? c : -c;
}
bool DictIterator::dive(int mode) {
  int n = key_bits_, m = 0;
  Ref | node = path_.empty() ? root_ : path_.back().next;
  if (!path_.empty()) {
    m = path_.back().pos + 1;
    n -= m;
    mode >>= 1;
  }
  // similar to dict_lookup_minmax: create new path down until the leaf
  while (1) {
    LabelParser label{std::move(node), n, label_mode_};
    int l = label.extract_label_to(key(m));
    assert(l >= 0 && l <= n);
    m += l;
    n -= l;
    if (!n) {
      leaf_ = std::move(label.remainder);
      return true;
    }
    if (l) {
      mode >>= 1;
    }
    int bit = mode & 1;
    node = label.remainder->prefetch_ref(bit);
    auto alt = label.remainder->prefetch_ref(1 - bit);
    path_.emplace_back(node, std::move(alt), m, bit);
    *key(m++) = (bool)bit;
    --n;
    mode >>= 1;
  }
}
bool DictIterator::rewind(bool to_end) {
  if (!is_valid()) {
    return false;
  }
  if (root_.is_null()) {
    return true;
  }
  auto node = root_;
  int k = 0, mode = order_ ^ -(int)to_end;
  // NB: can optimize by reusing several first entries of current path_
  while (k < (int)path_.size()) {
    auto& pe = path_[k++];
    assert(pe.pos >= 0 && pe.pos < key_bits_);
    if (pe.pos) {
      mode >>= 1;
    }
    if (pe.v != (bool)(mode & 1)) {
      // went different way at this node before, rotate and stop going down
      pe.rotate(key());
      leaf_.clear();
      path_.resize(k);  // drop the remainder of the original path after first incorrect branch
      return dive(mode);
    }
    mode >>= 1;
  }
  return !eof() || dive(mode);
}
bool DictIterator::next(bool go_back) {
  if (!is_valid() || root_.is_null() || eof()) {
    return false;
  }
  leaf_.clear();
  int mode = order_ ^ -(int)go_back;
  while (!path_.empty()) {
    auto& pe = path_.back();
    int bit = (mode >> (pe.pos > 0)) & 1;
    if (pe.v == bit) {
      pe.rotate(key());
      return dive(mode);
    }
    path_.pop_back();
  }
  return false;
}
bool DictIterator::lookup(td::ConstBitPtr pos, int pos_bits, bool strict_after, bool backw) {
  if (!is_valid() || root_.is_null() || pos_bits < 0 || pos_bits > key_bits_) {
    return false;
  }
  int fill_mode = -(strict_after ^ backw) ^ order_;
  if (!eof()) {
    // reuse part of current path
    std::size_t bp0 = 0;
    int bp;
    if (!key().compare(pos, pos_bits, &bp0)) {
      bp = pos_bits;
      if (bp >= key_bits_) {
        // already at the desired element
        return !strict_after || next(backw);
      }
    } else {
      bp = (int)bp0;
    }
    int k = 0;
    while (k < (int)path_.size() && path_[k].pos <= bp) {
      auto& pe = path_[k];
      if (pe.pos == bp) {
        if (bp < pos_bits || pe.v != ((fill_mode >> (bp > 0)) & 1)) {
          // rotate the last path element if it branched in incorrect direction
          path_[k++].rotate(key());
        }
        break;
      }
      ++k;
    }
    path_.resize(k);  // drop the remainder of the path
  }
  int m = 0, n = key_bits_;
  auto node = path_.empty() ? root_ : path_.back().next;
  if (!path_.empty()) {
    m = path_.back().pos + 1;  // m <= pos_bits + 1
    n -= m;
  }
  int mode = -backw ^ order_, action = 0;
  while (m < pos_bits && !action) {
    LabelParser label{std::move(node), n, label_mode_};
    int pfx_len = label.common_prefix_len(pos + m, pos_bits - m);
    int l = label.extract_label_to(key() + m);
    assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
    if (pfx_len == pos_bits - m) {
      // end of given position prefix
      if (strict_after) {
        // have to backtrace
        action = 2;
        break;
      } else {
        // label has correct prefix, register and dive down
        action = 1;
      }
    } else if (pfx_len < l) {
      // all subtree is either smaller or larger than required
      if (pos[m + pfx_len] != ((mode >> (int)(m + pfx_len > 0)) & 1)) {
        // label smaller than required, have to backtrace
        action = 2;
        break;
      } else {
        // label larger than required, register node and dive down
        action = 1;
      }
    }
    // if we are here, then either action=1 (dive down activated)
    // ... or l = pfx_len < pos_bits - m
    // continue going down
    m += l;
    n -= l;
    if (!n) {
      // key found in a leaf
      leaf_ = std::move(label.remainder);
      return true;
    }
    bool bit = action ? ((mode >> (m > 0)) & 1) : pos[m];
    node = label.remainder->prefetch_ref(bit);
    auto alt = label.remainder->prefetch_ref(1 - bit);
    path_.emplace_back(node, std::move(alt), m, bit);
    *key(m++) = (bool)bit;
    --n;
  }
  if (!action) {
    action = (strict_after ? 2 : 1);
  }
  if (action == 2) {
    // have to backtrace to the "next" larger branch
    // similar to next()
    leaf_.clear();
    while (!path_.empty()) {
      auto& pe = path_.back();
      int bit = (mode >> (pe.pos > 0)) & 1;
      if (pe.v == bit) {
        pe.rotate(key());
        return dive(mode);
      }
      path_.pop_back();
    }
    return false;  // eof: no suitable element
  }
  // action=1, dive down
  return dive(mode);
}
DictIterator DictionaryFixed::null_iterator() {
  force_validate();
  return DictIterator{*this};
}
DictIterator DictionaryFixed::make_iterator(int mode) {
  force_validate();
  DictIterator it{*this, mode};
  it.rewind();
  return it;
}
DictIterator DictionaryFixed::init_iterator(bool backw, bool invert_first) {
  return make_iterator((int)backw + 2 * (int)invert_first);
}
DictIterator DictionaryFixed::begin() {
  return init_iterator();
}
DictIterator DictionaryFixed::end() {
  return null_iterator();
}
DictIterator DictionaryFixed::cbegin() {
  return begin();
}
DictIterator DictionaryFixed::cend() {
  return end();
}
DictIterator DictionaryFixed::rbegin() {
  return init_iterator(true);
}
DictIterator DictionaryFixed::rend() {
  return null_iterator();
}
DictIterator DictionaryFixed::crbegin() {
  return rbegin();
}
DictIterator DictionaryFixed::crend() {
  return rend();
}
/*
 *
 *   END (ITERATORS)
 *
 */
std::pair [, bool> DictionaryFixed::extract_prefix_subdict_internal(Ref]| dict, td::ConstBitPtr prefix,
                                                                            int prefix_len, bool remove_prefix) const {
  if (is_empty() || prefix_len <= 0) {
    return {{}, false};  // unchanged
  }
  if (prefix_len > get_key_bits()) {
    return {{}, true};  // empty dict
  }
  int n = get_key_bits(), m = 0;
  while (true) {
    LabelParser label{std::move(dict), n - m, label_mode()};
    int l = std::min(prefix_len - m, label.l_bits);
    if (label.common_prefix_len(prefix + m, l) < l) {
      return {{}, true};  // empty dict
    }
    if (m + label.l_bits < prefix_len) {
      m += label.l_bits;
      dict = label.remainder->prefetch_ref(prefix[m++]);
      continue;
    }
    // end, have consumed all of prefix
    vm::CellBuilder cb;
    if (!remove_prefix) {
      if (!m) {
        // dictionary unchanged: all keys already begin with prefix
        return {{}, false};
      }
      // concatenate prefix with a suffix of the label
      assert(m <= prefix_len);
      unsigned char buffer[max_key_bytes];
      auto p = td::BitPtr{buffer};
      p.copy_from(prefix, m);
      label.extract_label_to(p + m);
      append_dict_label(cb, p, m + label.l_bits, key_bits);
    } else if (!label.l_same) {
      m += label.l_bits - prefix_len;  // leave that many last bits of the label
      append_dict_label(cb, label.bits_end() - m, m, key_bits - prefix_len);
      label.skip_label();
    } else {
      m += label.l_bits - prefix_len;  // leave that many last bits of the label
      append_dict_label_same(cb, label.l_same & 1, m, key_bits - prefix_len);
    }
    if (!cb.append_cellslice_bool(*label.remainder)) {
      throw VmError{Excno::cell_ov, "cannot create new dictionary root while constructing prefix subdictionary"};
    }
    return {Ref | {cb.finalize()}, true};
  }
}
bool DictionaryFixed::cut_prefix_subdict(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix) {
  force_validate();
  if (prefix_len < 0) {
    return false;
  }
  if (prefix_len > key_bits && remove_prefix) {
    return false;
  }
  auto res = extract_prefix_subdict_internal(get_root_cell(), prefix, prefix_len, remove_prefix);
  if (remove_prefix) {
    key_bits -= prefix_len;
  }
  if (res.second) {
    set_root_cell(std::move(res.first));
  }
  return true;
}
Ref DictionaryFixed::extract_prefix_subdict_root(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix) {
  force_validate();
  auto res = extract_prefix_subdict_internal(get_root_cell(), prefix, prefix_len, remove_prefix);
  return res.second ? res.first : root_cell;
}
std::pair [, int> DictionaryFixed::dict_filter(Ref]| dict, td::BitPtr key, int n,
                                                       const DictionaryFixed::filter_func_t& check_leaf,
                                                       int& skip_rest) const {
  // std::cerr << "dictionary filter for " << n << "-bit key = " << (key + n - key_bits).to_hex(key_bits - n)
  //           << std::endl;
  if (dict.is_null()) {
    // empty dictionary, return unchanged
    return {{}, 0};
  }
  if (skip_rest >= 0) {
    // either drop subtree completely (if skip_rest>0), or retain it completely (if skip_rest=0)
    return {{}, skip_rest};
  }
  LabelParser label{std::move(dict), n, label_mode()};
  assert(label.l_bits >= 0 && label.l_bits <= n);
  label.extract_label_to(key);
  key += label.l_bits;
  if (label.l_bits == n) {
    // leaf
    int res = check_leaf(label.remainder.write(), key - key_bits, key_bits);
    if (res >= (1 << 30)) {
      // skip all, or retain all
      res &= (1 << 30) - 1;
      skip_rest = (res ? 0 : (1 << 30));
    }
    return {{}, res < 0 ? res : !res};
  }
  // fork, process left and right subtrees
  ++key;
  key[-1] = false;
  int delta = label.l_bits + 1;
  n -= delta;
  auto left_res = dict_filter(label.remainder->prefetch_ref(0), key, n, check_leaf, skip_rest);
  if (left_res.second < 0) {
    return left_res;
  }
  key[-1] = true;
  auto right_res = dict_filter(label.remainder->prefetch_ref(1), key, n, check_leaf, skip_rest);
  if ((left_res.second | right_res.second) <= 0) {
    // error in right, or both left and right unchanged
    return right_res;
  }
  auto left = left_res.second ? std::move(left_res.first) : label.remainder->prefetch_ref(0);
  auto right = right_res.second ? std::move(right_res.first) : label.remainder->prefetch_ref(1);
  // 2^30 is effectively infinity, meaning that we dropped whole branches with unknown # of nodes
  auto changes = ((left_res.second | right_res.second) & (1 << 30)) ? (1 << 30) : left_res.second + right_res.second;
  label.clear();
  if (left.is_null()) {
    if (right.is_null()) {
      // both branches are empty => the result is an empty tree
      return {{}, changes};
    }
    std::swap(left, right);
  } else if (right.is_null()) {
    key[-1] = false;
  } else {
    // both new branches are non-empty => create new fork
    CellBuilder cb;
    append_dict_label(cb, key - delta, label.l_bits, n + delta);
    return {finish_create_fork(cb, std::move(left), std::move(right), n + 1), changes};
  }
  // only one child (in `left`) remains, collapse an edge
  // NB: similar to code in lookup_delete()
  assert(left.not_null() && right.is_null());
  LabelParser label2{std::move(left), n, label_mode()};
  label2.extract_label_to(key);
  CellBuilder cb;
  append_dict_label(cb, key - delta, delta + label2.l_bits, n + delta);
  if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
    throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
  }
  label2.remainder.clear();
  return {cb.finalize(), changes};
}
int DictionaryFixed::filter(DictionaryFixed::filter_func_t check_leaf) {
  force_validate();
  int skip_rest = -1;
  unsigned char buffer[DictionaryFixed::max_key_bytes];
  auto res = dict_filter(get_root_cell(), td::BitPtr{buffer}, key_bits, check_leaf, skip_rest);
  if (res.second > 0) {
    // std::cerr << "after filter (" << res.second << " changes): new augmented dictionary root is:\n";
    // vm::load_cell_slice(res.first).print_rec(std::cerr);
    set_root_cell(std::move(res.first));
  }
  return res.second;
}
void Dictionary::map(const map_func_t& map_func) {
  force_validate();
  int key_len = get_key_bits();
  unsigned char key_buffer[max_key_bytes];
  auto res = dict_map(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, map_func);
  set_root_cell(std::move(res));
}
void Dictionary::map(const simple_map_func_t& simple_map_func) {
  using namespace std::placeholders;
  map_func_t map_func = std::bind(simple_map_func, _1, _2);
  map(map_func);
}
// mode: +1 = forbid empty dict1 with non-empty dict2
//       +2 = forbid empty dict2 with non-empty dict1
Ref | DictionaryFixed::dict_combine_with(Ref | dict1, Ref | dict2, td::BitPtr key_buffer, int n,
                                             int total_key_len, const DictionaryFixed::combine_func_t& combine_func,
                                             int mode, int skip1, int skip2) const {
  if (dict1.is_null()) {
    assert(!skip2);
    if ((mode & 1) && dict2.is_null()) {
      throw CombineError{};
    }
    return dict2;
  } else if (dict2.is_null()) {
    assert(!skip1);
    if ((mode & 2)) {
      throw CombineError{};
    }
    return dict1;
  }
  // both dictionaries non-empty
  // skip1: remove that much first bits from all keys in dictionary dict1 (its keys are actually n + skip1 bits long)
  // skip2: similar for dict2
  // resulting dictionary will have n-bit keys
  LabelParser label1{dict1, n + skip1, label_mode()}, label2{dict2, n + skip2, label_mode()};
  int l1 = label1.l_bits - skip1, l2 = label2.l_bits - skip2;
  assert(l1 >= 0 && l2 >= 0);
  assert(!skip1 || label1.common_prefix_len(key_buffer - skip1, skip1) == skip1);
  assert(!skip2 || label2.common_prefix_len(key_buffer - skip2, skip2) == skip2);
  label1.extract_label_to(key_buffer - skip1);
  int c = label2.common_prefix_len(key_buffer - skip2, skip2 + l1) - skip2;
  assert(c >= 0 && c <= l1 && c <= l2);
  if (c < l1 && c < l2) {
    // the two dictionaries have disjoint keys
    dict1.clear();
    dict2.clear();
    if ((mode & 3)) {
      throw CombineError{};
    }
    CellBuilder cb;
    append_dict_label(cb, key_buffer + c + 1, l1 - c - 1, n - c - 1);
    if (!cell_builder_add_slice_bool(cb, *label1.remainder)) {
      throw VmError{Excno::cell_ov, "cannot prune label of an old dictionary cell while merging dictionaries"};
    }
    label1.remainder.clear();
    dict1 = cb.finalize();
    // cb.reset(); // included into finalize();
    // now dict1 has been "pruned" -- first skip1+c+1 bits removed from its root egde label
    label2.extract_label_to(key_buffer - skip2);
    append_dict_label(cb, key_buffer + c + 1, l2 - c - 1, n - c - 1);
    if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
    }
    label2.remainder.clear();
    dict2 = cb.finalize();
    // now dict2 has also been pruned
    if (!key_buffer[c]) {
      std::swap(dict1, dict2);
    }
    // put dict1 into the left tree (with smaller labels), dict2 into the right tree
    append_dict_label(cb, key_buffer, c, n);
    return finish_create_fork(cb, std::move(dict1), std::move(dict2), n - c);
  }
  if (c == l1 && c == l2) {
    // funny enough, the non-skipped parts of labels of l1 and l2 match
    dict1.clear();
    dict2.clear();
    label2.skip_label();
    CellBuilder cb;
    append_dict_label(cb, key_buffer, c, n);
    if (c == n) {
      // our two dictionaries are in fact leafs with matching edge labels (keys)
      if (!combine_func(cb, std::move(label1.remainder), std::move(label2.remainder), key_buffer + n - total_key_len,
                        total_key_len)) {
        // alas, the two values did not combine, this key will be absent from resulting dictionary
        return {};
      }
      return cb.finalize();
    }
    assert(c < n);
    key_buffer += c + 1;
    key_buffer[-1] = 0;
    // combine left subtrees
    auto c1 = dict_combine_with(label1.remainder->prefetch_ref(0), label2.remainder->prefetch_ref(0), key_buffer,
                                n - c - 1, total_key_len, combine_func);
    key_buffer[-1] = 1;
    // combine right subtrees
    auto c2 = dict_combine_with(label1.remainder->prefetch_ref(1), label2.remainder->prefetch_ref(1), key_buffer,
                                n - c - 1, total_key_len, combine_func);
    label1.remainder.clear();
    label2.remainder.clear();
    // c1 and c2 are merged left and right children of dict1 and dict2
    if (!c1.is_null() && !c2.is_null()) {
      // both children non-empty, simply put them into the new node
      return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
    }
    if (c1.is_null() && c2.is_null()) {
      return {};  // both children empty, resulting dictionary also empty
    }
    // exactly one of c1 and c2 is non-empty, have to merge labels
    bool sw = c1.is_null();
    key_buffer[-1] = sw;
    if (sw) {
      c1 = std::move(c2);
    }
    LabelParser label3{std::move(c1), n - c - 1, label_mode()};
    label3.extract_label_to(key_buffer);
    key_buffer -= c + 1;
    // store combined label for the new edge
    cb.reset();
    append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
    // store payload
    if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
    }
    return cb.finalize();
  }
  if (c == l1) {
    assert(c < l2);
    dict1.clear();
    if ((mode & 2)) {
      throw CombineError{};
    }
    // children of root node of dict1
    auto c1 = label1.remainder->prefetch_ref(0);
    auto c2 = label1.remainder->prefetch_ref(1);
    label1.remainder.clear();
    // have to merge dict2 with one of the children of dict1
    label2.extract_label_to(key_buffer - skip2);  // dict2 has longer label, extract it
    bool sw = key_buffer[c];
    if (!sw) {
      // merge c1 with dict2
      c1 = dict_combine_with(std::move(c1), std::move(dict2), key_buffer + c + 1, n - c - 1, total_key_len,
                             combine_func, mode, 0, skip2 + c + 1);
    } else {
      // merge c2 with dict2
      c2 = dict_combine_with(std::move(c2), std::move(dict2), key_buffer + c + 1, n - c - 1, total_key_len,
                             combine_func, mode, 0, skip2 + c + 1);
    }
    if (!c1.is_null() && !c2.is_null()) {
      CellBuilder cb;
      append_dict_label(cb, key_buffer, c, n);
      return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
    }
    // one of children is empty, have to merge root edges
    key_buffer[c] = !sw;
    if (!sw) {
      std::swap(c1, c2);
    }
    assert(!c1.is_null() && c2.is_null());
    LabelParser label3{std::move(c1), n - c - 1, label_mode()};
    label3.extract_label_to(key_buffer + c + 1);
    CellBuilder cb;
    append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
    // store payload
    if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
    }
    return cb.finalize();
  } else {
    assert(c == l2 && c < l1);
    dict2.clear();
    if ((mode & 1)) {
      throw CombineError{};
    }
    // children of root node of dict2
    label2.skip_label();  // dict2 had shorter label anyway, label1 is already unpacked
    auto c1 = label2.remainder->prefetch_ref(0);
    auto c2 = label2.remainder->prefetch_ref(1);
    label2.remainder.clear();
    // have to merge dict1 with one of the children of dict2
    bool sw = key_buffer[c];
    if (!sw) {
      // merge dict1 with c1
      c1 = dict_combine_with(std::move(dict1), std::move(c1), key_buffer + c + 1, n - c - 1, total_key_len,
                             combine_func, mode, skip1 + c + 1, 0);
    } else {
      // merge dict1 with c2
      c2 = dict_combine_with(std::move(dict1), std::move(c2), key_buffer + c + 1, n - c - 1, total_key_len,
                             combine_func, mode, skip1 + c + 1, 0);
    }
    if (!c1.is_null() && !c2.is_null()) {
      CellBuilder cb;
      append_dict_label(cb, key_buffer, c, n);
      return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
    }
    // one of children is empty, have to merge root edges
    key_buffer[c] = !sw;
    if (!sw) {
      std::swap(c1, c2);
    }
    assert(!c1.is_null() && c2.is_null());
    LabelParser label3{std::move(c1), n - c - 1, label_mode()};
    label3.extract_label_to(key_buffer + c + 1);
    CellBuilder cb;
    append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
    // store payload
    if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
    }
    return cb.finalize();
  }
}
bool DictionaryFixed::combine_with(DictionaryFixed& dict2, const combine_func_t& combine_func, int mode) {
  force_validate();
  dict2.force_validate();
  int key_len = get_key_bits();
  if (key_len != dict2.get_key_bits()) {
    throw VmError{Excno::dict_err, "cannot combine dictionaries with different key lengths"};
  }
  unsigned char key_buffer[max_key_bytes];
  try {
    auto res = dict_combine_with(get_root_cell(), dict2.get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len,
                                 combine_func, mode);
    set_root_cell(std::move(res));
    return true;
  } catch (CombineError) {
    return false;
  }
}
bool DictionaryFixed::combine_with(DictionaryFixed& dict2, const simple_combine_func_t& simple_combine_func, int mode) {
  using namespace std::placeholders;
  combine_func_t combine_func = std::bind(simple_combine_func, _1, _2, _3);
  return combine_with(dict2, combine_func, mode);
}
bool DictionaryFixed::combine_with(DictionaryFixed& dict2) {
  return combine_with(dict2,
                      [](CellBuilder&, Ref, Ref, td::ConstBitPtr key, int key_len) -> bool {
                        LOG(WARNING) << "dictionary merge conflict for key " << key.to_hex(key_len);
                        throw CombineError{};
                      });
}
bool DictionaryFixed::dict_check_for_each(Ref | dict, td::BitPtr key_buffer, int n, int total_key_len,
                                          const DictionaryFixed::foreach_func_t& foreach_func,
                                          bool invert_first, bool shuffle) const {
  if (dict.is_null()) {
    return true;
  }
  LabelParser label{std::move(dict), n, label_mode()};
  int l = label.l_bits;
  label.extract_label_to(key_buffer);
  if (l == n) {
    // leaf node, value left in label.remainder
    return foreach_func(std::move(label.remainder), key_buffer + n - total_key_len, total_key_len);
  }
  assert(l >= 0 && l < n);
  // a fork with two children, c1 and c2
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  label.remainder.clear();
  key_buffer += l + 1;
  if (l) {
    invert_first = false;
  }
  bool invert = shuffle ? td::Random::fast(0, 1) == 1: invert_first;
  if (invert) {
    std::swap(c1, c2);
  }
  key_buffer[-1] = invert;
  // recursive check_foreach applied to both children
  if (!dict_check_for_each(std::move(c1), key_buffer, n - l - 1, total_key_len, foreach_func, false, shuffle)) {
    return false;
  }
  key_buffer[-1] = !invert;
  return dict_check_for_each(std::move(c2), key_buffer, n - l - 1, total_key_len, foreach_func, false, shuffle);
}
bool DictionaryFixed::check_for_each(const foreach_func_t& foreach_func, bool invert_first, bool shuffle) {
  force_validate();
  if (is_empty()) {
    return true;
  }
  int key_len = get_key_bits();
  unsigned char key_buffer[max_key_bytes];
  return dict_check_for_each(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first,
                             shuffle);
}
static inline bool set_bit(td::BitPtr ptr, bool value = true) {
  *ptr = value;
  return true;
}
// mode: +1 = check augmentation of dict1, +2 = ... of dict2
bool DictionaryFixed::dict_scan_diff(Ref | dict1, Ref | dict2, td::BitPtr key_buffer, int n, int total_key_len,
                                     const scan_diff_func_t& diff_func, int mode, int skip1, int skip2) const {
  // skip1: remove that much first bits from all keys in dictionary dict1 (its keys are actually n + skip1 bits long)
  // skip2: similar for dict2
  // pretending to compare subdictionaries with n-bit keys
  if (dict1.is_null()) {
    if (dict2.is_null()) {
      return true;  // both dictionaries are empty
    }
    assert(!skip2);
    // dict1 empty, dict2 non-empty -> parse label of dict2
    LabelParser label{dict2, n, label_mode()};
    label.extract_label_to(key_buffer);
    if (label.l_bits >= n) {
      assert(label.l_bits == n);
      // leaf in dict2, empty dict1
      auto key = key_buffer + label.l_bits - total_key_len;
      if ((mode & 2) && !check_leaf(label.remainder, key, total_key_len)) {
        throw VmError{Excno::dict_err, "invalid leaf in the second dictionary being compared"};
      }
      return diff_func(key, total_key_len, {}, std::move(label.remainder));
    }
    n -= label.l_bits + 1;
    key_buffer += label.l_bits + 1;
    if ((mode & 2) && !check_fork_raw(label.remainder, n + 1)) {
      throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
    }
    // compare {} with each of children of dict2
    for (unsigned sw = 0; sw < 2; sw++) {
      key_buffer[-1] = (bool)sw;
      if (!dict_scan_diff({}, label.remainder->prefetch_ref(sw), key_buffer, n, total_key_len, diff_func, mode)) {
        return false;
      }
    }
    return true;
  } else if (dict2.is_null()) {
    assert(!skip1);
    // dict2 empty, dict1 non-empty -> parse label of dict1
    LabelParser label{dict1, n, label_mode()};
    label.extract_label_to(key_buffer);
    if (label.l_bits >= n) {
      assert(label.l_bits == n);
      // leaf in dict1, empty dict2
      auto key = key_buffer + label.l_bits - total_key_len;
      if ((mode & 1) && !check_leaf(label.remainder, key, total_key_len)) {
        throw VmError{Excno::dict_err, "invalid leaf in the first dictionary being compared"};
      }
      return diff_func(key, total_key_len, std::move(label.remainder), {});
    }
    n -= label.l_bits + 1;
    key_buffer += label.l_bits + 1;
    if ((mode & 1) && !check_fork_raw(label.remainder, n + 1)) {
      throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
    }
    // compare each of children of dict1 with {}
    for (unsigned sw = 0; sw < 2; sw++) {
      key_buffer[-1] = (bool)sw;
      if (!dict_scan_diff(label.remainder->prefetch_ref(sw), {}, key_buffer, n, total_key_len, diff_func, mode)) {
        return false;
      }
    }
    return true;
  }
  // both dictionaries non-empty
  if (skip1 == skip2 && (dict1 == dict2 || dict1->get_hash() == dict2->get_hash())) {
    // dictionaries match, subtree comparison not necessary
    return true;
  }
  LabelParser label1{dict1, n + skip1, label_mode()}, label2{dict2, n + skip2, label_mode()};
  int l1 = label1.l_bits - skip1, l2 = label2.l_bits - skip2;
  assert(l1 >= 0 && l2 >= 0);
  assert(!skip1 || label1.common_prefix_len(key_buffer - skip1, skip1) == skip1);
  assert(!skip2 || label2.common_prefix_len(key_buffer - skip2, skip2) == skip2);
  label1.extract_label_to(key_buffer - skip1);
  int c = label2.common_prefix_len(key_buffer - skip2, skip2 + l1) - skip2;
  assert(c >= 0 && c <= l1 && c <= l2);
  if (c < l1 && c < l2) {
    // the two dictionaries have disjoint keys
    if (!key_buffer[c]) {
      // all keys of dict1 are before dict2
      return dict_scan_diff(std::move(dict1), {}, key_buffer - skip1, n + skip1, total_key_len, diff_func, mode) &&
             dict_scan_diff({}, std::move(dict2), key_buffer - skip2, n + skip2, total_key_len, diff_func, mode);
    } else {
      // all keys of dict2 are before dict1
      return dict_scan_diff({}, std::move(dict2), key_buffer - skip2, n + skip2, total_key_len, diff_func, mode) &&
             dict_scan_diff(std::move(dict1), {}, key_buffer - skip1, n + skip1, total_key_len, diff_func, mode);
    }
  }
  if (c == l1 && c == l2) {
    // funny enough, the non-skipped parts of labels of l1 and l2 match
    dict1.clear();
    dict2.clear();
    label2.skip_label();
    if (c == n) {
      // our two dictionaries are in fact leafs with matching edge labels (keys)
      auto key = key_buffer + n - total_key_len;
      if ((mode & 1) && !check_leaf(label1.remainder, key, total_key_len)) {
        throw VmError{Excno::dict_err, "invalid leaf in the first dictionary being compared"};
      }
      if ((mode & 2) && !check_leaf(label2.remainder, key, total_key_len)) {
        throw VmError{Excno::dict_err, "invalid leaf in the second dictionary being compared"};
      }
      return label1.remainder->contents_equal(*label2.remainder) ||
             diff_func(key, total_key_len, std::move(label1.remainder), std::move(label2.remainder));
    }
    assert(c < n);
    key_buffer += c + 1;
    n -= c + 1;
    if ((mode & 1) && !check_fork_raw(label1.remainder, n + 1)) {
      throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
    }
    if ((mode & 2) && !check_fork_raw(label2.remainder, n + 1)) {
      throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
    }
    for (unsigned sw = 0; sw <= 1; sw++) {
      key_buffer[-1] = (bool)sw;
      // compare left and then right subtrees
      if (!dict_scan_diff(label1.remainder->prefetch_ref(sw), label2.remainder->prefetch_ref(sw), key_buffer, n,
                          total_key_len, diff_func, mode)) {
        return false;
      }
    }
    return true;
  }
  if (c == l1) {
    assert(c < l2);
    dict1.clear();
    if ((mode & 1) && !check_fork_raw(label1.remainder, n - c)) {
      throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
    }
    // children of root node of dict1
    auto c1 = label1.remainder->prefetch_ref(0);
    auto c2 = label1.remainder->prefetch_ref(1);
    label1.remainder.clear();
    // have to compare dict2 with one of the children of dict1
    label2.extract_label_to(key_buffer - skip2);  // dict2 has longer label, extract it
    key_buffer += c + 1;
    n -= c + 1;
    bool sw = key_buffer[-1];
    key_buffer[-1] = false;
    if (!sw) {
      // compare c1 with dict2, then c2 with {}
      return dict_scan_diff(std::move(c1), std::move(dict2), key_buffer, n, total_key_len, diff_func, mode, 0,
                            skip2 + c + 1) &&
             set_bit(key_buffer - 1) &&
             dict_scan_diff(std::move(c2), {}, key_buffer, n, total_key_len, diff_func, mode);
    } else {
      // compare c1 with {}, then c2 with dict2
      return dict_scan_diff(std::move(c1), {}, key_buffer, n, total_key_len, diff_func, mode) &&
             set_bit(key_buffer - 1) &&
             dict_scan_diff(std::move(c2), std::move(dict2), key_buffer, n, total_key_len, diff_func, mode, 0,
                            skip2 + c + 1);
    }
  } else {
    assert(c == l2 && c < l1);
    dict2.clear();
    label2.skip_label();  // dict2 had shorter label anyway, label1 is already unpacked
    if ((mode & 2) && !check_fork_raw(label2.remainder, n - c)) {
      throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
    }
    // children of root node of dict2
    auto c1 = label2.remainder->prefetch_ref(0);
    auto c2 = label2.remainder->prefetch_ref(1);
    label2.remainder.clear();
    // have to compare dict1 with one of the children of dict2
    key_buffer += c + 1;
    n -= c + 1;
    bool sw = key_buffer[-1];
    key_buffer[-1] = false;
    if (!sw) {
      // compare dict1 with c1, then {} with c2
      return dict_scan_diff(std::move(dict1), std::move(c1), key_buffer, n, total_key_len, diff_func, mode,
                            skip1 + c + 1, 0) &&
             set_bit(key_buffer - 1) &&
             dict_scan_diff({}, std::move(c2), key_buffer, n, total_key_len, diff_func, mode);
    } else {
      // compare {} with c1, then dict1 with c2
      return dict_scan_diff({}, std::move(c1), key_buffer, n, total_key_len, diff_func, mode) &&
             set_bit(key_buffer - 1) &&
             dict_scan_diff(std::move(dict1), std::move(c2), key_buffer, n, total_key_len, diff_func, mode,
                            skip1 + c + 1, 0);
    }
  }
}
bool DictionaryFixed::scan_diff(DictionaryFixed& dict2, const scan_diff_func_t& diff_func, int check_augm) {
  force_validate();
  dict2.force_validate();
  int key_len = get_key_bits();
  if (key_len != dict2.get_key_bits()) {
    throw VmError{Excno::dict_err, "cannot compare dictionaries with different key lengths"};
  }
  unsigned char key_buffer[max_key_bytes];
  try {
    return dict_scan_diff(get_root_cell(), dict2.get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, diff_func,
                          check_augm);
  } catch (CombineError) {
    return false;
  }
}
bool DictionaryFixed::dict_validate_check(Ref | dict, td::BitPtr key_buffer, int n, int total_key_len,
                                          const DictionaryFixed::foreach_func_t& foreach_func,
                                          bool invert_first) const {
  //LOG(DEBUG) << "dict_validate_check for " << total_key_len - n << "-bit key prefix " << (key_buffer - n + total_key_len).to_hex(total_key_len - n);
  if (dict.is_null()) {
    return true;
  }
  LabelParser label{std::move(dict), n, label_mode()};
  int l = label.l_bits;
  label.extract_label_to(key_buffer);
  if (l == n) {
    // leaf node, value left in label.remainder
    vm::CellSlice cs{*label.remainder};
    auto key = key_buffer + n - total_key_len;
    if (!(check_leaf(cs, key, total_key_len) && foreach_func(std::move(label.remainder), key, total_key_len))) {
      LOG(DEBUG) << "invalid dictionary leaf node with " << total_key_len << "-bit key " << key.to_hex(total_key_len);
      return false;
    }
    return true;
  }
  assert(l >= 0 && l < n);
  // a fork with two children, c1 and c2
  auto c1 = label.remainder.write().fetch_ref();
  auto c2 = label.remainder.unique_write().fetch_ref();
  key_buffer += l + 1;
  n -= l + 1;
  if (!check_fork(label.remainder.write(), c1, c2, n + 1)) {
    LOG(DEBUG) << "invalid dictionary fork augmentation for fork node with " << total_key_len - n - 1
               << "-bit key prefix " << (key_buffer + n - total_key_len).to_hex(total_key_len - n - 1);
    return false;
  }
  label.remainder.clear();
  if (l) {
    invert_first = false;
  } else if (invert_first) {
    std::swap(c1, c2);
  }
  key_buffer[-1] = invert_first;
  // recursive check_foreach applied to both children
  if (!dict_validate_check(std::move(c1), key_buffer, n, total_key_len, foreach_func)) {
    return false;
  }
  key_buffer[-1] = !invert_first;
  return dict_validate_check(std::move(c2), key_buffer, n, total_key_len, foreach_func);
}
bool DictionaryFixed::validate_check(const DictionaryFixed::foreach_func_t& foreach_func, bool invert_first) {
  if (!validate()) {
    return false;
  }
  if (is_empty()) {
    return true;
  }
  int key_len = get_key_bits();
  unsigned char key_buffer[max_key_bytes];
  return dict_validate_check(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first);
}
bool DictionaryFixed::validate_all() {
  return validate_check([](Ref value, td::ConstBitPtr key, int n) { return true; }) || invalidate();
}
/*
 * 
 *   PREFIX DICTIONARIES
 * 
 */
std::pair [, int> PrefixDictionary::lookup_prefix(td::ConstBitPtr key, int key_len) {
  force_validate();
  int n = get_key_bits();
  if (is_empty()) {
    return std::make_pair(Ref{}, 0);
  }
  //std::cerr << "dictionary lookup for key = " << key.to_hex(key_len) << std::endl;
  Ref]| cell = get_root_cell();
  int m = key_len;
  while (true) {
    LabelParser label{std::move(cell), n, 1};
    int l = label.common_prefix_len(key, m);
    if (l < label.l_bits) {
      //std::cerr << "(not a prefix)\n";
      return std::make_pair(Ref{}, key_len - m + l);
    }
    n -= label.l_bits;
    m -= label.l_bits;
    assert(m >= 0);
    label.skip_label();
    Ref cs = std::move(label.remainder);
    if (!cs->have(1)) {
      throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
    }
    if (!cs.unique_write().fetch_ulong(1)) {
      return std::make_pair(std::move(cs), key_len - m);
    }
    if (!n) {
      throw VmError{Excno::dict_err, "a fork node in a prefix code dictionary with zero remaining key length"};
    }
    if (cs->size() != 0 || cs->size_refs() != 2) {
      throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
    }
    if (!m) {
      return std::make_pair(Ref{}, key_len);
    }
    key += label.l_bits;
    bool sw = *key++;
    //std::cerr << "key bit at position " << key_bits - n << " equals " << sw << std::endl;
    --n;
    --m;
    cell = cs->prefetch_ref(sw);
  }
}
Ref PrefixDictionary::lookup(td::ConstBitPtr key, int key_len) {
  force_validate();
  if (key_len > get_key_bits()) {
    return {};
  }
  auto res = lookup_prefix(key, key_len);
  return res.second == key_len ? std::move(res.first) : Ref{};
}
bool PrefixDictionary::set_gen(td::ConstBitPtr key, int key_len, const std::function& store_val,
                               SetMode mode) {
  force_validate();
  if (key_len > get_key_bits() || key_len < 0) {
    return false;
  }
  auto res = pfx_dict_set(get_root_cell(), key, key_len, get_key_bits(), store_val, mode);
  if (res.second) {
    set_root_cell(std::move(res.first));
  }
  return res.second;
}
bool PrefixDictionary::set(td::ConstBitPtr key, int key_len, Ref value, SetMode mode) {
  return set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); }, mode);
}
bool PrefixDictionary::set_builder(td::ConstBitPtr key, int key_len, Ref val_b, SetMode mode) {
  return set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
Ref PrefixDictionary::lookup_delete(td::ConstBitPtr key, int key_len) {
  force_validate();
  if (key_len > get_key_bits() || key_len < 0) {
    return {};
  }
  auto res = pfx_dict_lookup_delete(get_root_cell(), key, key_len, get_key_bits());
  if (res.first.not_null()) {
    set_root_cell(std::move(res.second));
  }
  return std::move(res.first);
}
/*
 * 
 *   AUGMENTED DICTIONARIES
 * 
 */
namespace dict {
bool AugmentationData::check_empty(vm::CellSlice& cs) const {
  vm::CellBuilder cb;
  return eval_empty(cb) && cb.contents_equal(cs);
}
bool AugmentationData::check_leaf(vm::CellSlice& cs, vm::CellSlice& val_cs) const {
  vm::CellBuilder cb;
  return eval_leaf(cb, val_cs) && cb.contents_equal(cs);
}
bool AugmentationData::check_fork(vm::CellSlice& cs, vm::CellSlice& left_cs, vm::CellSlice& right_cs) const {
  vm::CellBuilder cb;
  return eval_fork(cb, left_cs, right_cs) && cb.contents_equal(cs);
}
Ref AugmentationData::extract_extra(vm::CellSlice& cs) const {
  Ref res{true, cs};
  return skip_extra(cs) && res.write().cut_tail(cs) ? std::move(res) : Ref{};
}
Ref AugmentationData::extract_extra(Ref cs_ref) const {
  CellSlice cs{*cs_ref};
  return skip_extra(cs) && cs_ref.write().cut_tail(cs) ? std::move(cs_ref) : Ref{};
}
bool AugmentationData::extract_extra_to(vm::CellSlice& cs, vm::CellSlice& extra) const {
  extra = cs;
  return cs.is_valid() && skip_extra(cs) && extra.cut_tail(cs);
}
}  // namespace dict
using dict::AugmentationData;
using dict::LabelParser;
AugmentedDictionary::AugmentedDictionary(int _n, const AugmentationData& _aug, bool validate)
    : DictionaryFixed(_n, false), aug(_aug) {
  if (validate) {
    force_validate();
  }
}
AugmentedDictionary::AugmentedDictionary(Ref _root, int _n, const AugmentationData& _aug, bool validate)
    : DictionaryFixed(std::move(_root), _n, false), aug(_aug) {
  if (validate) {
    force_validate();
  }
}
AugmentedDictionary::AugmentedDictionary(Ref | cell, int _n, const AugmentationData& _aug, bool validate)
    : DictionaryFixed(std::move(cell), _n, false), aug(_aug) {
  if (validate) {
    force_validate();
  }
}
AugmentedDictionary::AugmentedDictionary(DictNonEmpty, Ref _root, int _n, const AugmentationData& _aug,
                                         bool validate)
    : DictionaryFixed(DictNonEmpty{}, std::move(_root), _n, false), aug(_aug) {
  if (validate) {
    force_validate();
  }
}
bool AugmentedDictionary::validate() {
  if (is_valid()) {
    return true;
  }
  if (flags & f_invalid) {
    return false;
  }
  if (key_bits < 0 || key_bits > max_key_bits) {
    return invalidate();
  }
  if (flags & f_root_cached) {
    if (root.is_null() || !root->size()) {
      return invalidate();
    }
    bool non_empty = root->prefetch_ulong(1);
    if (non_empty && !root->size_refs()) {
      return invalidate();
    }
    if (root_cell.not_null()) {
      return invalidate();
    }
    vm::CellSlice cs{*root};
    if (!cs.advance(1)) {
      return invalidate();
    }
    if (non_empty) {
      root_cell = cs.fetch_ref();
      auto root_extra = get_root_extra();
      if (!(root_extra.not_null() && root_extra->contents_equal(cs))) {
        return invalidate();
      }
    } else {
      if (!aug.check_empty(cs)) {
        return invalidate();
      }
    }
  } else if (root.not_null()) {
    return invalidate();
  }
  flags |= f_valid;
  return true;
}
Ref AugmentedDictionary::get_root() const {
  if (!(flags & f_root_cached) && !compute_root()) {
    return {};
  }
  return root;
}
Ref AugmentedDictionary::extract_root() && {
  if (!(flags & f_root_cached) && !compute_root()) {
    return {};
  }
  flags = f_invalid;
  return std::move(root);
}
bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) const & {
  if (!is_valid()) {
    return false;
  }
  if (root_cell.is_null()) {
    return cb.store_zeroes_bool(1) && aug.eval_empty(cb);
  } else {
    return cb.store_ones_bool(1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra());
  }
}
bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) && {
  if (!is_valid()) {
    return false;
  }
  flags = f_invalid;
  if (root_cell.is_null()) {
    return cb.store_zeroes_bool(1) && aug.eval_empty(cb);
  } else {
    return cb.store_ones_bool(1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra());
  }
}
bool AugmentedDictionary::compute_root() const {
  if (!is_valid()) {
    return false;
  }
  if (root_cell.is_null()) {
    root = get_empty_dictionary();
    flags |= f_root_cached;
    return true;
  }
  CellBuilder cb;
  if (cb.store_long_bool(1, 1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra())) {
    root = Ref{true, cb.finalize()};
    flags |= f_root_cached;
    return true;
  } else {
    return false;
  }
}
Ref AugmentedDictionary::get_empty_dictionary() const {
  CellBuilder cb;
  cb.store_long(0, 1);
  return aug.eval_empty(cb) ? Ref{true, cb.finalize()} : Ref{};
}
Ref AugmentedDictionary::get_node_extra(Ref | cell_ref, int n) const {
  if (cell_ref.is_null()) {
    CellBuilder cb;
    if (!aug.eval_empty(cb)) {
      return {};
    }
    return Ref{true, cb.finalize()};
  }
  LabelParser label{std::move(cell_ref), n, 2};
  label.skip_label();
  if (label.l_bits == n) {
    return aug.extract_extra(std::move(label.remainder));
  } else if (label.remainder.write().advance_refs(2)) {
    vm::CellSlice cs{*label.remainder};
    if (aug.skip_extra(cs) && cs.empty_ext()) {
      return std::move(label.remainder);
    }
  }
  return {};
}
Ref AugmentedDictionary::extract_leaf_value(Ref leaf) const {
  if (leaf.not_null() && aug.skip_extra(leaf.write())) {
    return std::move(leaf);
  } else {
    return Ref{};
  }
}
Ref AugmentedDictionary::get_root_extra() const {
  return get_node_extra(root_cell, key_bits);
}
Ref AugmentedDictionary::extract_value(Ref value_extra) const {
  if (value_extra.not_null() && aug.skip_extra(value_extra.write())) {
    return value_extra;
  } else {
    return {};
  }
}
Ref | AugmentedDictionary::extract_value_ref(Ref value_extra) const {
  if (value_extra.not_null() && aug.skip_extra(value_extra.write()) && value_extra->size_ext() == 0x10000) {
    return value_extra->prefetch_ref();
  } else {
    return {};
  }
}
std::pair [, Ref> AugmentedDictionary::decompose_value_extra(Ref value_extra) const {
  if (value_extra.is_null()) {
    return {};
  }
  auto extra = aug.extract_extra(value_extra.write());
  if (extra.is_null()) {
    return {};
  } else {
    return {std::move(value_extra), std::move(extra)};
  }
}
std::pair][, Ref> AugmentedDictionary::decompose_value_ref_extra(Ref value_extra) const {
  if (value_extra.is_null()) {
    return {};
  }
  auto extra = aug.extract_extra(value_extra.write());
  if (extra.is_null() || value_extra->size_ext() != 0x10000) {
    return {};
  } else {
    return {value_extra->prefetch_ref(), std::move(extra)};
  }
}
Ref AugmentedDictionary::lookup_with_extra(td::ConstBitPtr key, int key_len) {
  return DictionaryFixed::lookup(key, key_len);
}
Ref AugmentedDictionary::lookup(td::ConstBitPtr key, int key_len) {
  return extract_value(lookup_with_extra(key, key_len));
}
Ref]| AugmentedDictionary::lookup_ref(td::ConstBitPtr key, int key_len) {
  return extract_value_ref(lookup_with_extra(key, key_len));
}
std::pair [, Ref> AugmentedDictionary::lookup_extra(td::ConstBitPtr key, int key_len) {
  return decompose_value_extra(lookup_with_extra(key, key_len));
}
std::pair][, Ref> AugmentedDictionary::lookup_ref_extra(td::ConstBitPtr key, int key_len) {
  return decompose_value_ref_extra(lookup_with_extra(key, key_len));
}
Ref AugmentedDictionary::lookup_delete_with_extra(td::ConstBitPtr key, int key_len) {
  return DictionaryFixed::lookup_delete(key, key_len);
}
Ref AugmentedDictionary::lookup_delete(td::ConstBitPtr key, int key_len) {
  return extract_value(lookup_delete_with_extra(key, key_len));
}
Ref]| AugmentedDictionary::lookup_delete_ref(td::ConstBitPtr key, int key_len) {
  return extract_value_ref(lookup_delete_with_extra(key, key_len));
}
std::pair [, Ref> AugmentedDictionary::lookup_delete_extra(td::ConstBitPtr key, int key_len) {
  return decompose_value_extra(lookup_delete_with_extra(key, key_len));
}
bool AugmentedDictionary::check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const {
  vm::CellSlice extra;
  return aug.extract_extra_to(cs, extra) && aug.check_leaf_key_extra(cs, extra, key, key_len);
}
bool AugmentedDictionary::check_fork(CellSlice& cs, Ref]| c1, Ref | c2, int n) const {
  if (n <= 0) {
    return false;
  }
  auto extra1 = get_node_extra(std::move(c1), n - 1);
  auto extra2 = get_node_extra(std::move(c2), n - 1);
  return extra1.not_null() && extra2.not_null() && aug.check_fork(cs, extra1.write(), extra2.write());
}
Ref | AugmentedDictionary::finish_create_leaf(CellBuilder& cb, const CellSlice& value) const {
  CellSlice value_copy{value};
  if (!aug.eval_leaf(cb, value_copy)) {
    throw VmError{Excno::dict_err, "cannot compute and store extra value into an augmented dictionary cell"};
  }
  if (!cb.append_cellslice_bool(value)) {
    throw VmError{Excno::dict_err, "cannot store new value into an augmented dictionary cell"};
  }
  return cb.finalize();
}
Ref | AugmentedDictionary::finish_create_fork(CellBuilder& cb, Ref | c1, Ref | c2, int n) const {
  assert(n > 0);
  if (!(cb.store_ref_bool(c1) && cb.store_ref_bool(c2))) {
    throw VmError{Excno::dict_err, "cannot store branch references into an augmented dictionary cell"};
  }
  auto extra1 = get_node_extra(std::move(c1), n - 1);
  auto extra2 = get_node_extra(std::move(c2), n - 1);
  if (extra1.is_null()) {
    throw VmError{Excno::dict_err, "cannot extract extra value from left branch of an augmented dictionary fork node"};
  }
  if (extra2.is_null()) {
    throw VmError{Excno::dict_err, "cannot extract extra value from left branch of an augmented dictionary fork node"};
  }
  if (!aug.eval_fork(cb, extra1.write(), extra2.write())) {
    throw VmError{Excno::dict_err, "cannot compute extra value for an augmented dictionary fork node"};
  }
  return cb.finalize();
}
std::pair [, bool> AugmentedDictionary::dict_set(Ref]| dict, td::ConstBitPtr key, int n,
                                                         const CellSlice& value, Dictionary::SetMode mode) const {
  //std::cerr << "augmented dictionary modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
  if (dict.is_null()) {
    // the dictionary is very empty
    if (mode == Dictionary::SetMode::Replace) {
      return std::make_pair [, bool>({}, false);
    }
    // create an one-element dictionary
    CellBuilder cb;
    append_dict_label(cb, key, n, n);
    return std::make_pair(finish_create_leaf(cb, value), true);
  }
  LabelParser label{std::move(dict), n, 2};
  label.validate();
  int pfx_len = label.common_prefix_len(key, n);
  assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
  if (pfx_len < label.l_bits) {
    // have to insert a new node (fork) inside the current edge
    if (mode == Dictionary::SetMode::Replace) {
      // key not found, return unchanged dictionary
      return std::make_pair(Ref]| {}, false);
    }
    // first, create the edge + new leaf cell
    int m = n - pfx_len - 1;
    CellBuilder cb;
    append_dict_label(cb, key + (pfx_len + 1), m, m);
    Ref | c1 = finish_create_leaf(cb, value);  // new leaf cell corresponding to `key`
    //cb.reset();
    // create the lower portion of the old edge
    int t = label.l_bits - pfx_len - 1;
    auto cs = std::move(label.remainder);
    if (label.l_same) {
      append_dict_label_same(cb, label.l_same & 1, t, m);
    } else {
      cs.write().advance(pfx_len + 1);
      append_dict_label(cb, cs->data_bits(), t, m);
      cs.unique_write().advance(t);
    }
    // now cs is the old payload of the edge, either a value or two subdictionary references
    if (!cell_builder_add_slice_bool(cb, *cs)) {
      throw VmError{Excno::cell_ov, "cannot change label of an old augmented dictionary cell (?)"};
    }
    Ref | c2 = cb.finalize();  // the other child of the new fork
    // cb.reset();
    append_dict_label(cb, key, pfx_len, n);
    bool sw_bit = key[pfx_len];
    if (sw_bit) {
      c1.swap(c2);
    }
    return std::make_pair(finish_create_fork(cb, std::move(c1), std::move(c2), n - pfx_len), true);
  }
  if (label.l_bits == n) {
    // the edge leads to a leaf node
    // this leaf node already contains a value for the key wanted
    if (mode == Dictionary::SetMode::Add) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    // replace the value of the only element of the dictionary
    CellBuilder cb;
    append_dict_label(cb, key, n, n);
    return std::make_pair(finish_create_leaf(cb, value), true);
  }
  // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
  auto c1 = label.remainder->prefetch_ref(0);
  auto c2 = label.remainder->prefetch_ref(1);
  label.remainder.clear();
  if (key[label.l_bits]) {
    // insert key into the right child (c2)
    auto res = dict_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, value, mode);
    if (!res.second) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    c2 = std::move(res.first);
  } else {
    // insert key into the left child (c1)
    auto res = dict_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, value, mode);
    if (!res.second) {
      // return unchanged dictionary
      return std::make_pair(Ref | {}, false);
    }
    c1 = std::move(res.first);
  }
  // create a new label with the same content
  CellBuilder cb;
  append_dict_label(cb, key, label.l_bits, n);
  return std::make_pair(finish_create_fork(cb, std::move(c1), std::move(c2), n - label.l_bits), true);
}
bool AugmentedDictionary::set(td::ConstBitPtr key, int key_len, Ref value, SetMode mode) {
  return value.not_null() && set(key, key_len, *value, mode);
}
bool AugmentedDictionary::set(td::ConstBitPtr key, int key_len, const CellSlice& value, SetMode mode) {
  force_validate();
  if (key_len != get_key_bits()) {
    return false;
  }
  auto res = dict_set(get_root_cell(), key, key_len, value, mode);
  if (res.second) {
    //vm::CellSlice cs{vm::NoVmOrd(), res.first};
    //std::cerr << "new augmented dictionary root is:\n";
    //cs.print_rec(std::cerr);
    set_root_cell(std::move(res.first));
  }
  return res.second;
}
bool AugmentedDictionary::set_ref(td::ConstBitPtr key, int key_len, Ref | value_ref, SetMode mode) {
  if (value_ref.not_null()) {
    CellBuilder cb;
    cb.store_ref(std::move(value_ref));
    return set(key, key_len, load_cell_slice(cb.finalize()));
  } else {
    return false;
  }
}
bool AugmentedDictionary::set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& value, SetMode mode) {
  return set(key, key_len, load_cell_slice(value.finalize_copy()));
}
bool AugmentedDictionary::check_for_each_extra(const foreach_extra_func_t& foreach_extra_func, bool invert_first) {
  force_validate();
  const auto& augm = aug;
  foreach_func_t foreach_func = [&foreach_extra_func, &augm](Ref value_extra, td::ConstBitPtr key,
                                                             int key_len) {
    auto extra = augm.extract_extra(value_extra.write());
    return extra.not_null() && foreach_extra_func(std::move(value_extra), std::move(extra), key, key_len);
  };
  return DictionaryFixed::check_for_each(foreach_func, invert_first);
}
std::pair [, Ref> AugmentedDictionary::dict_traverse_extra(
    Ref]| dict, td::BitPtr key_buffer, int n, const traverse_func_t& traverse_node) const {
  int m = get_key_bits();
  while (true) {
    CHECK(dict.not_null());
    LabelParser label{std::move(dict), n, 2};
    label.extract_label_to(key_buffer);
    key_buffer += label.l_bits;
    n -= label.l_bits;
    if (n <= 0) {
      // reached a leaf, check it
      assert(!n);
      auto pair = decompose_value_extra(std::move(label.remainder));
      if (pair.first.is_null()) {
        throw VmError{Excno::dict_err, "invalid leaf value/extra in an augmented dictionary"};
      }
      int r = traverse_node(key_buffer - m, m, pair.second /* extra */, pair.first /* value */);
      if (r < 0) {
        throw CombineErrorValue{r};
      } else if (r > 0) {
        return pair;
      } else {
        return {};
      }
    }
    // visit (traverse) fork
    auto c1 = label.remainder.write().fetch_ref(), c2 = label.remainder.write().fetch_ref();
    int r = traverse_node(key_buffer + n - m, m - n, std::move(label.remainder) /* extra */, {});
    if (r < 0 || (r & 3) == 3) {
      throw CombineErrorValue{r};
    } else if (!(r & 3)) {
      return {};
    }
    // r = 1 : visit only left, 2 = visit only right, 5 = visit right, then left, 6 = visit left, then right
    ++key_buffer;
    --n;
    bool sw = r & 1;
    if (sw) {
      std::swap(c1, c2);
    }
    if (r & 4) {
      // have to visit both children in some order; do a recursive call to visit the first child
      key_buffer[-1] = sw;
      auto tmp = dict_traverse_extra(std::move(c1), key_buffer, n, traverse_node);
      if (tmp.first.not_null()) {
        return tmp;
      }
    }
    // visit the remaining child
    key_buffer[-1] = !sw;
    dict = std::move(c2);
  }
}
std::pair [, Ref> AugmentedDictionary::traverse_extra(td::BitPtr key_buffer, int key_len,
                                                                              const traverse_func_t& traverse_node) {
  force_validate();
  if (key_len != get_key_bits() || is_empty()) {
    return {};
  }
  return dict_traverse_extra(get_root_cell(), key_buffer, key_len, traverse_node);
}
bool AugmentedDictionary::validate_check_extra(const AugmentedDictionary::foreach_extra_func_t& foreach_extra_func,
                                               bool invert_first) {
  const AugmentationData& augm = aug;
  int key_len = get_key_bits();
  return validate_check(
      [&foreach_extra_func, &augm, key_len](Ref value_extra, td::ConstBitPtr key, int value) {
        auto extra = augm.extract_extra(value_extra.write());
        return extra.not_null() && foreach_extra_func(std::move(value_extra), std::move(extra), key, key_len);
      },
      invert_first);
}
}  // namespace vm
] |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |