[util] Replace kutil with util

Now that kutil has no kernel-specific code in it anymore, it can
actually be linked to by anything, so I'm renaming it 'util'.

Also, I've tried to unify the way that the system libraries from
src/libraries are #included using <> instead of "".

Other small change: util::bip_buffer got a spinlock to guard against
state corruption.
This commit is contained in:
Justin C. Miller
2022-01-03 00:03:29 -08:00
parent 5f88f5ed02
commit cd9b85b555
68 changed files with 231 additions and 203 deletions

View File

@@ -0,0 +1,110 @@
#if __has_include(<assert.h>)
#include <assert.h>
#else
#define assert(x) ((void)0)
#endif
#include <util/bip_buffer.h>
namespace util {
bip_buffer::bip_buffer() :
m_start_a(0),
m_start_b(0),
m_size_a(0),
m_size_b(0),
m_size_r(0),
m_buffer_size(0),
m_buffer(nullptr)
{}
bip_buffer::bip_buffer(uint8_t *buffer, size_t size) :
m_start_a(0),
m_start_b(0),
m_size_a(0),
m_size_b(0),
m_size_r(0),
m_buffer_size(size),
m_buffer(buffer)
{}
size_t bip_buffer::reserve(size_t size, void **area)
{
scoped_lock lock {m_lock};
if (m_size_r) {
*area = nullptr;
return 0;
}
size_t remaining = 0;
if (m_size_b) {
// If B exists, we're appending there. Get space between
// the end of B and start of A.
remaining = m_start_a - m_start_b - m_size_b;
m_start_r = m_start_b + m_size_b;
} else {
// B doesn't exist, check the space both before and after A.
// If the end of A has enough room for this write, put it there.
remaining = m_buffer_size - m_start_a - m_size_a;
m_start_r = m_start_a + m_size_a;
// Otherwise use the bigger of the areas in front of and after A
if (remaining < size && m_start_a > remaining) {
remaining = m_start_a;
m_start_r = 0;
}
}
if (!remaining) {
*area = nullptr;
return 0;
}
m_size_r = (remaining < size) ? remaining : size;
*area = &m_buffer[m_start_r];
return m_size_r;
}
void bip_buffer::commit(size_t size)
{
scoped_lock lock {m_lock};
assert(size <= m_size_r && "Tried to commit more than reserved");
if (m_start_r == m_start_a + m_size_a) {
// We were adding to A
m_size_a += size;
} else {
// We were adding to B
assert(m_start_r == m_start_b + m_size_b && "Bad m_start_r!");
m_size_b += size;
}
m_start_r = m_size_r = 0;
}
size_t bip_buffer::get_block(void **area) const
{
scoped_lock lock {m_lock};
*area = m_size_a ? &m_buffer[m_start_a] : nullptr;
return m_size_a;
}
void bip_buffer::consume(size_t size)
{
scoped_lock lock {m_lock};
assert(size <= m_size_a && "Consumed more bytes than exist in A");
if (size >= m_size_a) {
m_size_a = m_size_b;
m_start_a = m_start_b;
m_size_b = m_start_b = 0;
} else {
m_size_a -= size;
m_start_a += size;
}
}
} // namespace util

View File

@@ -0,0 +1,63 @@
#pragma once
/// \file bip_buffer.h
/// A Bip Buffer (bipartite circular buffer). For more on the Bip Buffer structure, see
/// https://www.codeproject.com/Articles/3479/The-Bip-Buffer-The-Circular-Buffer-with-a-Twist
#include <stddef.h>
#include <stdint.h>
#include <util/spinlock.h>
namespace util {
class bip_buffer
{
public:
/// Default constructor. Creates a zero-size buffer.
bip_buffer();
/// Constructor.
bip_buffer(uint8_t *buffer, size_t size);
/// Reserve an area of buffer for a write.
/// \arg size Requested size, in bytes
/// \arg area [out] Pointer to returned area
/// \returns Size of returned area, in bytes, or 0 on failure
size_t reserve(size_t size, void **area);
/// Commit a pending write started by reserve()
/// \arg size Amount of data used, in bytes
void commit(size_t size);
/// Get a pointer to a block of data in the buffer.
/// \arg area [out] Pointer to the retuned area
/// \returns Size of the returned area, in bytes
size_t get_block(void **area) const;
/// Mark a number of bytes as consumed, freeing buffer space
/// \arg size Number of bytes to consume
void consume(size_t size);
/// Get total amount of data in the buffer.
/// \returns Number of bytes committed to the buffer
inline size_t size() const { return m_size_a + m_size_b; }
/// Get total amount of free buffer remaining
/// \returns Number of bytes of buffer that are free
inline size_t free_space() const { return m_buffer_size - size(); }
private:
size_t m_start_a;
size_t m_start_b;
size_t m_start_r;
size_t m_size_a;
size_t m_size_b;
size_t m_size_r;
mutable spinlock m_lock;
const size_t m_buffer_size;
uint8_t * const m_buffer;
};
} // namespace util

