Tools made in assistance of the Metacall Project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

288 lines
6.4 KiB

#pragma once
#include <cstddef>
#include <array>
#include <utility>
#include <atomic>
#include <new>
#include <memory>
#include <cassert>
#include <optional>
#include <chrono>
#include <thread>
using namespace std::chrono_literals;
/**
Pensé en un mundo sin memoria, sin tiempo; consideré la posibilidad de un lenguaje
que ignorara los sustantivos, un lenguaje de verbos impersonales y de indeclinables
epítetos. Así fueron muriendo los días y con los días los años, pero algo parecido
a la felicidad ocurrió una mañana. Llovió, con lentitud poderosa.
**/
namespace mct20 {
template<typename T>
class accessor {
public:
accessor(const T* ptr, std::atomic<unsigned int>& incremented_ref)
: pointer(ptr)
, reference_cnt(incremented_ref)
{
assert(reference_cnt.load() != 0);
}
accessor(const accessor& a)
: pointer(a.pointer)
, reference_cnt(a.reference_cnt)
{
reference_cnt.fetch_add(1);
}
accessor(const accessor&& a)
: pointer(a.pointer)
, reference_cnt(a.reference_cnt)
{
reference_cnt.fetch_add(1);
}
operator const T&() {
return *pointer;
}
~accessor() {
reference_cnt.fetch_sub(1);
}
private:
const T* pointer;
std::atomic<unsigned int>& reference_cnt;
};
namespace _details_ {
#ifdef __cpp_lib_hardware_interference_size
constexpr size_t predictable_padding = std::hardware_constructive_interference_size;
#else
// Wild guess, may be suboptimal or plain wrong
constexpr size_t predictable_padding = 128;
#endif
size_t rotl(size_t a, uint8_t b) {
b%=sizeof(size_t)*8;
return a << b | a >> (sizeof(size_t)*8 - b);
}
template<size_t against>
constexpr size_t alignment =
(against%predictable_padding!=0)*predictable_padding
+ (against/predictable_padding)*predictable_padding;
template<typename K, typename V>
class bucket {
constexpr static uint32_t delete_mode = 65536;
std::atomic<uint32_t> delete_lock;
void reader_lock() {
while(delete_lock.fetch_add(1) >= delete_mode) {
delete_lock.fetch_sub(1);
std::this_thread::yield();
}
return;
}
void writer_lock() {
while(delete_lock.fetch_add(delete_mode) >= delete_mode) {
delete_lock.fetch_sub(delete_mode);
std::this_thread::yield();
}
while(delete_lock.load() != delete_mode) {
}
return;
}
void reader_unlock() {
delete_lock.fetch_sub(1);
}
void writer_unlock() {
delete_lock.fetch_sub(delete_mode);
}
struct RGuard {
bucket& master;
RGuard(bucket& m)
: master(m)
{master.reader_lock();}
~RGuard()
{master.reader_unlock();}
};
struct WGuard {
bucket& master;
WGuard(bucket& m)
: master(m)
{master.writer_lock();}
~WGuard()
{master.writer_unlock();}
};
public:
bucket()
: start{nullptr}
{}
void remove(size_t hash, const K& key) {
WGuard _g(*this);
auto it = start.load();
auto prev = &start;
do{
if(it == nullptr) return;
while(it->contents.hash != hash)
{
prev = reinterpret_cast<node_ptr*>(&(it->contents.next));
it = (node*)it->contents.next.load();
if(it == nullptr) return;
}
if(it->contents.key == key) {
prev->store(reinterpret_cast<node*>(it->contents.next.load()));
it->contents.references.fetch_sub(1);
while(it->contents.references.load()!=0) {
std::this_thread::yield();
}
delete it->contents.ptr;
delete it;
return;
}
prev = reinterpret_cast<node_ptr*>(&(it->contents.next));
it = (node*)it->contents.next.load();
} while(true);
}
void push(size_t hash, const K& key, const V& value) {
RGuard _g(*this);
auto t = new node{
.contents = node_contents{
.key{key},
.ptr{new V{value}},
.hash{hash},
.references{1}
}
};
t->contents.next.store(t);
node* expect;
do {
expect = start.load();
t->contents.next.store(expect);
} while(
!std::atomic_compare_exchange_strong(
&start,
&expect,
t
)
);
}
std::optional<accessor<V>> get(const size_t hash, const K& key) {
RGuard _g(*this);
auto v = start.load();
while(v) {
if(v->contents.references.fetch_add(1)!=0)
{
if(v->contents.hash == hash) {
if(v->contents.key == key) {
return accessor<V>(
v->contents.ptr,
v->contents.references
);
} else {
auto n = reinterpret_cast<node*>(v->contents.next.load());
v->contents.references.fetch_sub(1);
v = n;
}
} else {
auto n = reinterpret_cast<node*>(v->contents.next.load());
v->contents.references.fetch_sub(1);
v = n;
}
}
else
{
auto n = reinterpret_cast<node*>(v->contents.next.load());
v->contents.references.fetch_sub(1);
v = n;
}
}
return std::nullopt;
}
struct node_contents{
std::atomic<void*> next;
const K key;
const V* ptr;
size_t hash;
std::atomic<unsigned int> references;
};
using node = union {
alignas(alignment<sizeof(node_contents)>) node_contents contents;
};
using node_ptr = std::atomic<node*>;
node_ptr start;
};
}
template<typename K, typename V, size_t bucket_count, typename hash = std::hash<K>>
class lfhmap {
using bucket = _details_::bucket<K, V>;
public:
std::optional<accessor<V>> get(const K& key) {
auto l = hash{}(key);
auto ret = buckets[l%bucket_count].get(l, key);
if(ret) return ret;
l = _details_::rotl(l, sizeof(size_t)*4);
return buckets[l%bucket_count].get(l, key);
}
void set(const K& key, const V& value) {
const auto l = hash{}(key);
auto& ref = buckets[l%bucket_count];
if(ref.start.load() == nullptr)
{
ref.push(l, key, value);
return;
}
const auto l2 = _details_::rotl(l, sizeof(size_t)*4);
auto& ref2 = buckets[l2%bucket_count];
if(ref2.start.load() == nullptr)
{
ref2.push(l2, key, value);
return;
}
if((l^l2)&1) {
ref.push(l, key, value);
} else {
ref2.push(l2, key, value);
}
return;
}
void remove(const K& key) {
const auto l = hash{}(key);
auto& ref = buckets[l%bucket_count];
ref.remove(l, key);
const auto l2 = _details_::rotl(l, sizeof(size_t)*4);
auto& ref2 = buckets[l2%bucket_count];
ref2.remove(l2, key);
return;
}
lfhmap() {
for(auto& a : buckets) {
a.start = nullptr;
}
}
private:
std::array<bucket, bucket_count> buckets;
};
}