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.
 
 
 

198 lines
4.3 KiB

#pragma once
#include <cstddef>
#include <array>
#include <utility>
#include <atomic>
#include <new>
#include <memory>
#include <cassert>
#include <optional>
/**
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 {
public:
bucket()
: start{nullptr}
{}
void push(size_t hash, const K& key, const V& value) {
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) {
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;
}
lfhmap() {
for(auto& a : buckets) {
a.start = nullptr;
}
}
private:
std::array<bucket, bucket_count> buckets;
};
}