View File

@@ -0,0 +1,42 @@
#pragma once
/// \file constexpr_hash.h
/// A complile-time hashing function
#include <stdint.h>
#include <stddef.h>
namespace util {
constexpr static const uint8_t pearson_hash_table[256] = {
0x76,0x07,0xbe,0x47,0xcf,0x41,0x0a,0xe8,0x01,0x5c,0x9f,0xc5,0x24,0x63,0x9a,0x85,
0x39,0x2c,0xe2,0x34,0xb9,0xf2,0xae,0x40,0x10,0x90,0x94,0xd1,0x98,0x2d,0x16,0xfd,
0xc6,0x48,0x0d,0xce,0x74,0x43,0x28,0xf9,0x61,0x12,0xd0,0xcd,0xd8,0xd7,0xa8,0x78,
0x73,0x70,0xcc,0x1e,0x17,0xa7,0x87,0x38,0x68,0x91,0xc1,0x04,0x3f,0xf5,0xde,0xa3,
0x8a,0xe5,0x9b,0xec,0x97,0xd5,0x71,0x4a,0x20,0xca,0xc8,0xc4,0x83,0x53,0xe7,0x7b,
0x64,0x31,0x06,0xe0,0x7a,0xb6,0x52,0x8c,0xba,0x58,0xcb,0xb5,0x37,0x51,0x59,0xa1,
0x11,0xe3,0x5a,0xdb,0xe1,0x6d,0x46,0x62,0xaf,0xbd,0x57,0xb8,0x0e,0xf4,0xdd,0xa6,
0x45,0xf8,0x35,0x42,0x56,0xdf,0xad,0x80,0xb2,0x0b,0x5b,0xd4,0x86,0xb3,0xf0,0xc9,
0x3c,0xa5,0xc0,0x8e,0x55,0x77,0xeb,0x36,0x79,0xab,0x4c,0x25,0xed,0xa9,0x75,0x8f,
0xee,0xc2,0x72,0x8b,0x60,0x2a,0xfa,0x32,0xe9,0xda,0x03,0x1b,0x27,0x69,0x18,0x9e,
0x88,0x96,0x54,0x81,0x30,0x22,0x7c,0x4f,0xc7,0xef,0x5d,0xa4,0x67,0x44,0xc3,0x99,
0xbb,0xd3,0x8d,0x65,0xb1,0x82,0x09,0x1a,0x13,0xd9,0x9c,0x4d,0xb0,0xfc,0xac,0xbc,
0x6a,0x29,0x95,0x19,0x92,0xaa,0x49,0x7d,0x3b,0xfb,0x50,0xb7,0xf3,0x5e,0x3e,0x6b,
0x3a,0x14,0x2b,0xb4,0xfe,0xe6,0x93,0x23,0xd6,0x1f,0xd2,0x0c,0x1d,0x9d,0x6c,0x66,
0x1c,0x89,0xbf,0xf6,0xff,0x6f,0x84,0x6e,0x2e,0xea,0x21,0xf7,0x7f,0x33,0xf1,0xe4,
0x3d,0x0f,0x05,0x08,0x4e,0xa2,0xa0,0x2f,0xdc,0x00,0x5f,0x15,0x7e,0x02,0x4b,0x26
};
constexpr inline uint8_t pearson_hash_8(const char *s, uint8_t inv) {
return (*s) ? pearson_hash_8(s + 1, pearson_hash_table[inv ^ *s]) : inv;
}
constexpr inline uint32_t djb_hash_32(const char *s, int off = 0) {
return !s[off] ? 5381 : (djb_hash_32(s, off+1)*33) ^ s[off];
}
} // namespace util
constexpr inline uint8_t operator "" _h (const char *s, size_t len) {
return util::pearson_hash_8(s, static_cast<uint8_t>(len & 0xff));
}

View File

