From: Egor Date: Tue, 3 Feb 2026 00:36:51 +0000 (+0200) Subject: map: use a flat_map (except it's not std:: for now) X-Git-Url: https://git.patchysicp.xyz/?a=commitdiff_plain;h=c8a90a41e9ee08e58af7b5d2816bd76b432197bd;p=patchyircd.git map: use a flat_map (except it's not std:: for now) --- diff --git a/Makefile.am b/Makefile.am index 479a35c..f62c282 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ ircd_SOURCES = \ src/ircdata.hpp \ src/ircfmt.cpp src/ircfmt.hpp \ src/job.cpp src/job.hpp \ - src/map.cpp src/map.hpp \ + src/map.hpp \ src/net.cpp src/net.hpp \ src/netpoll.cpp src/netpoll.hpp \ src/passwd.cpp src/passwd.hpp \ diff --git a/src/common.hpp b/src/common.hpp index 2a88544..f7ac225 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include using namespace std::string_view_literals; diff --git a/src/map.cpp b/src/map.cpp deleted file mode 100644 index 18cfb9c..0000000 --- a/src/map.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2023, 2025-2026 Egor -// SPDX-License-Identifier: GPL-2.0-or-later -#include "common.hpp" -#include "map.hpp" -#include "ircdata.hpp" - -template -class Comparisons { - friend constexpr bool operator==(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; } - friend constexpr bool operator!=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; } - friend constexpr bool operator< (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs < 0; } - friend constexpr bool operator<=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; } - friend constexpr bool operator> (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs > 0; } - friend constexpr bool operator>=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; } - - friend constexpr bool operator==(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; } - friend constexpr bool operator!=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; } - friend constexpr bool operator< (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs < 0; } - friend constexpr bool operator<=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; } - friend constexpr bool operator> (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs > 0; } - friend constexpr bool operator>=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; } -}; - -class LowercasingView : Comparisons { - std::string_view sv; -public: - explicit constexpr LowercasingView(std::string_view sv) :sv(sv) {} - friend constexpr std::strong_ordering operator<=>(std::string_view lhs, LowercasingView rhs) noexcept { - size_t len = std::min(lhs.size(), rhs.sv.size()); - const char *a = lhs.data(); - const char *b = rhs.sv.data(); - unsigned char cha, chb; - while (len--) { - cha = *a++; - chb = *b++; - if (chb >= 'A' && chb <= 'Z') - chb |= 0x20; - if (auto diff = cha <=> chb; diff != 0) - return diff; - } - return lhs.size() <=> rhs.sv.size(); - } - friend constexpr std::strong_ordering operator<=>(LowercasingView lhs, std::string_view rhs) noexcept { - return rhs <=> lhs; - } -}; - -static size_t ent_find(map_base *root, auto key, int *found) -{ - auto it = std::partition_point(root->ptr, root->ptr + root->len, - [key](const auto &p){ return std::string_view(p.key) < key; }); - *found = it != root->ptr + root->len && std::string_view(it->key) == key; - return it - root->ptr; -} -void *map_base::_get(const char *key) -{ - int found; - size_t i = ent_find(this, std::string_view(key), &found); - return found ? ptr[i].value : 0; -} -void *map_base::_iget(const char *key) -{ - int found; - size_t i = ent_find(this, LowercasingView(key), &found); - return found ? ptr[i].value : 0; -} -void map_base::_set(const char *key, const void *value, void **oldvalue) -{ - int found; - size_t i = ent_find(this, std::string_view(key), &found); - if (found) { - if (oldvalue) - *oldvalue = ptr[i].value; - if (!value) { - delete[] ptr[i].key; - std::copy(ptr+i+1, ptr+len, ptr+i); - len--; - } else { - ptr[i].value = const_cast(value); - } - } else { - if (oldvalue) - *oldvalue = 0; - if (value) { - if (len == cap) { - size_t ncap = cap ? cap*2 : 2; - map_value *nptr = new map_value[ncap]; - memcpy(nptr, ptr, cap*sizeof(map_value)); - ptr = nptr; - cap = ncap; - } - size_t slen = strlen(key)+1; - char *nkey = new char[slen]; - memcpy(nkey, key, slen); - std::copy_backward(ptr+i, ptr+len, ptr+len+1); - len++; - ptr[i].key = nkey; - ptr[i].value = const_cast(value); - } - } -} -void map_base::clear() { - while (len) { - delete[] ptr[--len].key; - } - delete[] ptr; - ptr = 0; - len = cap = 0; -} -map_base::~map_base() { - clear(); -} diff --git a/src/map.hpp b/src/map.hpp index 77021c5..df7b68d 100644 --- a/src/map.hpp +++ b/src/map.hpp @@ -4,6 +4,133 @@ #define MAP_H_ #include "util.hpp" +template +class Comparisons { + friend constexpr bool operator==(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; } + friend constexpr bool operator!=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; } + friend constexpr bool operator< (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs < 0; } + friend constexpr bool operator<=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; } + friend constexpr bool operator> (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs > 0; } + friend constexpr bool operator>=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; } + + friend constexpr bool operator==(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; } + friend constexpr bool operator!=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; } + friend constexpr bool operator< (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs < 0; } + friend constexpr bool operator<=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; } + friend constexpr bool operator> (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs > 0; } + friend constexpr bool operator>=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; } +}; + +class LowercasingView : Comparisons { + std::string_view sv; +public: + explicit constexpr LowercasingView(std::string_view sv) :sv(sv) {} + friend constexpr std::strong_ordering operator<=>(std::string_view lhs, LowercasingView rhs) noexcept { + size_t len = std::min(lhs.size(), rhs.sv.size()); + const char *a = lhs.data(); + const char *b = rhs.sv.data(); + unsigned char cha, chb; + while (len--) { + cha = *a++; + chb = *b++; + if (chb >= 'A' && chb <= 'Z') + chb |= 0x20; + if (auto diff = cha <=> chb; diff != 0) + return diff; + } + return lhs.size() <=> rhs.sv.size(); + } + friend constexpr std::strong_ordering operator<=>(LowercasingView lhs, std::string_view rhs) noexcept { + return 0 <=> (rhs <=> lhs); + } +}; + +// TODO: use std::flat_set once clang is new enough +template +concept is_transparent = requires { typename T::is_transparent; }; +template, typename KeyContainer=std::vector> +class FlatSet { + KeyContainer m_vec; + [[no_unique_address]] Compare cmp; +public: + using iterator = KeyContainer::iterator; + auto begin() noexcept { return m_vec.begin(); } + auto end() noexcept { return m_vec.end(); } + + auto lower_bound(const Key& key) { + return std::partition_point(begin(), end(), [&](const auto &e){ return cmp(e, key); }); + } + template requires is_transparent + auto lower_bound(const K& key) { + return std::partition_point(begin(), end(), [&](const auto &e){ return cmp(e, key); }); + } + + auto find(const Key& key) { + auto it = lower_bound(key); + if (it != end() && !cmp(key, *it)) + return it; + else + return end(); + } + template requires is_transparent + auto find(const K& key) { + auto it = lower_bound(key); + if (it != end() && !cmp(key, *it)) + return it; + else + return end(); + } + + auto insert(const Key& key) { + auto it = lower_bound(key); + if (it != end() && !cmp(key, *it)) { + return std::make_pair(it, false); + } else { + return std::make_pair(m_vec.insert(it, key), true); + } + } + auto insert(Key&& key) { + auto it = lower_bound(key); + if (it != end() && !cmp(key, *it)) { + return std::make_pair(it, false); + } else { + return std::make_pair(m_vec.insert(it, std::move(key)), true); + } + } + + auto erase(typename KeyContainer::iterator it) { + return m_vec.erase(it); + } + size_t erase(const Key& key) { + auto it = lower_bound(key); + if (it != end() && !cmp(key, *it)) { + erase(it); + return 1; + } else { + return 0; + } + } + template requires is_transparent + size_t erase(const K& key) { + auto it = lower_bound(key); + if (it != end() && !cmp(key, *it)) { + erase(it); + return 1; + } else { + return 0; + } + } + + void clear() noexcept { + m_vec.clear(); + } + auto size() noexcept { + return m_vec.size(); + } + + FlatSet& operator=(FlatSet&&) = delete; +}; + /* A map abstract data type with following properties: * - keys are lexicographically-sorted unique NUL-terminated strings * - values are non-NULL pointers @@ -14,31 +141,87 @@ * A map is created by allocating and zero-initializing an object of type * map_t. All map functions take a pointer to map_t. */ +template +struct MapValueLess { + using is_transparent = void; + constexpr std::string_view to_key(const T &v) { + return v.key; + } + constexpr bool operator()(const T& lhs, const T& rhs) noexcept { + return to_key(lhs) < to_key(rhs); + } + constexpr bool operator()(const T& lhs, std::string_view rhs) noexcept { + return to_key(lhs) < rhs; + } + constexpr bool operator()(std::string_view lhs, const T& rhs) noexcept { + return lhs < to_key(rhs); + } + constexpr bool operator()(const T& lhs, LowercasingView rhs) noexcept { + return to_key(lhs) < rhs; + } + constexpr bool operator()(LowercasingView lhs, const T& rhs) noexcept { + return lhs < to_key(rhs); + } +}; struct map_base { struct map_value { char *key; void *value; }; - map_value *ptr = 0; - size_t len = 0; - size_t cap = 0; + FlatSet> m; map_base& operator=(map_base&&) = delete; - ~map_base(); + ~map_base() { + clear(); + } /* Return the associated value for a given key. * Dynarr complexity: O(log n) */ - void *_get(const char *key); + void *_get(const char *key) { + auto it = m.find(std::string_view(key)); + return it != m.end() ? it->value : nullptr; + } /* _get() but lowercase the key */ - void *_iget(const char *key); + void *_iget(const char *key) { + auto it = m.find(LowercasingView(key)); + return it != m.end() ? it->value : nullptr; + } /* Associate a new value with a given key. * If oldvalue is not NULL, it receives the old value of key. * No allocation failures on value = NULL. * Dynarr complexity: O(n) */ - void _set(const char *key, const void *value, void **oldvalue); + void _set(const char *key, const void *value, void **oldvalue) { + if (value) { + map_value mv; + mv.key = const_cast(key); + mv.value = const_cast(value); + auto [it, inserted] = m.insert(mv); + if (inserted) { + if (oldvalue) + *oldvalue = nullptr; + it->key = mystrdup(it->key); + } else { + if (oldvalue) + *oldvalue = it->value; + it->value = mv.value; + } + } else { + auto it = m.find(std::string_view(key)); + if (oldvalue) + *oldvalue = it != m.end() ? it->value : nullptr; + if (it != m.end()) { + delete[] it->key; + m.erase(it); + } + } + } /* Removes all keys from the map and frees any associated memory. * The map_t is reset to zero-initialized state and can be used again. * Dynarr complexity: O(n) */ - void clear(); + void clear() { + for (auto &mv : m) + delete[] mv.key; + m.clear(); + } /* Iterate over a map. * Do not modify the map while iterating. */ @@ -47,8 +230,11 @@ struct map_base { size_t index; auto next() { map_value kv{0, 0}; - if (index < root->len) - kv = root->ptr[index++]; + auto it = root->m.begin() + index; + if (it < root->m.end()) { + kv = *it; + index++; + } return std::make_pair(kv.key, kv.value); } }; @@ -68,12 +254,12 @@ struct map_base2 : map_base { iterator &operator--() { --ptr; return *this; } iterator operator--(int) { auto it = *this; --ptr; return it; } bool operator==(const iterator& other) const = default; - reference operator*() const { return T::to_value(ptr); } - map_value *ptr; + reference operator*() const { return T::to_value(&*ptr); } + decltype(m)::iterator ptr; }; static_assert(std::bidirectional_iterator); - auto begin() { return iterator{ptr}; } - auto end() { return iterator{ptr+len}; } + auto begin() { return iterator{m.begin()}; } + auto end() { return iterator{m.end()}; } struct reverse { map_base2 &obj; auto begin() { return std::make_reverse_iterator(obj.end()); } @@ -84,7 +270,7 @@ struct map_base2 : map_base { return reverse{*this}; } size_t size() { - return len; + return m.size(); } protected: static const char *to_key(V v) {