diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 8ba54b2..ca59af4 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -27,6 +27,7 @@ jobs: - name: Building and running tests in debug mode with coverage run: | swift test \ + -v \ -c debug \ --enable-code-coverage \ --build-path .build-test-debug diff --git a/Package.swift b/Package.swift index 09d4833..cbfd899 100644 --- a/Package.swift +++ b/Package.swift @@ -38,10 +38,12 @@ let development = envEnable("OPENGRAPH_DEVELOPMENT", default: false) let swiftBinPath = Context.environment["_"] ?? "/usr/bin/swift" let swiftBinURL = URL(fileURLWithPath: swiftBinPath) let SDKPath = swiftBinURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().path -let includePath = SDKPath.appending("/usr/lib/swift") +let swiftIncludePath = SDKPath.appending("/usr/lib/swift") +let includePath = SDKPath.appending("/usr/include") var sharedCSettings: [CSetting] = [ - .unsafeFlags(["-I", includePath], .when(platforms: .nonDarwinPlatforms)), + .unsafeFlags(["-I", swiftIncludePath], .when(platforms: .nonDarwinPlatforms)), // NOTE: For CoreFoundation include + .unsafeFlags(["-I", includePath], .when(platforms: .nonDarwinPlatforms)), // NOTE: somehow is missing on non-Darwin SDK, .define("NDEBUG", .when(configuration: .release)), ] diff --git a/Sources/OpenGraphCxx/Util/HashTable.cpp b/Sources/OpenGraphCxx/Util/HashTable.cpp new file mode 100644 index 0000000..6dc6a65 --- /dev/null +++ b/Sources/OpenGraphCxx/Util/HashTable.cpp @@ -0,0 +1,306 @@ +// +// HashTable.cpp +// OpenGraphCxx +// +// Status: Complete +// Modified based Compute code + +#include +#include +#include + +namespace util { + +uint64_t pointer_hash(void const *pointer) { + int64_t result = (-1 ^ (int64_t)(pointer) << 0x20) + (int64_t)pointer; + result = result ^ result >> 0x16; + result = result + (-1 ^ result << 0xd); + result = (result ^ result >> 8) * 9; + result = result ^ result >> 0xf; + result = result + (-1 ^ result << 0x1b); + return result ^ result >> 0x1f; +} + +bool pointer_compare(void const *a, void const *b) { return a == b; } + +uint64_t string_hash(char const *str) { + int64_t result = 0; + for (char const *c = str; *c; c += 1) { + result = result * 33 + *c; + } + return result; +} + +constexpr uint32_t initial_bucket_mask_width = 4; + +UntypedTable *UntypedTable::create() { return new UntypedTable(); } + +void UntypedTable::destroy(UntypedTable *value) { delete value; } + +UntypedTable::UntypedTable() { + _hash = pointer_hash; + _compare = pointer_compare; + _did_remove_key = nullptr; + _did_remove_value = nullptr; + _heap = nullptr; + _spare_node = 0; + _buckets = nullptr; + _count = 0; + _bucket_mask = 0; + _bucket_mask_width = 0; + _is_heap_owner = true; + _compare_by_pointer = true; +} + +UntypedTable::UntypedTable(hasher custom_hash, key_equal custom_compare, key_callback did_remove_key, + value_callback did_remove_value, Heap *heap) { + _hash = custom_hash != nullptr ? custom_hash : pointer_hash; + _compare = custom_compare != nullptr ? custom_compare : pointer_compare; + _did_remove_key = did_remove_key; + _did_remove_value = did_remove_value; + _heap = heap; + _spare_node = 0; + _buckets = nullptr; + _count = 0; + _bucket_mask = 0; + _bucket_mask_width = 0; + _is_heap_owner = heap == nullptr; + _compare_by_pointer = custom_compare == nullptr || custom_compare == pointer_compare; +} + +UntypedTable::~UntypedTable() { + if ((_did_remove_key || _did_remove_value) && _count) { + for (uint32_t bucket = 0; !(bucket >> _bucket_mask_width); bucket++) { + for (HashNode *node = _buckets[bucket]; node != nullptr; node = node->next) { + if (_did_remove_key) { + _did_remove_key(node->key); + } + if (_did_remove_value) { + _did_remove_value(node->value); + } + } + } + } + if (_bucket_mask_width > initial_bucket_mask_width) { + free(_buckets); + } + if (_is_heap_owner && _heap) { + delete _heap; + } +} + +#pragma mark - Managing buckets + +// Buckets are initially allocated by the util::Heap instance, +// until they grow past initial_bucket_mask_width where they are allocated using new.ˆ + +void UntypedTable::create_buckets() { + if (_buckets != nullptr) { + return; + } + + _bucket_mask_width = initial_bucket_mask_width; + _bucket_mask = (1 << initial_bucket_mask_width) - 1; + + if (_heap == nullptr) { + _heap = new Heap(nullptr, 0, Heap::minimum_increment); + } + + size_t num_buckets = (1 << initial_bucket_mask_width); + _buckets = _heap->alloc(num_buckets); + memset(_buckets, 0, sizeof(Bucket) * num_buckets); +} + +void UntypedTable::grow_buckets() { + if (_bucket_mask_width > 30) { + return; + } + + uint32_t old_width = _bucket_mask_width; + _bucket_mask_width = old_width + 1; + + Bucket *old_buckets = _buckets; + Bucket *new_buckets = nullptr; + + size_t num_buckets = 1 << _bucket_mask_width; + if (_bucket_mask_width > initial_bucket_mask_width) { + new_buckets = new Bucket[num_buckets]; + } else { + if (_heap == nullptr) { + _heap = new Heap(nullptr, 0, Heap::minimum_increment); + } + new_buckets = _heap->alloc(num_buckets); + } + memset(new_buckets, 0, sizeof(Bucket) * num_buckets); + + // redistribute old buckets into new + if (new_buckets) { + _bucket_mask = num_buckets - 1; + for (uint32_t i = 0; !(i >> old_width); i++) { + for (UntypedTable::HashNode *node = old_buckets[i]; node != nullptr; node = node->next) { + uint64_t new_bucket = _bucket_mask & node->hash_value; + node->next = new_buckets[new_bucket]; + new_buckets[new_bucket] = node; + } + } + _buckets = new_buckets; + + if (old_width > initial_bucket_mask_width) { + delete old_buckets; + } + } +} + +#pragma mark - Lookup + +UntypedTable::value_type UntypedTable::lookup(key_type key, nullable_key_type *found_key_out) const noexcept { + if (_count) { + uint64_t hash_value = _hash(key); + HashNode *node = _buckets[_bucket_mask & hash_value]; + if (_compare_by_pointer) { + for (; node != nullptr; node = node->next) { + if (node->key == key) { + if (found_key_out) { + *found_key_out = node->key; + } + return node->value; + } + } + } else if (node) { + for (; node != nullptr; node = node->next) { + if (node->hash_value == hash_value && _compare(node->key, key)) { + if (found_key_out) { + *found_key_out = node->key; + } + return node->value; + } + } + } + } + if (found_key_out) { + *found_key_out = nullptr; + } + return nullptr; +} + +#pragma mark - Modifying entries + +bool UntypedTable::insert(key_type key, value_type value) { + if (_buckets == nullptr) { + this->create_buckets(); + } + + void *result; + + uint64_t hash_value = _hash(key); + HashNode *node = _buckets[hash_value & _bucket_mask]; + + // replace existing if match + for (; node != nullptr; node = node->next) { + if (node->hash_value == hash_value && _compare(node->key, key)) { + if (_did_remove_key) { + _did_remove_key(node->key); + } + if (_did_remove_value) { + _did_remove_value(node->value); + } + node->key = key; + node->value = value; + return false; + } + } + + // insert new + if (_count + 1 > 4 << _bucket_mask_width) { + this->grow_buckets(); + } + if (!_heap) { + _heap = new Heap(nullptr, 0, Heap::minimum_increment); + } + + HashNode *inserted_node = _spare_node; + if (inserted_node) { + _spare_node = _spare_node->next; + } else { + inserted_node = _heap->alloc(); + } + + inserted_node->key = key; + inserted_node->value = value; + inserted_node->hash_value = hash_value; + + uint64_t bucket = _bucket_mask & hash_value; + inserted_node->next = _buckets[bucket]; + _buckets[bucket] = inserted_node; + + _count += 1; + + return true; +} + +bool UntypedTable::remove(key_type key) { + if (_count == 0) { + return false; + } + if (_compare_by_pointer) { + return this->remove_ptr(key); + } + uint64_t hash_value = _hash(key); + HashNode *node = _buckets[_bucket_mask & hash_value]; + + for (HashNode *candidate = node; candidate != nullptr; candidate = candidate->next) { + if (candidate->hash_value == hash_value && _compare(candidate->key, key)) { + node->next = candidate->next; + if (_did_remove_key) { + _did_remove_key(candidate->key); + } + if (_did_remove_value) { + _did_remove_value(candidate->value); + } + candidate->next = _spare_node; + _spare_node = candidate; + _count -= 1; + return true; + } + node = candidate; + } + + return false; +} + +bool UntypedTable::remove_ptr(key_type key) { + if (_count == 0) { + return false; + } + + HashNode *node = _buckets[_bucket_mask & _hash(key)]; + for (HashNode *candidate = node; candidate != nullptr; candidate = candidate->next) { + if (candidate->key == key) { + node->next = candidate->next; + if (_did_remove_key) { + _did_remove_key(candidate->key); + } + if (_did_remove_value) { + _did_remove_value(candidate->value); + } + candidate->next = _spare_node; + _spare_node = candidate; + _count -= 1; + return true; + } + node = candidate; + } + return false; +} + +void UntypedTable::for_each(entry_callback body, void *context) const { + if (_count) { + for (uint32_t i = 0; !(i >> _bucket_mask_width); i++) { + for (UntypedTable::HashNode *node = _buckets[i]; node != nullptr; node = node->next) { + body(node->key, node->value, context); + } + } + } +} + +} /* namespace util */ diff --git a/Sources/OpenGraphCxx/Util/Heap.cpp b/Sources/OpenGraphCxx/Util/Heap.cpp new file mode 100644 index 0000000..41fc896 --- /dev/null +++ b/Sources/OpenGraphCxx/Util/Heap.cpp @@ -0,0 +1,99 @@ +// +// Heap.cpp +// OpenGraphCxx +// +// Status: Complete +// Modified based Compute code + +#include +#include + +namespace util { + +constexpr size_t default_increment = 0x2000; + +Heap *Heap::create(char *_Nullable start, size_t capacity, size_t increment) { + return new Heap(start, capacity, increment); +} + +void Heap::destroy(Heap *value) { delete value; } + +Heap::Heap(char *start, size_t capacity, size_t increment) { + // enforce minimum but treat 0 as the default + size_t effective_increment = increment > 0 ? std::max(increment, minimum_increment) : default_increment; + + _increment = effective_increment; + _node = nullptr; + reset(start, capacity); +}; + +util::Heap::~Heap() { reset(nullptr, 0); } + +void *util::Heap::alloc_(size_t size) { + if (_capacity >= size) { + char *result = _free_start; + _free_start += size; + _capacity -= size; + return result; + } + + if (size <= minimum_increment) { + int64_t increment = _increment; + char *buffer = static_cast(malloc(increment)); + + _free_start = buffer; + _capacity = increment; + + Node *node = alloc(); + + node->next = _node; + node->buffer = buffer; + _node = node; + + char *result = _free_start; + _free_start += size; + _capacity -= size; + return result; + } + + Node *node = alloc(); + void *result = malloc(size); + if (result) { + node->next = _node; + node->buffer = result; + _node = node; + } + return result; +} + +void util::Heap::reset(char *_Nullable start, size_t capacity) { + while (_node) { + void *buffer = _node->buffer; + _node = _node->next; + free(buffer); + } + + constexpr uintptr_t alignment_mask = sizeof(char *) - 1; + char *aligned_start = (char *)(((uintptr_t)start + alignment_mask) & ~alignment_mask); + + bool prealigned = ((uintptr_t)start & alignment_mask) == 0; + _free_start = prealigned ? start : aligned_start; + _capacity = capacity + (start - aligned_start); +} + +size_t util::Heap::num_nodes() const { + size_t count = 0; + for (Node *node = _node; node != nullptr; node = node->next) { + count += 1; + } + return count; +} + +void util::Heap::print() const { + fprintf(stdout, "Nodes\n"); + for (Node *node = _node; node != nullptr; node = node->next) { + fprintf(stdout, "address=%-16p; buffer=%-16p; next=%-16p\n", node, node->buffer, node->next); + } +} + +}; /* namespace util */ diff --git a/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/ForwardList.hpp b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/ForwardList.hpp new file mode 100644 index 0000000..88b512d --- /dev/null +++ b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/ForwardList.hpp @@ -0,0 +1,179 @@ +// +// ForwardList.hpp +// OpenGraphCxx +// +// Status: Complete +// Modified based Compute code + +#ifndef OPENGRAPH_CXX_UTIL_FORWARD_LIST_HPP +#define OPENGRAPH_CXX_UTIL_FORWARD_LIST_HPP + +#include +#include + +OG_ASSUME_NONNULL_BEGIN + +namespace util { + +/// ForwardList is a linked list container that uses util::Heap to allocate nodes, +/// reusing previously removed nodes where possible. +template class ForwardList { + public: + using reference = T &; + using const_reference = const T &; + + private: + struct Node { + Node *_Nullable next; + T value; + }; + + util::Heap *_heap; + Node *_Nullable _front; + Node *_Nullable _spare; + bool _is_heap_owner; + + public: + ForwardList(); + ForwardList(util::Heap *heap); + ~ForwardList(); + + // non-copyable + ForwardList(const ForwardList &) = delete; + ForwardList &operator=(const ForwardList &) = delete; + + // non-movable + ForwardList(ForwardList &&) = delete; + ForwardList &operator=(ForwardList &&) = delete; + + // MARK: Element access + + reference front(); + const_reference front() const; + + // MARK: Capacity + + bool empty() const noexcept { return _front == nullptr; } + + // MARK: Modifiers + + void push_front(const T &value); + void push_front(T &&value); + template void emplace_front(Args &&...args); + + void pop_front(); +}; + +template +ForwardList::ForwardList() : _heap(new Heap(nullptr, 0, util::Heap::minimum_increment)), _is_heap_owner(true){}; + +template ForwardList::ForwardList(util::Heap *heap) : _heap(heap), _is_heap_owner(false){}; + +template ForwardList::~ForwardList() { + if (_is_heap_owner && _heap) { + delete _heap; + } +}; + +template ForwardList::reference ForwardList::front() { + assert(!empty()); + return _front->value; +} + +template ForwardList::const_reference ForwardList::front() const { + assert(!empty()); + return _front->value; +} + +template void ForwardList::push_front(const T &value) { + Node *new_node; + if (_spare != nullptr) { + new_node = _spare; + _spare = _spare->next; + } else { + new_node = _heap->alloc(); + } + new_node->next = _front; + new_node->value = value; + _front = new_node; +} + +template void ForwardList::push_front(T &&value) { + Node *new_node; + if (_spare != nullptr) { + new_node = _spare; + _spare = _spare->previous; + } else { + new_node = _heap->alloc(); + } + new_node->next = _front; + new_node->value = std::move(value); + _front = new_node; +} + +template template void ForwardList::emplace_front(Args &&...args) { + Node *new_node; + if (_spare != nullptr) { + new_node = _spare; + _spare = _spare->next; + } else { + new_node = _heap->alloc(); + } + new_node->next = _front; + new (&new_node->value) T(args...); + _front = new_node; +} + +template void ForwardList::pop_front() { + if (_front == nullptr) { + return; + } + + Node *next = _front->next; + T value = _front->value; + + _front->next = _spare; + _spare = _front; + + _front = next; +} + +#ifdef SWIFT_TESTING + +class UInt64ForwardList : public ForwardList { + public: + static UInt64ForwardList *create(); + static void destroy(UInt64ForwardList *value); + + bool empty() const noexcept; + + uint64_t front(); + + void push_front(const uint64_t &element); + void push_front(uint64_t &&element); + + void pop_front(); + +} SWIFT_UNSAFE_REFERENCE; + +UInt64ForwardList *UInt64ForwardList::create() { return new UInt64ForwardList(); } + +void UInt64ForwardList::destroy(UInt64ForwardList *value) { delete value; } + +bool UInt64ForwardList::empty() const noexcept { return ForwardList::empty(); } + +uint64_t UInt64ForwardList::front() { return ForwardList::front(); } + +void UInt64ForwardList::push_front(const uint64_t &element) { ForwardList::push_front(element); } + +void UInt64ForwardList::push_front(uint64_t &&element) { ForwardList::push_front(element); } + +void UInt64ForwardList::pop_front() { ForwardList::pop_front(); } + +#endif + +} /* namespace util */ + +OG_ASSUME_NONNULL_END + +#endif /* OPENGRAPH_CXX_UTIL_FORWARD_LIST_HPP */ diff --git a/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/HashTable.hpp b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/HashTable.hpp new file mode 100644 index 0000000..0de0ed3 --- /dev/null +++ b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/HashTable.hpp @@ -0,0 +1,133 @@ +// +// HashTable.hpp +// OpenGraphCxx +// +// Status: Complete +// Modified based Compute code + +#ifndef OPENGRAPH_CXX_UTIL_HASHTABLE_HPP +#define OPENGRAPH_CXX_UTIL_HASHTABLE_HPP + +#include +#include + +OG_ASSUME_NONNULL_BEGIN + +namespace util { + +class Heap; + +uint64_t string_hash(char const *str); + +class UntypedTable { +public: + using key_type = const void *_Nonnull; + using nullable_key_type = const void *_Nullable; + using value_type = const void *_Nullable; + using size_type = uint64_t; + using hasher = uint64_t (*)(void const *); + using key_equal = bool (*)(void const *, void const *); + using key_callback = void (*)(const key_type); + using value_callback = void (*)(const value_type); + using entry_callback = void (*)(const key_type, const value_type, void *context); + +private: + struct HashNode { + HashNode *next; + key_type key; + value_type value; + uint64_t hash_value; + }; + using Bucket = HashNode *_Nonnull; + + hasher _hash; + key_equal _compare; + key_callback _did_remove_key; + value_callback _did_remove_value; + Heap *_heap; + HashNode *_spare_node; + Bucket *_Nonnull _buckets; + uint64_t _count; + uint64_t _bucket_mask; + uint32_t _bucket_mask_width; + bool _is_heap_owner; + bool _compare_by_pointer; + + // Managing buckets + void create_buckets(); + void grow_buckets(); + +public: + static UntypedTable *create(); + static void destroy(UntypedTable *value); + + UntypedTable(); + UntypedTable(hasher _Nullable custom_hasher, key_equal _Nullable custom_compare, + key_callback _Nullable did_remove_key, value_callback _Nullable did_remove_value, + Heap *_Nullable heap); + ~UntypedTable(); + + // non-copyable + UntypedTable(const UntypedTable &) = delete; + UntypedTable &operator=(const UntypedTable &) = delete; + + // non-movable + UntypedTable(UntypedTable &&) = delete; + UntypedTable &operator=(UntypedTable &&) = delete; + + // Lookup + bool empty() const noexcept { return _count == 0; }; + size_type count() const noexcept { return _count; }; + value_type lookup(key_type key, nullable_key_type *_Nullable found_key) const noexcept; + void for_each(entry_callback body, void *context) const; + + // Modifiers + bool insert(const key_type key, const value_type value); + bool remove(const key_type key); + bool remove_ptr(const key_type key); +} SWIFT_UNSAFE_REFERENCE; + +template class Table : public UntypedTable { +public: + using key_type = Key; + using value_type = Value; + using hasher = uint64_t (*)(const key_type); + using key_equal = bool (*)(const key_type, const key_type); + using key_callback = void (*)(const key_type); + using value_callback = void (*)(const value_type); + using entry_callback = void (*)(const key_type, const value_type, void *context); + + Table() : UntypedTable() {}; + Table(hasher _Nullable custom_hasher, key_equal _Nullable custom_compare, key_callback _Nullable did_remove_key, + value_callback _Nullable did_remove_value, Heap *_Nullable heap) + : UntypedTable(reinterpret_cast(custom_hasher), + reinterpret_cast(custom_compare), + reinterpret_cast(did_remove_key), + reinterpret_cast(did_remove_value), heap) {}; + + // Lookup + + value_type lookup(const key_type key, key_type *_Nullable found_key) const noexcept { + auto result = UntypedTable::lookup(*(void **)&key, + reinterpret_cast(found_key)); + return *(value_type *)&result; + }; + + void for_each(entry_callback _Nonnull body, void *_Nullable context) const { + UntypedTable::for_each((UntypedTable::entry_callback)body, context); + }; + + // Modifying entries + + bool insert(const key_type key, const value_type value) { + return UntypedTable::insert(*(void **)&key, *(void **)&value); + }; + bool remove(const key_type key) { return UntypedTable::remove(*(void **)&key); }; + bool remove_ptr(const key_type key) { return UntypedTable::remove_ptr(key); }; +}; + +} /* namespace util */ + +OG_ASSUME_NONNULL_END + +#endif /* OPENGRAPH_CXX_UTIL_HASHTABLE_HPP */ diff --git a/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/Heap.hpp b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/Heap.hpp new file mode 100644 index 0000000..182e81e --- /dev/null +++ b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/Heap.hpp @@ -0,0 +1,80 @@ +// +// Heap.hpp +// OpenGraphCxx +// +// Status: Complete +// Modified based Compute code + +#ifndef OPENGRAPH_CXX_UTIL_HEAP_HPP +#define OPENGRAPH_CXX_UTIL_HEAP_HPP + +#include +#include + +OG_ASSUME_NONNULL_BEGIN + +namespace util { + +class Heap { +protected: + typedef struct Node { + struct Node *next; + void *buffer; + } Node; + + size_t _increment; + Node *_node; + char *_free_start; + size_t _capacity; + + void *alloc_(size_t arg1); + +public: + static constexpr size_t minimum_increment = 0x400; + + static Heap *create(char *_Nullable start, size_t capacity, size_t increment); + static void destroy(Heap *value); + + Heap(char *_Nullable start, size_t capacity, size_t increment); + ~Heap(); + + // non-copyable + Heap(const Heap &) = delete; + Heap &operator=(const Heap &) = delete; + + // non-movable + Heap(Heap &&) = delete; + Heap &operator=(Heap &&) = delete; + + template inline T *_Nonnull alloc(size_t count = 1) { + return static_cast(alloc_(sizeof(T) * count)); + }; + void reset(char *_Nullable start, size_t capacity); + + // Debugging + + size_t num_nodes() const; + size_t increment() const { return _increment; } + size_t capacity() const { return _capacity; } + + void print() const; + + #ifdef SWIFT_TESTING + uint64_t *alloc_uint64_t(size_t count = 1) { return alloc(count); } + #endif + +} SWIFT_UNSAFE_REFERENCE; + +template class InlineHeap : public Heap { +private: + char _inline_buffer[_inline_size] = {}; + +public: + InlineHeap() : Heap(_inline_buffer, _inline_size, 0) {} +}; + +} /* namespace util */ + +OG_ASSUME_NONNULL_END + +#endif /* OPENGRAPH_CXX_UTIL_HEAP_HPP */ diff --git a/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/cf_ptr.hpp b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/cf_ptr.hpp index 59f7832..0449ddf 100644 --- a/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/cf_ptr.hpp +++ b/Sources/OpenGraphCxx/include/OpenGraphCxx/Util/cf_ptr.hpp @@ -97,8 +97,10 @@ template class cf_ptr { explicit operator bool() const OG_NOEXCEPT { return _storage != nullptr; } }; /* class cf_ptr */ +#ifdef SWIFT_TESTING using cf_data_ptr = cf_ptr; using cf_mutable_data_ptr = cf_ptr; +#endif /* SWIFT_TESTING */ } /* namespace util */