@@ -0,0 +1,43 @@
#pragma once
/// \file hash.h
/// Simple templated hashing functions
#include <stddef.h>
#include <stdint.h>
namespace util {
constexpr uint64_t fnv_64_prime = 0x100000001b3ull;
constexpr uint64_t fnv1a_64_init = 0xcbf29ce484222325ull;
/// Return the FNV-1a hash of the given 0-terminated string.
inline uint64_t hash_string(char const *s, uint64_t init = 0) {
if (!init) init = fnv1a_64_init;
while(s && *s) {
init ^= static_cast<uint64_t>(*s++);
init *= fnv_64_prime;
}
return init;
}
/// Return the FNV-1a hash of the given buffer.
inline uint64_t hash_buffer(const void *v, size_t len, uint64_t init = 0) {
uint8_t const *p = reinterpret_cast<uint8_t const*>(v);
uint8_t const *end = p + len;
if (!init) init = fnv1a_64_init;
while(p < end) {
init ^= static_cast<uint64_t>(*p++);
init *= fnv_64_prime;
}
return init;
}
template <typename T>
inline uint64_t hash(const T &v) {
return hash_buffer(reinterpret_cast<const void*>(&v), sizeof(T));
}
template <> inline uint64_t hash<uint64_t>(const uint64_t &i) { return i; }
template <> inline uint64_t hash<const char *>(const char * const &s) { return hash_string(s); }
} // namespace util

View File

