--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include <iostream>
+
+#include "include/byteorder.h"
+
+#include "crimson/common/layout.h"
+
+namespace crimson::common {
+
+/**
+ * FixedKVNodeLayout
+ *
+ * Reusable implementation of a fixed size block mapping
+ * K -> V with internal representations KINT and VINT.
+ *
+ * Uses absl::container_internal::Layout for the actual memory layout.
+ *
+ * The primary interface exposed is centered on the fixed_node_iter_t
+ * and related methods.
+ *
+ * Also included are helpers for doing splits and merges as for a btree.
+ */
+template <
+ size_t CAPACITY,
+ typename K,
+ typename KINT,
+ typename V,
+ typename VINT>
+class FixedKVNodeLayout {
+ char *buf = nullptr;
+
+ using L = absl::container_internal::Layout<ceph_le32, KINT, VINT>;
+ static constexpr L layout{1, CAPACITY, CAPACITY};
+
+public:
+ struct fixed_node_iter_t {
+ friend class FixedKVNodeLayout;
+ FixedKVNodeLayout *node;
+ uint16_t offset;
+
+ fixed_node_iter_t(
+ FixedKVNodeLayout *parent,
+ uint16_t offset) : node(parent), offset(offset) {}
+
+ fixed_node_iter_t(const fixed_node_iter_t &) = default;
+ fixed_node_iter_t(fixed_node_iter_t &&) = default;
+ fixed_node_iter_t &operator=(const fixed_node_iter_t &) = default;
+ fixed_node_iter_t &operator=(fixed_node_iter_t &&) = default;
+
+ // Work nicely with for loops without requiring a nested type.
+ fixed_node_iter_t &operator*() { return *this; }
+ fixed_node_iter_t *operator->() { return this; }
+
+ fixed_node_iter_t operator++(int) {
+ auto ret = *this;
+ ++offset;
+ return ret;
+ }
+
+ fixed_node_iter_t &operator++() {
+ ++offset;
+ return *this;
+ }
+
+ uint16_t operator-(const fixed_node_iter_t &rhs) const {
+ assert(rhs.node == node);
+ return offset - rhs.offset;
+ }
+
+ fixed_node_iter_t operator+(uint16_t off) const {
+ return fixed_node_iter_t(
+ node,
+ offset + off);
+ }
+ fixed_node_iter_t operator-(uint16_t off) const {
+ return fixed_node_iter_t(
+ node,
+ offset - off);
+ }
+
+ bool operator==(const fixed_node_iter_t &rhs) const {
+ assert(node == rhs.node);
+ return rhs.offset == offset;
+ }
+
+ bool operator!=(const fixed_node_iter_t &rhs) const {
+ return !(*this == rhs);
+ }
+
+ K get_key() const {
+ return K(node->get_key_ptr()[offset]);
+ }
+
+ void set_key(K _lb) {
+ KINT lb;
+ lb = _lb;
+ node->get_key_ptr()[offset] = lb;
+ }
+
+ K get_next_key_or_max() const {
+ auto next = *this + 1;
+ if (next == node->end())
+ return std::numeric_limits<K>::max();
+ else
+ return next->get_key();
+ }
+
+ V get_val() const {
+ return V(node->get_val_ptr()[offset]);
+ };
+
+ void set_val(V val) {
+ node->get_val_ptr()[offset] = VINT(val);
+ }
+
+ bool contains(K addr) {
+ return (get_key() <= addr) && (get_next_key_or_max() > addr);
+ }
+
+ uint16_t get_offset() const {
+ return offset;
+ }
+
+ private:
+ char *get_key_ptr() {
+ return reinterpret_cast<char *>(node->get_key_ptr() + offset);
+ }
+
+ char *get_val_ptr() {
+ return reinterpret_cast<char *>(node->get_val_ptr() + offset);
+ }
+ };
+
+public:
+ FixedKVNodeLayout(char *buf) :
+ buf(buf) {}
+
+ fixed_node_iter_t begin() {
+ return fixed_node_iter_t(
+ this,
+ 0);
+ }
+
+ fixed_node_iter_t end() {
+ return fixed_node_iter_t(
+ this,
+ get_size());
+ }
+
+ fixed_node_iter_t iter_idx(uint16_t off) {
+ return fixed_node_iter_t(
+ this,
+ off);
+ }
+
+ fixed_node_iter_t find(K l) {
+ auto ret = begin();
+ for (; ret != end(); ++ret) {
+ if (ret->get_key() == l)
+ break;
+ }
+ return ret;
+ }
+
+ fixed_node_iter_t get_split_pivot() {
+ return iter_idx(get_size() / 2);
+ }
+
+private:
+ KINT *get_key_ptr() {
+ return layout.template Pointer<1>(buf);
+ }
+
+ VINT *get_val_ptr() {
+ return layout.template Pointer<2>(buf);
+ }
+
+public:
+ uint16_t get_size() const {
+ return *layout.template Pointer<0>(buf);
+ }
+
+ void set_size(uint16_t size) {
+ *layout.template Pointer<0>(buf) = size;
+ }
+
+ constexpr static size_t get_capacity() {
+ return CAPACITY;
+ }
+
+ /**
+ * copy_from_foreign
+ *
+ * Copies entries from [from_src, to_src) to tgt.
+ *
+ * tgt and from_src must be from different nodes.
+ * from_src and to_src must be from the same node.
+ */
+ static void copy_from_foreign(
+ fixed_node_iter_t tgt,
+ fixed_node_iter_t from_src,
+ fixed_node_iter_t to_src) {
+ assert(tgt->node != from_src->node);
+ assert(to_src->node == from_src->node);
+ memcpy(
+ tgt->get_val_ptr(), from_src->get_val_ptr(),
+ to_src->get_val_ptr() - from_src->get_val_ptr());
+ memcpy(
+ tgt->get_key_ptr(), from_src->get_key_ptr(),
+ to_src->get_key_ptr() - from_src->get_key_ptr());
+ }
+
+ /**
+ * copy_from_local
+ *
+ * Copies entries from [from_src, to_src) to tgt.
+ *
+ * tgt, from_src, and to_src must be from the same node.
+ */
+ static void copy_from_local(
+ fixed_node_iter_t tgt,
+ fixed_node_iter_t from_src,
+ fixed_node_iter_t to_src) {
+ assert(tgt->node == from_src->node);
+ assert(to_src->node == from_src->node);
+ memmove(
+ tgt->get_val_ptr(), from_src->get_val_ptr(),
+ to_src->get_val_ptr() - from_src->get_val_ptr());
+ memmove(
+ tgt->get_key_ptr(), from_src->get_key_ptr(),
+ to_src->get_key_ptr() - from_src->get_key_ptr());
+ }
+
+ /**
+ * split_into
+ *
+ * Takes *this and splits its contents into left and right.
+ */
+ K split_into(
+ FixedKVNodeLayout &left,
+ FixedKVNodeLayout &right) {
+ auto piviter = get_split_pivot();
+
+ left.copy_from_foreign(left.begin(), begin(), piviter);
+ left.set_size(piviter - begin());
+
+ right.copy_from_foreign(right.begin(), piviter, end());
+ right.set_size(end() - piviter);
+
+ return piviter->get_key();
+ }
+
+ /**
+ * merge_from
+ *
+ * Takes two nodes and copies their contents into *this.
+ *
+ * precondition: left.size() + right.size() < CAPACITY
+ */
+ void merge_from(
+ FixedKVNodeLayout &left,
+ FixedKVNodeLayout &right)
+ {
+ copy_from_foreign(
+ end(),
+ left.begin(),
+ left.end());
+ set_size(left.get_size());
+ copy_from_foreign(
+ end(),
+ right.begin(),
+ right.end());
+ set_size(left.get_size() + right.get_size());
+ }
+
+ /**
+ * balance_into_new_nodes
+ *
+ * Takes the contents of left and right and copies them into
+ * replacement_left and replacement_right such that in the
+ * event that the number of elements is odd the extra goes to
+ * the left side iff prefer_left.
+ */
+ static K balance_into_new_nodes(
+ FixedKVNodeLayout &left,
+ FixedKVNodeLayout &right,
+ bool prefer_left,
+ FixedKVNodeLayout &replacement_left,
+ FixedKVNodeLayout &replacement_right)
+ {
+ auto total = left.get_size() + right.get_size();
+ auto pivot_idx = (left.get_size() + right.get_size()) / 2;
+ if (total % 2 && prefer_left) {
+ pivot_idx++;
+ }
+ auto replacement_pivot = pivot_idx > left.get_size() ?
+ right.iter_idx(pivot_idx - left.get_size())->get_key() :
+ left.iter_idx(pivot_idx)->get_key();
+
+ if (pivot_idx < left.get_size()) {
+ replacement_left.copy_from_foreign(
+ replacement_left.end(),
+ left.begin(),
+ left.iter_idx(pivot_idx));
+ replacement_left.set_size(pivot_idx);
+
+ replacement_right.copy_from_foreign(
+ replacement_right.end(),
+ left.iter_idx(pivot_idx),
+ left.end());
+
+ replacement_right.set_size(left.get_size() - pivot_idx);
+ replacement_right.copy_from_foreign(
+ replacement_right.end(),
+ right.begin(),
+ right.end());
+ replacement_right.set_size(total - pivot_idx);
+ } else {
+ replacement_left.copy_from_foreign(
+ replacement_left.end(),
+ left.begin(),
+ left.end());
+ replacement_left.set_size(left.get_size());
+
+ replacement_left.copy_from_foreign(
+ replacement_left.end(),
+ right.begin(),
+ right.iter_idx(pivot_idx - left.get_size()));
+ replacement_left.set_size(pivot_idx);
+
+ replacement_right.copy_from_foreign(
+ replacement_right.end(),
+ right.iter_idx(pivot_idx - left.get_size()),
+ right.end());
+ replacement_right.set_size(total - pivot_idx);
+ }
+
+ return replacement_pivot;
+ }
+};
+
+}
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <stdio.h>
+#include "gtest/gtest.h"
+
+#include "crimson/common/fixed_kv_node_layout.h"
+
+using namespace crimson;
+using namespace crimson::common;
+
+struct test_val_t {
+ uint32_t t1 = 0;
+ int32_t t2 = 0;
+
+ bool operator==(const test_val_t &rhs) const {
+ return rhs.t1 == t1 && rhs.t2 == t2;
+ }
+};
+
+struct test_val_le_t {
+ ceph_le32 t1 = init_le32(0);
+ ceph_les32 t2 = init_les32(0);
+
+ test_val_le_t() = default;
+ test_val_le_t(const test_val_le_t &) = default;
+ test_val_le_t(const test_val_t &nv)
+ : t1(init_le32(nv.t1)), t2(init_les32(nv.t2)) {}
+
+ operator test_val_t() {
+ return test_val_t{t1, t2};
+ }
+};
+
+constexpr size_t CAPACITY = 341;
+
+struct TestNode : FixedKVNodeLayout<
+ CAPACITY,
+ uint32_t, ceph_le32,
+ test_val_t, test_val_le_t> {
+ char buf[4096];
+ TestNode() : FixedKVNodeLayout(buf) {
+ memset(buf, 0, sizeof(buf));
+ }
+};
+
+TEST(FixedKVNodeTest, basic) {
+ auto node = TestNode();
+ ASSERT_EQ(node.get_size(), 0);
+ node.set_size(1);
+ ASSERT_EQ(node.get_size(), 1);
+
+ auto iter = node.begin();
+ iter.set_key(1);
+ ASSERT_EQ(iter.get_key(), 1);
+
+ auto val = test_val_t{ 1, 1 };
+ iter.set_val(val);
+ ASSERT_EQ(val, iter.get_val());
+
+ ASSERT_EQ(std::numeric_limits<uint32_t>::max(), iter.get_next_key_or_max());
+}
+
+TEST(FixedKVNodeTest, at_capacity) {
+ auto node = TestNode();
+ ASSERT_EQ(CAPACITY, node.get_capacity());
+
+ ASSERT_EQ(node.get_size(), 0);
+ node.set_size(node.get_capacity());
+ ASSERT_EQ(node.get_size(), CAPACITY);
+
+ unsigned short num = 0;
+ for (auto &i : node) {
+ i.set_key(num);
+ i.set_val({ num, num});
+ ++num;
+ }
+
+ num = 0;
+ for (auto &i : node) {
+ ASSERT_EQ(i.get_key(), num);
+ ASSERT_EQ(i.get_val(), (test_val_t{num, num}));
+ if (num < (CAPACITY - 1)) {
+ ASSERT_EQ(i.get_next_key_or_max(), num + 1);
+ } else {
+ ASSERT_EQ(std::numeric_limits<uint32_t>::max(), i.get_next_key_or_max());
+ }
+ ++num;
+ }
+}
+
+TEST(FixedKVNodeTest, split) {
+ auto node = TestNode();
+
+ ASSERT_EQ(node.get_size(), 0);
+
+ node.set_size(CAPACITY);
+
+ unsigned short num = 0;
+ for (auto &i : node) {
+ i.set_key(num);
+ i.set_val({ num, num});
+ ++num;
+ }
+
+ auto split_left = TestNode();
+ auto split_right = TestNode();
+ node.split_into(split_left, split_right);
+
+ ASSERT_EQ(split_left.get_size() + split_right.get_size(), CAPACITY);
+ num = 0;
+ for (auto &i : split_left) {
+ ASSERT_EQ(i.get_key(), num);
+ ASSERT_EQ(i.get_val(), (test_val_t{num, num}));
+ if (num < split_left.get_size() - 1) {
+ ASSERT_EQ(i.get_next_key_or_max(), num + 1);
+ } else {
+ ASSERT_EQ(std::numeric_limits<uint32_t>::max(), i.get_next_key_or_max());
+ }
+ ++num;
+ }
+ for (auto &i : split_right) {
+ ASSERT_EQ(i.get_key(), num);
+ ASSERT_EQ(i.get_val(), (test_val_t{num, num}));
+ if (num < CAPACITY - 1) {
+ ASSERT_EQ(i.get_next_key_or_max(), num + 1);
+ } else {
+ ASSERT_EQ(std::numeric_limits<uint32_t>::max(), i.get_next_key_or_max());
+ }
+ ++num;
+ }
+ ASSERT_EQ(num, CAPACITY);
+}
+
+
+TEST(FixedKVNodeTest, merge) {
+ auto node = TestNode();
+ auto node2 = TestNode();
+
+ ASSERT_EQ(node.get_size(), 0);
+ ASSERT_EQ(node2.get_size(), 0);
+
+ node.set_size(CAPACITY / 2);
+ node2.set_size(CAPACITY / 2);
+
+ auto total = node.get_size() + node2.get_size();
+
+ unsigned short num = 0;
+ for (auto &i : node) {
+ i.set_key(num);
+ i.set_val({ num, num});
+ ++num;
+ }
+ for (auto &i : node2) {
+ i.set_key(num);
+ i.set_val({ num, num});
+ ++num;
+ }
+
+ auto node_merged = TestNode();
+ node_merged.merge_from(node, node2);
+
+ ASSERT_EQ(node_merged.get_size(), total);
+ num = 0;
+ for (auto &i : node_merged) {
+ ASSERT_EQ(i.get_key(), num);
+ ASSERT_EQ(i.get_val(), (test_val_t{num, num}));
+ if (num < node_merged.get_size() - 1) {
+ ASSERT_EQ(i.get_next_key_or_max(), num + 1);
+ } else {
+ ASSERT_EQ(std::numeric_limits<uint32_t>::max(), i.get_next_key_or_max());
+ }
+ ++num;
+ }
+ ASSERT_EQ(num, total);
+}
+
+void run_balance_test(unsigned left, unsigned right, bool prefer_left)
+{
+ auto node = TestNode();
+ auto node2 = TestNode();
+
+ ASSERT_EQ(node.get_size(), 0);
+ ASSERT_EQ(node2.get_size(), 0);
+
+ node.set_size(left);
+ node2.set_size(right);
+
+ auto total = left + right;
+
+ unsigned short num = 0;
+ for (auto &i : node) {
+ i.set_key(num);
+ i.set_val({ num, num});
+ ++num;
+ }
+ for (auto &i : node2) {
+ i.set_key(num);
+ i.set_val({ num, num});
+ ++num;
+ }
+
+ auto node_balanced = TestNode();
+ auto node_balanced2 = TestNode();
+ TestNode::balance_into_new_nodes(
+ node,
+ node2,
+ prefer_left,
+ node_balanced,
+ node_balanced2);
+
+ ASSERT_EQ(total, node_balanced.get_size() + node_balanced2.get_size());
+
+ if (total % 2) {
+ if (prefer_left) {
+ ASSERT_EQ(node_balanced.get_size(), node_balanced2.get_size() + 1);
+ } else {
+ ASSERT_EQ(node_balanced.get_size() + 1, node_balanced2.get_size());
+ }
+ } else {
+ ASSERT_EQ(node_balanced.get_size(), node_balanced2.get_size());
+ }
+
+ num = 0;
+ for (auto &i: node_balanced) {
+ ASSERT_EQ(i.get_key(), num);
+ ASSERT_EQ(i.get_val(), (test_val_t{num, num}));
+ if (num < node_balanced.get_size() - 1) {
+ ASSERT_EQ(i.get_next_key_or_max(), num + 1);
+ } else {
+ ASSERT_EQ(std::numeric_limits<uint32_t>::max(), i.get_next_key_or_max());
+ }
+ ++num;
+ }
+ for (auto &i: node_balanced2) {
+ ASSERT_EQ(i.get_key(), num);
+ ASSERT_EQ(i.get_val(), (test_val_t{num, num}));
+ if (num < total - 1) {
+ ASSERT_EQ(i.get_next_key_or_max(), num + 1);
+ } else {
+ ASSERT_EQ(std::numeric_limits<uint32_t>::max(), i.get_next_key_or_max());
+ }
+ ++num;
+ }
+}
+
+TEST(FixedKVNodeTest, balanced) {
+ run_balance_test(CAPACITY / 2, CAPACITY, true);
+ run_balance_test(CAPACITY / 2, CAPACITY, false);
+ run_balance_test(CAPACITY, CAPACITY / 2, true);
+ run_balance_test(CAPACITY, CAPACITY / 2, false);
+ run_balance_test(CAPACITY - 1, CAPACITY / 2, true);
+ run_balance_test(CAPACITY / 2, CAPACITY - 1, false);
+ run_balance_test(CAPACITY / 2, CAPACITY / 2, false);
+}