@@ -0,0 +1,349 @@
#pragma once
/// \file linked_list.h
/// A generic templatized linked list.
#include <stddef.h>
namespace util {
template <typename T> class linked_list;
/// A list node in a `linked_list<T>` or `sortable_linked_list<T>`.
template <typename T>
class list_node :
public T
{
public:
using item_type = T;
using node_type = list_node<T>;
/// Dereference operator. Helper to cast this node to the contained type.
/// \returns A pointer to the node, cast to T*.
inline item_type & operator*() { return *this; }
/// Dereference operator. Helper to cast this node to the contained type.
/// \returns A pointer to the node, cast to T*.
inline const item_type & operator*() const { return *this; }
/// Cast operator. Helper to cast this node to the contained type.
/// \returns A reference to the node, cast to T&.
inline operator item_type& () { return *this; }
/// Cast operator. Helper to cast this node to the contained type.
/// \returns A reference to the node, cast to const T&.
inline operator const item_type& () { return *this; }
/// Accessor for the next pointer.
/// \returns The next node in the list
inline node_type * next() { return m_next; }
/// Accessor for the next pointer.
/// \returns The next node in the list
inline const node_type * next() const { return m_next; }
/// Accessor for the prev pointer.
/// \returns The prev node in the list
inline node_type * prev() { return m_prev; }
/// Accessor for the prev pointer.
/// \returns The prev node in the list
inline const node_type * prev() const { return m_prev; }
private:
friend class linked_list<T>;
/// Insert an item after this one in the list.
/// \arg item The item to insert
void insert_after(node_type *item)
{
if (m_next) m_next->m_prev = item;
item->m_next = m_next;
item->m_prev = this;
m_next = item;
}
/// Insert an item before this one in the list.
/// \arg item The item to insert
void insert_before(node_type *item)
{
if (m_prev) m_prev->m_next = item;
item->m_prev = m_prev;
item->m_next = this;
m_prev = item;
}
/// Remove this item from its list.
void remove()
{
if (m_next) m_next->m_prev = m_prev;
if (m_prev) m_prev->m_next = m_next;
m_next = m_prev = nullptr;
}
node_type *m_next;
node_type *m_prev;
};
/// An iterator for linked lists
template <typename T>
class list_iterator
{
public:
using item_type = list_node<T>;
list_iterator(item_type *item) : m_item(item) {}
inline item_type * operator*() { return m_item; }
inline const item_type * operator*() const { return m_item; }
inline list_iterator & operator++() { m_item = m_item ? m_item->next() : nullptr; return *this; }
inline list_iterator operator++(int) { return list_iterator<T>(m_item ? m_item->next() : nullptr); }
inline bool operator!=(const list_iterator<T> &other) { return m_item != other.m_item; }
private:
item_type *m_item;
};
/// A templatized doubly-linked list container of `list_node<T>` items.
template <typename T>
class linked_list
{
public:
using item_type = list_node<T>;
using iterator = list_iterator<T>;
/// Constructor. Creates an empty list.
linked_list() :
m_head(nullptr),
m_tail(nullptr),
m_count(0)
{}
/// Move constructor. Takes ownership of list elements.
linked_list(linked_list<T> &&other) :
m_head(other.m_head),
m_tail(other.m_tail),
m_count(other.m_count)
{
other.m_head = other.m_tail = nullptr;
other.m_count = 0;
}
/// Assignment operator. Takes ownership of list elements.
/// Destructive towards current data!
linked_list & operator=(linked_list &&other)
{
m_head = other.m_head;
m_tail = other.m_tail;
m_count = other.m_count;
other.m_head = other.m_tail = nullptr;
other.m_count = 0;
return *this;
}
/// Check if the list is empty.
/// \returns true if the list is empty
bool empty() const { return m_head == nullptr; }
/// Get the cached length of the list.
/// \returns The number of entries in the list.
size_t length() const { return m_count; }
/// Count the items in the list.
/// \returns The number of entries in the list.
size_t count_length()
{
size_t len = 0;
for (item_type *cur = m_head; cur; cur = cur->m_next) ++len;
m_count = len;
return len;
}
/// Get the item at the front of the list, without removing it
/// \returns The first item in the list
inline item_type * front() { return m_head; }
/// Get the item at the back of the list, without removing it
/// \returns The last item in the list
inline item_type * back() { return m_tail; }
/// Prepend an item to the front of this list.
/// \arg item The node to insert.
void push_front(item_type *item)
{
if (!item)
return;
if (!m_head) {
m_head = m_tail = item;
item->m_next = item->m_prev = nullptr;
} else {
m_head->m_prev = item;
item->m_next = m_head;
item->m_prev = nullptr;
m_head = item;
}
m_count += 1;
}
/// Append an item to the end of this list.
/// \arg item The node to append.
void push_back(item_type *item)
{
if (!item)
return;
if (!m_tail) {
m_head = m_tail = item;
item->m_next = item->m_prev = nullptr;
} else {
m_tail->m_next = item;
item->m_prev = m_tail;
item->m_next = nullptr;
m_tail = item;
}
m_count += 1;
}
/// Remove an item from the front of this list.
/// \returns The node that was removed
item_type * pop_front()
{
item_type *item = m_head;
remove(item);
return item;
}
/// Remove an item from the end of this list.
/// \returns The node that was removed
item_type * pop_back()
{
item_type *item = m_tail;
remove(item);
return item;
}
/// Append the contents of another list to the end of this list. The other
/// list is emptied, and this list takes ownership of its items.
/// \arg list The other list.
void append(linked_list<T> &list)
{
if (!list.m_head) return;
if (!m_tail) {
m_head = list.m_head;
m_tail = list.m_tail;
} else {
m_tail->m_next = list.m_head;
m_tail = list.m_tail;
}
m_count += list.m_count;
list.m_count = 0;
list.m_head = list.m_tail = nullptr;
}
/// Append the contents of another list to the end of this list. The other
/// list is emptied, and this list takes ownership of its items.
/// \arg list The other list.
void append(linked_list<T> &&list)
{
if (!list.m_head) return;
if (!m_tail) {
m_head = list.m_head;
m_tail = list.m_tail;
} else {
m_tail->m_next = list.m_head;
m_tail = list.m_tail;
}
m_count += list.m_count;
list.m_count = 0;
list.m_head = list.m_tail = nullptr;
}
/// Remove an item from the list.
/// \arg item The item to remove
void remove(item_type *item)
{
if (!item) return;
if (item == m_head)
m_head = item->m_next;
if (item == m_tail)
m_tail = item->m_prev;
item->remove();
m_count -= 1;
}
/// Inserts an item into the list before another given item.
/// \arg existing The existing item to insert before
/// \arg item The new item to insert
void insert_before(item_type *existing, item_type *item)
{
if (!item) return;
if (!existing) {
push_back(item);
} else if (existing == m_head) {
push_front(item);
} else {
existing->insert_before(item);
m_count += 1;
}
}
/// Inserts an item into the list after another given item.
/// \arg existing The existing item to insert after
/// \arg item The new item to insert
void insert_after(item_type *existing, item_type *item)
{
if (!item) return;
if (!existing) {
push_front(item);
} else if (existing == m_tail) {
push_back(item);
} else {
existing->insert_after(item);
m_count += 1;
}
}
/// Insert an item into the list in a sorted position. Depends on T
/// having a method `int compare(const T *other)`.
/// \arg item The item to insert
void sorted_insert(item_type *item)
{
if (!item) return;
item_type *cur = m_head;
while (cur && item->compare(*cur) > 0)
cur = cur->m_next;
insert_before(cur, item);
}
/// Range-based for iterator generator.
/// \returns An iterator to the beginning of the list
inline iterator begin() { return iterator(m_head); }
/// Range-based for iterator generator.
/// \returns A const iterator to the beginning of the list
inline const iterator begin() const { return iterator(m_head); }
/// Range-based for end-iterator generator.
/// \returns An iterator to the end of the list
inline const iterator end() const { return iterator(nullptr); }
private:
item_type *m_head;
item_type *m_tail;
size_t m_count;
};
} // namespace util

View File

@@ -0,0 +1,294 @@
#pragma once
/// \file map.h
/// Definition of a simple associative array collection for use in kernel space.
/// Thanks to the following people for inspiration of this implementation:
///
/// Sebastian Sylvan
/// https://www.sebastiansylvan.com/post/robin-hood-hashing-should-be-your-default-hash-table-implementation/
///
/// Emmanuel Goossaert
/// http://codecapsule.com/2013/11/11/robin-hood-hashing/
/// http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
#include <new>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <util/hash.h>
#include <util/vector.h>
#include <util/util.h>
namespace util {
/// Templated equality check to allow overriding
template <typename T>
inline bool equal(const T &a, const T &b) { return a == b; }
template <>
inline bool equal<const char *>(const char * const &a, const char * const &b) {
if (!a || !b) return a == b;
const char *a1 = a, *b1 = b;
while (*a1 && *b1) if (*a1++ != *b1++) return false;
return *a1 == *b1; // Make sure they're both zero
}
template <typename K, typename V>
struct hash_node
{
uint64_t h {0};
K key;
V val;
hash_node(hash_node &&o) : h(o.h), key(std::move(o.key)), val(std::move(o.val)) {}
hash_node(uint64_t h, K &&k, V &&v) : h(h), key(std::move(k)), val(std::move(v)) {}
~hash_node() { h = 0; }
inline uint64_t & hash() { return h; }
inline uint64_t hash() const { return h; }
};
/// Base class for hash maps
template <typename K, typename V>
class base_map
{
protected:
using node = hash_node<K, V>;
public:
static constexpr size_t min_capacity = 8;
static constexpr size_t max_load = 90;
class iterator
{
public:
inline node & operator*() { return *m_node; }
inline node * operator->() { return m_node; }
inline const node & operator*() const { return *m_node; }
inline iterator & operator++() { incr(); return *this; }
inline iterator operator++(int) { node *old = m_node; incr(); return iterator(old); }
inline bool operator!=(const iterator &o) { return m_node != o.m_node; }
private:
friend class base_map;
iterator(node *n) : m_node(n), m_end(n) {}
iterator(node *n, node *end) : m_node(n), m_end(end) {}
void incr() { while (m_node < m_end) { ++m_node; if (m_node->hash()) break; } }
node *m_node;
node *m_end;
};
/// Default constructor. Creates an empty map with the given capacity.
base_map(size_t capacity = 0) :
m_count(0),
m_capacity(0),
m_nodes(nullptr)
{
if (capacity)
set_capacity(1 << log2(capacity));
}
virtual ~base_map() {
for (size_t i = 0; i < m_capacity; ++i)
m_nodes[i].~node();
delete [] reinterpret_cast<uint8_t*>(m_nodes);
}
iterator begin() {
if (!m_count) return iterator {0};
iterator it {m_nodes - 1, m_nodes + m_capacity};
return ++it;
}
const iterator begin() const {
if (!m_count) return iterator {0};
iterator it {m_nodes - 1, m_nodes + m_capacity};
return ++it;
}
const iterator end() const {
if (!m_count) return iterator {0};
return iterator(m_nodes + m_capacity);
}
void insert(K k, V v) {
if (++m_count > threshold()) grow();
insert_node(hash(k), std::move(k), std::move(v));
}
bool erase(const K &k)
{
node *n = lookup(k);
if (!n) return false;
n->~node();
--m_count;
size_t i = n - m_nodes;
while (true) {
size_t next = mod(i+1);
node &m = m_nodes[next];
if (!m.hash() || mod(m.hash()) == next) break;
construct(i, m.hash(), std::move(m.key), std::move(m.val));
m.~node();
i = mod(++i);
}
return true;
}
inline size_t count() const { return m_count; }
inline size_t capacity() const { return m_capacity; }
inline size_t threshold() const { return (m_capacity * max_load) / 100; }
protected:
inline size_t mod(uint64_t i) const { return i & (m_capacity - 1); }
inline size_t offset(uint64_t h, size_t i) const {
return mod(i + m_capacity - mod(h));
}
void set_capacity(size_t capacity) {
assert((capacity & (capacity - 1)) == 0 &&
"Map capacity must be a power of two");
m_capacity = capacity;
const size_t size = m_capacity * sizeof(node);
m_nodes = reinterpret_cast<node*>(new uint8_t [size]);
memset(m_nodes, 0, size);
}
void grow() {
node *old = m_nodes;
size_t count = m_capacity;
size_t cap = m_capacity * 2;
if (cap < min_capacity)
cap = min_capacity;
set_capacity(cap);
for (size_t i = 0; i < count; ++i) {
node &n = old[i];
insert_node(n.hash(), std::move(n.key), std::move(n.val));
n.~node();
}
delete [] reinterpret_cast<uint8_t*>(old);
}
inline node * construct(size_t i, uint64_t h, K &&k, V &&v) {
return new (&m_nodes[i]) node(h, std::move(k), std::move(v));
}
node * insert_node(uint64_t h, K &&k, V &&v) {
size_t i = mod(h);
size_t dist = 0;
while (true) {
if (!m_nodes[i].hash()) {
return construct(i, h, std::move(k), std::move(v));
}
node &elem = m_nodes[i];
size_t elem_dist = offset(elem.hash(), i);
if (elem_dist < dist) {
std::swap(h, elem.hash());
std::swap(k, elem.key);
std::swap(v, elem.val);
dist = elem_dist;
}
i = mod(++i);
++dist;
}
}
node * lookup(const K &k) {
if (!m_count)
return nullptr;
uint64_t h = hash(k);
size_t i = mod(h);
size_t dist = 0;
while (true) {
node &n = m_nodes[i];
if (!n.hash() || dist > offset(n.hash(), i))
return nullptr;
else if (n.hash() == h && equal(n.key, k))
return &n;
i = mod(++i);
++dist;
}
}
const node * lookup(const K &k) const {
if (!m_count)
return nullptr;
uint64_t h = hash(k);
size_t i = mod(h);
size_t dist = 0;
while (true) {
const node &n = m_nodes[i];
if (!n.hash() || dist > offset(n.hash(), i))
return nullptr;
else if (n.hash() == h && equal(n.key, k))
return &n;
i = mod(++i);
++dist;
}
}
size_t m_count;
size_t m_capacity;
node *m_nodes;
};
/// An open addressing hash map using robinhood hashing.
template <typename K, typename V>
class map :
public base_map<K, V>
{
using base = base_map<K, V>;
using node = typename base::node;
public:
map(size_t capacity = 0) :
base(capacity) {}
V * find(const K &k) {
node *n = this->lookup(k);
return n ? &n->val : nullptr;
}
const V * find(const K &k) const {
const node *n = this->lookup(k);
return n ? &n->val : nullptr;
}
};
/// An open addressing hash map using robinhood hashing. Specialization
/// for storing pointers: don't return a pointer to a pointer.
template <typename K, typename V>
class map <K, V*> :
public base_map<K, V*>
{
using base = base_map<K, V*>;
using node = typename base::node;
public:
map(size_t capacity = 0) :
base(capacity) {}
V * find(const K &k) const {
const node *n = this->lookup(k);
return n ? n->val : nullptr;
}
};
} // namespace util

View File

@@ -0,0 +1,12 @@
#pragma once
namespace util {
constexpr uint32_t
byteswap(uint32_t x)
{
return ((x >> 24) & 0x000000ff) | ((x >> 8) & 0x0000ff00)
| ((x << 8) & 0x00ff0000) | ((x << 24) & 0xff000000);
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
/// \file no_construct.h
/// Tools for creating objects witout running constructors
namespace util {
/// Helper template for creating objects witout running constructors
template <typename T>
union no_construct
{
T value;
no_construct() {}
~no_construct() {}
};
} // namespace util

View File

@@ -0,0 +1,46 @@
/// \file spinlock.h
/// Spinlock types and related defintions
#pragma once
namespace util {
/// An MCS based spinlock
class spinlock
{
public:
spinlock();
~spinlock();
/// A node in the wait queue.
struct waiter
{
bool blocked;
waiter *next;
};
void acquire(waiter *w);
void release(waiter *w);
private:
waiter *m_lock;
};
/// Scoped lock that owns a spinlock::waiter
class scoped_lock
{
public:
inline scoped_lock(spinlock &lock) : m_lock(lock) {
m_lock.acquire(&m_waiter);
}
inline ~scoped_lock() {
m_lock.release(&m_waiter);
}
private:
spinlock &m_lock;
spinlock::waiter m_waiter;
};
} // namespace util

View File

@@ -0,0 +1,16 @@
#pragma once
/// \file util.h
/// Utility functions used in other util code
#include <stdint.h>
namespace util {
// Get the base-2 logarithm of i
inline unsigned log2(uint64_t i) {
if (i < 2) return 0;
const unsigned clz = __builtin_clzll(i - 1);
return 64 - clz;
}
}

View File

@@ -0,0 +1,296 @@
#pragma once
/// \file vector.h
/// Definition of a simple dynamic vector collection for use in kernel space
#include <assert.h>
#include <string.h>
#include <utility>
#include <util/util.h>
namespace util {
/// A dynamic array.
template <typename T, typename S = uint32_t>
class vector
{
using count_t = S;
static constexpr count_t min_capacity = 4;
static constexpr count_t cap_mask = static_cast<S>(-1) >> 1;
public:
/// Default constructor. Creates an empty vector with no capacity.
vector() :
m_size(0),
m_capacity(0),
m_elements(nullptr)
{}
/// Constructor. Creates an empty array with capacity.
/// \arg capacity Initial capacity to allocate
vector(count_t capacity) :
m_size(0),
m_capacity(0),
m_elements(nullptr)
{
set_capacity(capacity);
}
/// Copy constructor. Allocates a copy of the other's array.
vector(const vector& other) :
m_size(0),
m_capacity(0),
m_elements(nullptr)
{
set_capacity(other.m_capacity);
memcpy(m_elements, other.m_elements, other.m_size * sizeof(T));
m_size = other.m_size;
}
/// Move constructor. Takes ownership of the other's array.
vector(vector &&other) :
m_size(other.m_size),
m_capacity(other.m_capacity),
m_elements(other.m_elements)
{
other.m_size = 0;
other.m_capacity = 0;
other.m_elements = nullptr;
}
/// Static array constructor. Starts the vector off with the given
/// static storage.
vector(T *data, count_t size, count_t capacity) :
m_size(size),
m_capacity(capacity | ~cap_mask),
m_elements(&data[0])
{
}
/// Destructor. Destroys any remaining items in the array.
~vector()
{
while (m_size) remove();
bool was_static = m_capacity & ~cap_mask;
if (!was_static)
delete [] m_elements;
}
/// Get the size of the array.
inline count_t count() const { return m_size; }
/// Get the capacity of the array. This is the amount of space
/// actually allocated.
inline count_t capacity() const { return m_capacity & cap_mask; }
/// Access an element in the array.
inline T & operator[] (count_t i) { return m_elements[i]; }
/// Access an element in the array.
inline const T & operator[] (count_t i) const { return m_elements[i]; }
/// Get a pointer to the beginning for iteration.
T * begin() { return m_elements; }
/// Get a pointer to the beginning for iteration.
const T * begin() const { return m_elements; }
/// Get a pointer to the end for iteration.
T * end() { return m_elements + m_size; }
/// Get a pointer to the end for iteration.
const T * end() const { return m_elements + m_size; }
/// Add an item onto the array by copying it.
/// \arg item The item to add
/// \returns A reference to the added item
T & append(const T& item)
{
ensure_capacity(m_size + 1);
m_elements[m_size] = item;
return m_elements[m_size++];
}
/// Construct an item in place onto the end of the array.
/// \returns A reference to the added item
template <typename... Args>
T & emplace(Args&&... args)
{
ensure_capacity(m_size + 1);
new (&m_elements[m_size]) T(std::forward<Args>(args)...);
return m_elements[m_size++];
}
/// Insert an item into the array at the given index
void insert(count_t i, const T& item)
{
if (i >= count()) {
append(item);
return;
}
ensure_capacity(m_size + 1);
for (count_t j = m_size; j > i; --j)
m_elements[j] = m_elements[j-1];
m_size += 1;
m_elements[i] = item;
}
/// Insert an item into the list in a sorted position. Depends on T
/// having a method `int compare(const T &other)`.
/// \returns index of the new item
count_t sorted_insert(const T& item)
{
count_t start = 0;
count_t end = m_size;
while (end > start) {
count_t m = start + (end - start) / 2;
int c = item.compare(m_elements[m]);
if (c < 0) end = m;
else start = m + 1;
}
insert(start, item);
return start;
}
/// Remove an item from the end of the array.
void remove()
{
assert(m_size && "Called remove() on an empty array");
m_size -= 1;
m_elements[m_size].~T();
}
/// Remove an item from the front of the array, preserving order.
void remove_front()
{
assert(m_size && "Called remove_front() on an empty array");
remove_at(0);
}
/// Remove an item from the array.
void remove(const T &item)
{
assert(m_size && "Called remove() on an empty array");
for (count_t i = 0; i < m_size; ++i) {
if (m_elements[i] == item) {
remove_at(i);
break;
}
}
}
/// Remove n items starting at the given index from the array,
/// order-preserving.
void remove_at(count_t i, count_t n = 1)
{
for (count_t j = i; j < i + n; ++j) {
if (j >= m_size) return;
m_elements[j].~T();
}
for (; i < m_size - n; ++i)
m_elements[i] = m_elements[i+n];
m_size -= n;
}
/// Remove the first occurance of an item from the array, not
/// order-preserving. Does nothing if the item is not in the array.
void remove_swap(const T &item)
{
for (count_t i = 0; i < m_size; ++i) {
if (m_elements[i] == item) {
remove_swap_at(i);
break;
}
}
}
/// Remove the item at the given index from the array, not
/// order-preserving.
void remove_swap_at(count_t i)
{
if (i >= count()) return;
m_elements[i].~T();
if (i < m_size - 1)
m_elements[i] = m_elements[m_size - 1];
m_size -= 1;
}
/// Remove an item from the end of the array and return it.
T pop()
{
assert(m_size && "Called pop() on an empty array");
T temp = m_elements[m_size - 1];
remove();
return temp;
}
/// Remove an item from the beginning of the array and return it.
T pop_front()
{
assert(m_size && "Called pop_front() on an empty array");
T temp = m_elements[0];
remove_front();
return temp;
}
/// Set the size of the array. Any new items are default constructed.
/// Any items past the end are deleted. The array is realloced if needed.
/// \arg size The new size
void set_size(count_t size)
{
ensure_capacity(size);
for (count_t i = size; i < m_size; ++i)
m_elements[i].~T();
for (count_t i = m_size; i < size; ++i)
new (&m_elements[i]) T;
m_size = size;
}
/// Ensure the array will fit an item.
/// \arg size Size of the array
void ensure_capacity(count_t size)
{
if (capacity() >= size) return;
count_t capacity = (1 << log2(size));
if (capacity < min_capacity)
capacity = min_capacity;
set_capacity(capacity);
}
/// Reallocate the array. Copy over any old elements that will
/// fit into the new array. The rest are destroyed.
/// \arg capacity Number of elements to allocate
void set_capacity(count_t capacity)
{
bool was_static = m_capacity & ~cap_mask;
T *new_array = reinterpret_cast<T*>(new uint8_t [capacity * sizeof(T)]);
count_t size = capacity > m_size ? m_size : capacity;
memcpy(new_array, m_elements, size * sizeof(T));
while (size < m_size) remove();
m_size = size;
m_capacity = capacity;
if (!was_static)
delete [] m_elements;
m_elements = new_array;
}
private:
count_t m_size;
count_t m_capacity;
T *m_elements;
};
} // namespace util

View File

@@ -0,0 +1,46 @@
#include <util/spinlock.h>
namespace util {
static constexpr int memorder = __ATOMIC_SEQ_CST;
spinlock::spinlock() : m_lock {nullptr} {}
spinlock::~spinlock() {}
void
spinlock::acquire(waiter *w)
{
w->next = nullptr;
w->blocked = true;
// Point the lock at this waiter
waiter *prev = __atomic_exchange_n(&m_lock, w, memorder);
if (prev) {
// If there was a previous waiter, wait for them to
// unblock us
prev->next = w;
while (w->blocked)
asm ("pause");
} else {
w->blocked = false;
}
}
void
spinlock::release(waiter *w)
{
// If we're still the last waiter, we're done
waiter *expected = w;
if(__atomic_compare_exchange_n(&m_lock, &expected, nullptr, false, memorder, memorder))
return;
// Wait for the subseqent waiter to tell us who they are
while (!w->next)
asm ("pause");
// Unblock the subseqent waiter
w->next->blocked = false;
}
} // namespace util

View File

@@ -0,0 +1,9 @@
# vim: ft=python
module("util",
kind = "lib",
includes = [ "include" ],
sources = [
"bip_buffer.cpp",
"spinlock.cpp",
])