25 changes: 25 additions & 0 deletions libc/src/__support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_header_library(
bit.h
DEPENDS
libc.src.__support.macros.attributes
libc.src.__support.CPP.type_traits
)

add_header_library(
Expand Down Expand Up @@ -230,6 +231,28 @@ add_header_library(
libc.src.__support.OSUtil.osutil
)

add_header_library(
hash
HDRS
hash.h
DEPENDS
.bit
.uint128
libc.src.__support.macros.attributes
)

add_header_library(
memory_size
HDRS
memory_size.h
DEPENDS
libc.src.__support.CPP.type_traits
libc.src.__support.CPP.limits
libc.src.__support.macros.optimization
libc.src.__support.macros.attributes
libc.src.__support.macros.config
)

add_subdirectory(FPUtil)
add_subdirectory(OSUtil)
add_subdirectory(StringUtil)
Expand All @@ -241,3 +264,5 @@ add_subdirectory(RPC)
add_subdirectory(threads)

add_subdirectory(File)

add_subdirectory(HashTable)
52 changes: 52 additions & 0 deletions libc/src/__support/HashTable/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# TODO: `DISABLE_SSE2_OPT` does not quite work yet.
# We will investigate a better way of feature flag control.
add_header_library(
bitmask
HDRS
bitmask.h
FLAGS
DISABLE_SSE2_OPT
DEPENDS
libc.src.__support.bit
libc.src.__support.macros.properties.cpu_features
)

list(FIND TARGET_ENTRYPOINT_NAME_LIST getrandom getrandom_index)
if (NOT ${getrandom_index} EQUAL -1)
message(STATUS "Using getrandom for hashtable randomness")
set(randomness_compile_flags -DLIBC_HASHTABLE_USE_GETRANDOM)
set(randomness_extra_depends
libc.src.sys.random.getrandom libc.src.errno.errno)
endif()


add_header_library(
table
HDRS
table.h
DEPENDS
.bitmask
libc.src.__support.memory_size
libc.src.__support.bit
libc.src.__support.CPP.type_traits
libc.src.__support.CPP.new
libc.src.__support.macros.attributes
libc.src.__support.macros.optimization
libc.src.__support.hash
libc.src.string.memset
libc.src.string.strcmp
libc.src.string.strlen
libc.include.llvm-libc-types.ENTRY
)

add_header_library(
randomness
HDRS
randomness.h
COMPILE_OPTIONS
${randomness_compile_flags}
DEPENDS
libc.src.__support.hash
libc.src.__support.common
${randomness_extra_depends}
)
94 changes: 94 additions & 0 deletions libc/src/__support/HashTable/bitmask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//===-- HashTable BitMasks --------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC___SUPPORT_HASHTABLE_BITMASK_H
#define LLVM_LIBC_SRC___SUPPORT_HASHTABLE_BITMASK_H

#include "src/__support/bit.h"
#include "src/__support/macros/properties/cpu_features.h"
#include <stddef.h> // size_t
#include <stdint.h> // uint8_t, uint64_t

namespace LIBC_NAMESPACE {
namespace internal {

// Implementations of the bitmask.
// The backend word type may vary depending on different microarchitectures.
// For example, with X86 SSE2, the bitmask is just the 16bit unsigned integer
// corresponding to lanes in a SIMD register.
//
// Notice that this implementation is simplified from traditional swisstable:
// since we do not support deletion, we only need to care about if the highest
// bit is set or not:
// =============================
// | Slot Status | Bitmask |
// =============================
// | Available | 0b1xxx'xxxx |
// | Occupied | 0b0xxx'xxxx |
// =============================
template <typename T, T WORD_MASK, size_t WORD_STRIDE> struct BitMaskAdaptor {
// A masked constant whose bits are all set.
LIBC_INLINE_VAR constexpr static T MASK = WORD_MASK;
// A stride in the bitmask may use multiple bits.
LIBC_INLINE_VAR constexpr static size_t STRIDE = WORD_STRIDE;

T word;

// Check if any bit is set inside the word.
LIBC_INLINE constexpr bool any_bit_set() const { return word != 0; }

// Count trailing zeros with respect to stride. (Assume the bitmask is none
// zero.)
LIBC_INLINE constexpr size_t lowest_set_bit_nonzero() const {
return unsafe_ctz<T>(word) / WORD_STRIDE;
}
};

// Not all bitmasks are iterable --- only those who has only MSB set in each
// lane. Hence, we make the types nomially different to distinguish them.
template <class BitMask> struct IteratableBitMaskAdaptor : public BitMask {
// Use the bitmask as an iterator. Update the state and return current lowest
// set bit. To make the bitmask iterable, each stride must contain 0 or exact
// 1 set bit.
LIBC_INLINE void remove_lowest_bit() {
// Remove the last set bit inside the word:
// word = 011110100 (original value)
// word - 1 = 011110011 (invert all bits up to the last set bit)
// word & (word - 1) = 011110000 (value with the last bit cleared)
this->word = this->word & (this->word - 1);
}
using value_type = size_t;
using iterator = BitMask;
using const_iterator = BitMask;
LIBC_INLINE size_t operator*() const {
return this->lowest_set_bit_nonzero();
}
LIBC_INLINE IteratableBitMaskAdaptor &operator++() {
this->remove_lowest_bit();
return *this;
}
LIBC_INLINE IteratableBitMaskAdaptor begin() { return *this; }
LIBC_INLINE IteratableBitMaskAdaptor end() { return {0}; }
LIBC_INLINE bool operator==(const IteratableBitMaskAdaptor &other) {
return this->word == other.word;
}
LIBC_INLINE bool operator!=(const IteratableBitMaskAdaptor &other) {
return this->word != other.word;
}
};

} // namespace internal
} // namespace LIBC_NAMESPACE

#if defined(LIBC_TARGET_CPU_HAS_SSE2)
#include "sse2/bitmask_impl.inc"
#else
#include "generic/bitmask_impl.inc"
#endif

#endif // LLVM_LIBC_SRC___SUPPORT_HASHTABLE_BITMASK_H
102 changes: 102 additions & 0 deletions libc/src/__support/HashTable/generic/bitmask_impl.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//===-- HashTable BitMasks Generic Implementation ---------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/endian.h"

namespace LIBC_NAMESPACE {
namespace internal {
// Helper function to spread a byte across the whole word.
// Accumutively, the procedure looks like:
// byte = 0x00000000000000ff
// byte | (byte << 8) = 0x000000000000ffff
// byte | (byte << 16) = 0x00000000ffffffff
// byte | (byte << 32) = 0xffffffffffffffff
LIBC_INLINE constexpr uintptr_t repeat_byte(uintptr_t byte) {
size_t shift_amount = 8;
while (shift_amount < sizeof(uintptr_t) * 8) {
byte |= byte << shift_amount;
shift_amount <<= 1;
}
return byte;
}

using BitMask = BitMaskAdaptor<uintptr_t, repeat_byte(0x80), 0x8ull>;
using IteratableBitMask = IteratableBitMaskAdaptor<BitMask>;

struct Group {
uintptr_t data;

// Load a group of control words from an arbitary address.
LIBC_INLINE static Group load(const void *__restrict addr) {
union {
uintptr_t value;
char bytes[sizeof(uintptr_t)];
} data;
for (size_t i = 0; i < sizeof(uintptr_t); ++i)
data.bytes[i] = static_cast<const char *>(addr)[i];
return {data.value};
}

// Find out the lanes equal to the given byte and return the bitmask
// with corresponding bits set.
LIBC_INLINE IteratableBitMask match_byte(uint8_t byte) const {
// Given byte = 0x10, suppose the data is:
//
// data = [ 0x10 | 0x10 | 0x00 | 0xF1 | ... ]
//
// First, we compare the byte using XOR operation:
//
// [ 0x10 | 0x10 | 0x10 | 0x10 | ... ] (0)
// ^ [ 0x10 | 0x10 | 0x00 | 0xF1 | ... ] (1)
// = [ 0x00 | 0x00 | 0x10 | 0xE1 | ... ] (2)
//
// Notice that the equal positions will now be 0x00, so if we substract 0x01
// respective to every byte, it will need to carry the substraction to upper
// bits (assume no carry from the hidden parts)
// [ 0x00 | 0x00 | 0x10 | 0xE1 | ... ] (2)
// - [ 0x01 | 0x01 | 0x01 | 0x01 | ... ] (3)
// = [ 0xFE | 0xFF | 0x0F | 0xE0 | ... ] (4)
//
// But there may be some bytes whose highest bit is already set after the
// xor operation. To rule out these positions, we AND them with the NOT
// of the XOR result:
//
// [ 0xFF | 0xFF | 0xEF | 0x1E | ... ] (5, NOT (2))
// & [ 0xFE | 0xFF | 0x0F | 0xE0 | ... ] (4)
// = [ 0xFE | 0xFF | 0x0F | 0x10 | ... ] (6)
//
// To make the bitmask iteratable, only one bit can be set in each stride.
// So we AND each byte with 0x80 and keep only the highest bit:
//
// [ 0xFE | 0xFF | 0x0F | 0x10 | ... ] (6)
// & [ 0x80 | 0x80 | 0x80 | 0x80 | ... ] (7)
// = [ 0x80 | 0x80 | 0x00 | 0x00 | ... ] (8)
//
// However, there are possitbilites for false positives. For example, if the
// data is [ 0x10 | 0x11 | 0x10 | 0xF1 | ... ]. This only happens when there
// is a key only differs from the searched by the lowest bit. The claims
// are:
//
// - This never happens for `EMPTY` and `DELETED`, only full entries.
// - The check for key equality will catch these.
// - This only happens if there is at least 1 true match.
// - The chance of this happening is very low (< 1% chance per byte).
auto cmp = data ^ repeat_byte(byte);
auto result = LIBC_NAMESPACE::Endian::to_little_endian(
(cmp - repeat_byte(0x01)) & ~cmp & repeat_byte(0x80));
return {result};
}

// Find out the lanes equal to EMPTY or DELETE (highest bit set) and
// return the bitmask with corresponding bits set.
LIBC_INLINE BitMask mask_available() const {
return {LIBC_NAMESPACE::Endian::to_little_endian(data) & repeat_byte(0x80)};
}
};
} // namespace internal
} // namespace LIBC_NAMESPACE
62 changes: 62 additions & 0 deletions libc/src/__support/HashTable/randomness.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===-- HashTable Randomness ------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC___SUPPORT_HASHTABLE_RANDOMNESS_H
#define LLVM_LIBC_SRC___SUPPORT_HASHTABLE_RANDOMNESS_H

#include "src/__support/common.h"
#include "src/__support/hash.h"
#include "src/__support/macros/attributes.h"
#if defined(LIBC_HASHTABLE_USE_GETRANDOM)
#include "src/errno/libc_errno.h"
#include "src/sys/random/getrandom.h"
#endif

namespace LIBC_NAMESPACE {
namespace internal {
namespace randomness {
// We need an initial state for the hash function. More entropy are to be added
// at the first use and each round of reseeding. The following random numbers
// are generated from https://www.random.org/cgi-bin/randbyte?nbytes=64&format=h
LIBC_INLINE_VAR thread_local HashState state = {
0x38049a7ea6f5a79b, 0x45cb02147c3f718a, 0x53eb431c12770718,
0x5b55742bd20a2fcb};
LIBC_INLINE_VAR thread_local uint64_t counter = 0;
LIBC_INLINE_VAR constexpr uint64_t RESEED_PERIOD = 1024;
LIBC_INLINE uint64_t next_random_seed() {
if (counter % RESEED_PERIOD == 0) {
uint64_t entropy[2];
entropy[0] = reinterpret_cast<uint64_t>(&entropy);
entropy[1] = reinterpret_cast<uint64_t>(&state);
#if defined(LIBC_HASHTABLE_USE_GETRANDOM)
int errno_backup = libc_errno;
ssize_t count = sizeof(entropy);
uint8_t *buffer = reinterpret_cast<uint8_t *>(entropy);
while (count > 0) {
ssize_t len = getrandom(buffer, count, 0);
if (len == -1) {
if (libc_errno == ENOSYS)
break;
continue;
}
count -= len;
buffer += len;
}
libc_errno = errno_backup;
#endif
state.update(&entropy, sizeof(entropy));
}
state.update(&counter, sizeof(counter));
counter++;
return state.finish();
}

} // namespace randomness
} // namespace internal
} // namespace LIBC_NAMESPACE
#endif // LLVM_LIBC_SRC___SUPPORT_HASHTABLE_RANDOMNESS_H
40 changes: 40 additions & 0 deletions libc/src/__support/HashTable/sse2/bitmask_impl.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===-- HashTable BitMasks SSE2 Implementation ------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include <immintrin.h>
namespace LIBC_NAMESPACE {
namespace internal {
// With SSE2, every bitmask is iteratable as
// we use single bit to encode the data.

using BitMask = BitMaskAdaptor<uint16_t, 0xffffu, 0x1u>;
using IteratableBitMask = IteratableBitMaskAdaptor<BitMask>;

struct Group {
__m128i data;

// Load a group of control words from an arbitary address.
LIBC_INLINE static Group load(const void *__restrict addr) {
return {_mm_loadu_si128(static_cast<const __m128i *>(addr))};
}

// Find out the lanes equal to the given byte and return the bitmask
// with corresponding bits set.
LIBC_INLINE IteratableBitMask match_byte(uint8_t byte) const {
auto cmp = _mm_cmpeq_epi8(data, _mm_set1_epi8(byte));
auto bitmask = static_cast<uint16_t>(_mm_movemask_epi8(cmp));
return {bitmask};
}

LIBC_INLINE BitMask mask_available() const {
auto bitmask = static_cast<uint16_t>(_mm_movemask_epi8(data));
return {bitmask};
}
};
} // namespace internal
} // namespace LIBC_NAMESPACE
235 changes: 235 additions & 0 deletions libc/src/__support/HashTable/table.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
//===-- Fix-sized Monotonic HashTable ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC___SUPPORT_HASHTABLE_table_H
#define LLVM_LIBC_SRC___SUPPORT_HASHTABLE_table_H

#include "include/llvm-libc-types/ENTRY.h"
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/HashTable/bitmask.h"
#include "src/__support/bit.h"
#include "src/__support/hash.h"
#include "src/__support/macros/attributes.h"
#include "src/__support/macros/optimization.h"
#include "src/__support/memory_size.h"
#include "src/string/memset.h"
#include "src/string/strcmp.h"
#include "src/string/strlen.h"
#include <stddef.h>
#include <stdint.h>

namespace LIBC_NAMESPACE {
namespace internal {

LIBC_INLINE uint8_t secondary_hash(uint64_t hash) {
// top 7 bits of the hash.
return static_cast<uint8_t>(hash >> 57);
}

// Probe sequence based on triangular numbers, which is guaranteed (since our
// table size is a power of two) to visit every group of elements exactly once.
//
// A triangular probe has us jump by 1 more group every time. So first we
// jump by 1 group (meaning we just continue our linear scan), then 2 groups
// (skipping over 1 group), then 3 groups (skipping over 2 groups), and so on.
//
// If we set sizeof(Group) to be one unit:
// T[k] = sum {1 + 2 + ... + k} = k * (k + 1) / 2
// It is provable that T[k] mod 2^m generates a permutation of
// 0, 1, 2, 3, ..., 2^m - 2, 2^m - 1
// Detailed proof is available at:
// https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/
struct ProbeSequence {
size_t position;
size_t stride;
size_t entries_mask;

LIBC_INLINE size_t next() {
position += stride;
position &= entries_mask;
stride += sizeof(Group);
return position;
}
};

// The number of entries is at least group width: we do not
// need to do the fixup when we set the control bytes.
// The number of entries is at least 8: we don't have to worry
// about special sizes when check the fullness of the table.
LIBC_INLINE size_t capacity_to_entries(size_t cap) {
if (8 >= sizeof(Group) && cap < 8)
return 8;
if (16 >= sizeof(Group) && cap < 15)
return 16;
if (cap < sizeof(Group))
cap = sizeof(Group);
// overflow is always checked in allocate()
return next_power_of_two(cap * 8 / 7);
}

// The heap memory layout for N buckets HashTable is as follows:
//
// =======================
// | N * Entry |
// ======================= <- align boundary
// | Header |
// =======================
// | (N + 1) * Byte |
// =======================
//
// The trailing group part is to make sure we can always load
// a whole group of control bytes.

struct HashTable {
HashState state;
size_t entries_mask; // number of buckets - 1
size_t available_slots; // less than capacity
private:
// How many entries are there in the table.
LIBC_INLINE size_t num_of_entries() const { return entries_mask + 1; }

LIBC_INLINE bool is_full() const { return available_slots == 0; }

LIBC_INLINE size_t offset_from_entries() const {
size_t entries_size = num_of_entries() * sizeof(ENTRY);
return entries_size + offset_to(entries_size, table_alignment());
}

LIBC_INLINE constexpr static size_t table_alignment() {
return alignof(HashTable) > alignof(ENTRY) ? alignof(HashTable)
: alignof(ENTRY);
}

LIBC_INLINE constexpr static size_t offset_to_groups() {
return sizeof(HashTable);
}

LIBC_INLINE ENTRY &entry(size_t i) {
return reinterpret_cast<ENTRY *>(this)[-i - 1];
}

LIBC_INLINE uint8_t &control(size_t i) {
uint8_t *ptr = reinterpret_cast<uint8_t *>(this) + offset_to_groups();
return ptr[i];
}

// We duplicate a group of control bytes to the end. Thus, it is possible that
// we need to set two control bytes at the same time.
LIBC_INLINE void set_ctrl(size_t index, uint8_t value) {
size_t index2 = ((index - sizeof(Group)) & entries_mask) + sizeof(Group);
control(index) = value;
control(index2) = value;
}

public:
LIBC_INLINE static void deallocate(HashTable *table) {
if (table) {
void *ptr =
reinterpret_cast<uint8_t *>(table) - table->offset_from_entries();
operator delete(ptr, std::align_val_t{table_alignment()});
}
}
LIBC_INLINE static HashTable *allocate(size_t capacity, uint64_t randomness) {
// check if capacity_to_entries overflows MAX_MEM_SIZE
if (capacity > size_t{1} << (8 * sizeof(size_t) - 1 - 3))
return nullptr;
SafeMemSize entries{capacity_to_entries(capacity)};
SafeMemSize entries_size = entries * SafeMemSize{sizeof(ENTRY)};
SafeMemSize align_boundary = entries_size.align_up(table_alignment());
SafeMemSize ctrl_sizes = entries + SafeMemSize{sizeof(Group)};
SafeMemSize header_size{offset_to_groups()};
SafeMemSize total_size =
(align_boundary + header_size + ctrl_sizes).align_up(table_alignment());
if (!total_size.valid())
return nullptr;
AllocChecker ac;

void *mem = operator new(total_size, std::align_val_t{table_alignment()},
ac);

HashTable *table = reinterpret_cast<HashTable *>(
static_cast<uint8_t *>(mem) + align_boundary);
if (ac) {
table->entries_mask = entries - 1u;
table->available_slots = entries / 8 * 7;
table->state = HashState{randomness};
memset(&table->control(0), 0x80, ctrl_sizes);
memset(mem, 0, table->offset_from_entries());
}
return table;
}

private:
LIBC_INLINE size_t find(const char *key, uint64_t primary) {
uint8_t secondary = secondary_hash(primary);
ProbeSequence sequence{static_cast<size_t>(primary), 0, entries_mask};
while (true) {
size_t pos = sequence.next();
Group ctrls = Group::load(&control(pos));
IteratableBitMask masks = ctrls.match_byte(secondary);
for (size_t i : masks) {
size_t index = (pos + i) & entries_mask;
ENTRY &entry = this->entry(index);
if (LIBC_LIKELY(entry.key != nullptr && strcmp(entry.key, key) == 0))
return index;
}
BitMask available = ctrls.mask_available();
// Since there is no deletion, the first time we find an available slot
// it is also ready to be used as an insertion point. Therefore, we also
// return the first available slot we find. If such entry is empty, the
// key will be nullptr.
if (LIBC_LIKELY(available.any_bit_set())) {
size_t index =
(pos + available.lowest_set_bit_nonzero()) & entries_mask;
return index;
}
}
}

private:
LIBC_INLINE ENTRY *insert(ENTRY item, uint64_t primary) {
auto index = find(item.key, primary);
auto slot = &this->entry(index);
// SVr4 and POSIX.1-2001 specify that action is significant only for
// unsuccessful searches, so that an ENTER should not do anything
// for a successful search.
if (slot->key != nullptr)
return slot;

if (!is_full()) {
set_ctrl(index, secondary_hash(primary));
slot->key = item.key;
slot->data = item.data;
available_slots--;
return slot;
}
return nullptr;
}

public:
LIBC_INLINE ENTRY *find(const char *key) {
LIBC_NAMESPACE::internal::HashState hasher = state;
hasher.update(key, strlen(key));
uint64_t primary = hasher.finish();
ENTRY &entry = this->entry(find(key, primary));
if (entry.key == nullptr)
return nullptr;
return &entry;
}
LIBC_INLINE ENTRY *insert(ENTRY item) {
LIBC_NAMESPACE::internal::HashState hasher = state;
hasher.update(item.key, strlen(item.key));
uint64_t primary = hasher.finish();
return insert(item, primary);
}
};
} // namespace internal
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC___SUPPORT_HASHTABLE_table_H
40 changes: 40 additions & 0 deletions libc/src/__support/bit.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_BIT_H
#define LLVM_LIBC_SRC___SUPPORT_BIT_H

#include "src/__support/CPP/type_traits.h" // make_unsigned
#include "src/__support/macros/attributes.h" // LIBC_INLINE

namespace LIBC_NAMESPACE {
Expand All @@ -28,6 +29,14 @@ template <typename T> LIBC_INLINE int constexpr correct_zero(T val, int bits) {
}

template <typename T> LIBC_INLINE constexpr int clz(T val);
template <> LIBC_INLINE int clz<unsigned char>(unsigned char val) {
return __builtin_clz(static_cast<unsigned int>(val)) -
8 * (sizeof(unsigned int) - sizeof(unsigned char));
}
template <> LIBC_INLINE int clz<unsigned short>(unsigned short val) {
return __builtin_clz(static_cast<unsigned int>(val)) -
8 * (sizeof(unsigned int) - sizeof(unsigned short));
}
template <> LIBC_INLINE int clz<unsigned int>(unsigned int val) {
return __builtin_clz(val);
}
Expand All @@ -42,6 +51,12 @@ clz<unsigned long long int>(unsigned long long int val) {
}

template <typename T> LIBC_INLINE constexpr int ctz(T val);
template <> LIBC_INLINE int ctz<unsigned char>(unsigned char val) {
return __builtin_ctz(static_cast<unsigned int>(val));
}
template <> LIBC_INLINE int ctz<unsigned short>(unsigned short val) {
return __builtin_ctz(static_cast<unsigned int>(val));
}
template <> LIBC_INLINE int ctz<unsigned int>(unsigned int val) {
return __builtin_ctz(val);
}
Expand Down Expand Up @@ -72,6 +87,31 @@ template <typename T> LIBC_INLINE constexpr int unsafe_clz(T val) {
return __internal::clz(val);
}

template <typename T> LIBC_INLINE constexpr T next_power_of_two(T val) {
if (val == 0)
return 1;
T idx = safe_clz(val - 1);
return static_cast<T>(1) << ((8ull * sizeof(T)) - idx);
}

template <typename T> LIBC_INLINE constexpr bool is_power_of_two(T val) {
return val != 0 && (val & (val - 1)) == 0;
}

template <typename T> LIBC_INLINE constexpr T offset_to(T val, T align) {
return (-val) & (align - 1);
}

template <typename T> LIBC_INLINE constexpr T rotate_left(T val, T amount) {
// Implementation taken from "Safe, Efficient, and Portable Rotate in C/C++"
// https://blog.regehr.org/archives/1063
// Using the safe version as the rotation pattern is now recognized by both
// GCC and Clang.
using U = cpp::make_unsigned_t<T>;
U v = static_cast<U>(val);
U a = static_cast<U>(amount);
return (v << a) | (v >> ((-a) & (sizeof(U) * 8 - 1)));
}
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC___SUPPORT_BIT_H
164 changes: 164 additions & 0 deletions libc/src/__support/hash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//===-- Portable string hash function ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC___SUPPORT_HASH_H
#define LLVM_LIBC_SRC___SUPPORT_HASH_H

#include "src/__support/UInt128.h" // UInt128
#include "src/__support/bit.h" // rotate_left
#include "src/__support/macros/attributes.h" // LIBC_INLINE
#include <stdint.h> // For uint64_t

namespace LIBC_NAMESPACE {
namespace internal {

// Folded multiplication.
// This function multiplies two 64-bit integers and xor the high and
// low 64-bit parts of the result.
LIBC_INLINE uint64_t folded_multiply(uint64_t x, uint64_t y) {
UInt128 p = static_cast<UInt128>(x) * static_cast<UInt128>(y);
uint64_t low = static_cast<uint64_t>(p);
uint64_t high = static_cast<uint64_t>(p >> 64);
return low ^ high;
}

// Read as little endian.
// Shift-and-or implementation does not give a satisfactory code on aarch64.
// Therefore, we use a union to read the value.
template <typename T> LIBC_INLINE T read_little_endian(const void *ptr) {
const uint8_t *bytes = static_cast<const uint8_t *>(ptr);
union {
T value;
uint8_t buffer[sizeof(T)];
} data;
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
// Compiler should able to optimize this as a load followed by a byte swap.
// On aarch64 (-mbig-endian), this compiles to the following for int:
// ldr w0, [x0]
// rev w0, w0
// ret
for (size_t i = 0; i < sizeof(T); ++i) {
data.buffer[i] = bytes[sizeof(T) - i - 1];
}
#else
for (size_t i = 0; i < sizeof(T); ++i) {
data.buffer[i] = bytes[i];
}
#endif
return data.value;
}

// Specialized read functions for small values. size must be <= 8.
LIBC_INLINE void read_small_values(const void *ptr, size_t size, uint64_t &low,
uint64_t &high) {
const uint8_t *bytes = static_cast<const uint8_t *>(ptr);
if (size >= 2) {
if (size >= 4) {
low = static_cast<uint64_t>(read_little_endian<uint32_t>(&bytes[0]));
high =
static_cast<uint64_t>(read_little_endian<uint32_t>(&bytes[size - 4]));
} else {
low = static_cast<uint64_t>(read_little_endian<uint16_t>(&bytes[0]));
high = static_cast<uint64_t>(bytes[size - 1]);
}
} else {
if (size > 0) {
low = static_cast<uint64_t>(bytes[0]);
high = static_cast<uint64_t>(bytes[0]);
} else {
low = 0;
high = 0;
}
}
}

// This constant comes from Kunth's prng (it empirically works well).
LIBC_INLINE_VAR constexpr uint64_t MULTIPLE = 6364136223846793005;
// Rotation amount for mixing.
LIBC_INLINE_VAR constexpr uint64_t ROTATE = 23;

// Randomly generated values. For now, we use the same values as in aHash as
// they are widely tested.
// https://github.com/tkaitchuck/aHash/blob/9f6a2ad8b721fd28da8dc1d0b7996677b374357c/src/random_state.rs#L38
LIBC_INLINE_VAR constexpr uint64_t RANDOMNESS[2][4] = {
{0x243f6a8885a308d3, 0x13198a2e03707344, 0xa4093822299f31d0,
0x082efa98ec4e6c89},
{0x452821e638d01377, 0xbe5466cf34e90c6c, 0xc0ac29b7c97c50dd,
0x3f84d5b5b5470917},
};

// This is a portable string hasher. It is not cryptographically secure.
// The quality of the hash is good enough to pass all tests in SMHasher.
// The implementation is derived from the generic routine of aHash.
class HashState {
uint64_t buffer;
uint64_t pad;
uint64_t extra_keys[2];
LIBC_INLINE void update(uint64_t low, uint64_t high) {
uint64_t combined =
folded_multiply(low ^ extra_keys[0], high ^ extra_keys[1]);
buffer = (buffer + pad) ^ combined;
buffer = rotate_left(buffer, ROTATE);
}
LIBC_INLINE static uint64_t mix(uint64_t seed) {
HashState mixer{RANDOMNESS[0][0], RANDOMNESS[0][1], RANDOMNESS[0][2],
RANDOMNESS[0][3]};
mixer.update(seed, 0);
return mixer.finish();
}

public:
LIBC_INLINE constexpr HashState(uint64_t a, uint64_t b, uint64_t c,
uint64_t d)
: buffer(a), pad(b), extra_keys{c, d} {}
LIBC_INLINE HashState(uint64_t seed) {
// Mix one more round of the seed to make it stronger.
uint64_t mixed = mix(seed);
buffer = RANDOMNESS[1][0] ^ mixed;
pad = RANDOMNESS[1][1] ^ mixed;
extra_keys[0] = RANDOMNESS[1][2] ^ mixed;
extra_keys[1] = RANDOMNESS[1][3] ^ mixed;
}
LIBC_INLINE void update(const void *ptr, size_t size) {
uint8_t const *bytes = static_cast<const uint8_t *>(ptr);
buffer = (buffer + size) * MULTIPLE;
uint64_t low, high;
if (size > 8) {
if (size > 16) {
// update tail
low = read_little_endian<uint64_t>(&bytes[size - 16]);
high = read_little_endian<uint64_t>(&bytes[size - 8]);
update(low, high);
while (size > 16) {
low = read_little_endian<uint64_t>(&bytes[0]);
high = read_little_endian<uint64_t>(&bytes[8]);
update(low, high);
bytes += 16;
size -= 16;
}
} else {
low = read_little_endian<uint64_t>(&bytes[0]);
high = read_little_endian<uint64_t>(&bytes[size - 8]);
update(low, high);
}
} else {
read_small_values(ptr, size, low, high);
update(low, high);
}
}
LIBC_INLINE uint64_t finish() {
uint64_t rot = buffer & 63;
uint64_t folded = folded_multiply(buffer, pad);
return rotate_left(folded, rot);
}
};

} // namespace internal
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC___SUPPORT_HASH_H
72 changes: 72 additions & 0 deletions libc/src/__support/memory_size.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===-- Memory Size ---------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/CPP/limits.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/bit.h"
#include "src/__support/macros/attributes.h"
#include "src/__support/macros/optimization.h"

namespace LIBC_NAMESPACE {
namespace internal {
template <class T> LIBC_INLINE bool mul_overflow(T a, T b, T *res) {
#if LIBC_HAS_BUILTIN(__builtin_mul_overflow)
return __builtin_mul_overflow(a, b, res);
#else
T max = cpp::numeric_limits<T>::max();
T min = cpp::numeric_limits<T>::min();
bool overflow = (b > 0 && (a > max / b || a < min / b)) ||
(b < 0 && (a < max / b || a > min / b));
if (!overflow)
*res = a * b;
return overflow;
#endif
}
// Limit memory size to the max of ssize_t
class SafeMemSize {
private:
using type = cpp::make_signed_t<size_t>;
type value;
LIBC_INLINE explicit SafeMemSize(type value) : value(value) {}

public:
LIBC_INLINE_VAR static constexpr size_t MAX_MEM_SIZE =
static_cast<size_t>(cpp::numeric_limits<type>::max());
LIBC_INLINE explicit SafeMemSize(size_t value)
: value(value <= MAX_MEM_SIZE ? static_cast<type>(value) : -1) {}
LIBC_INLINE operator size_t() { return static_cast<size_t>(value); }
LIBC_INLINE bool valid() { return value >= 0; }
LIBC_INLINE SafeMemSize operator+(const SafeMemSize &other) {
type result;
if (LIBC_UNLIKELY((value | other.value) < 0))
result = -1;
result = value + other.value;
return SafeMemSize{result};
}
LIBC_INLINE SafeMemSize operator*(const SafeMemSize &other) {
type result;
if (LIBC_UNLIKELY((value | other.value) < 0))
result = -1;
if (LIBC_UNLIKELY(mul_overflow(value, other.value, &result)))
result = -1;
return SafeMemSize{result};
}
LIBC_INLINE SafeMemSize align_up(size_t alignment) {
if (!is_power_of_two(alignment) || alignment > MAX_MEM_SIZE || !valid())
return SafeMemSize{type{-1}};

type offset = LIBC_NAMESPACE::offset_to<size_t>(value, alignment);

if (LIBC_UNLIKELY(offset > static_cast<type>(MAX_MEM_SIZE) - value))
return SafeMemSize{type{-1}};

return SafeMemSize{value + offset};
}
};
} // namespace internal
} // namespace LIBC_NAMESPACE
79 changes: 79 additions & 0 deletions libc/src/search/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
add_subdirectory(hsearch)

add_entrypoint_object(
hcreate
SRCS
hcreate.cpp
HDRS
hcreate.h
DEPENDS
libc.src.search.hsearch.global
libc.src.__support.HashTable.table
libc.src.__support.HashTable.randomness
libc.src.errno.errno
libc.include.search
)

add_entrypoint_object(
hcreate_r
SRCS
hcreate_r.cpp
HDRS
hcreate_r.h
DEPENDS
libc.src.__support.HashTable.table
libc.src.__support.HashTable.randomness
libc.src.errno.errno
libc.include.search
)

add_entrypoint_object(
hsearch
SRCS
hsearch.cpp
HDRS
hsearch.h
DEPENDS
libc.src.search.hsearch.global
libc.src.__support.HashTable.table
libc.src.__support.libc_assert
libc.src.errno.errno
libc.include.search
)

add_entrypoint_object(
hsearch_r
SRCS
hsearch_r.cpp
HDRS
hsearch_r.h
DEPENDS
libc.src.__support.HashTable.table
libc.src.errno.errno
libc.include.search
)

add_entrypoint_object(
hdestroy
SRCS
hdestroy.cpp
HDRS
hdestroy.h
DEPENDS
libc.src.search.hsearch.global
libc.src.__support.HashTable.table
libc.src.__support.libc_assert
libc.include.search
)

add_entrypoint_object(
hdestroy_r
SRCS
hdestroy_r.cpp
HDRS
hdestroy_r.h
DEPENDS
libc.src.errno.errno
libc.src.__support.HashTable.table
libc.include.search
)
28 changes: 28 additions & 0 deletions libc/src/search/hcreate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===-- Implementation of hcreate -------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/search/hcreate.h"
#include "src/__support/HashTable/randomness.h"
#include "src/__support/HashTable/table.h"
#include "src/errno/libc_errno.h"
#include "src/search/hsearch/global.h"

namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(int, hcreate, (size_t capacity)) {
uint64_t randomness = internal::randomness::next_random_seed();
internal::HashTable *table =
internal::HashTable::allocate(capacity, randomness);
if (table == nullptr) {
libc_errno = ENOMEM;
return 0;
}
internal::global_hash_table = table;
return 1;
}

} // namespace LIBC_NAMESPACE
18 changes: 18 additions & 0 deletions libc/src/search/hcreate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for hcreate -----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_SEARCH_HCREATE_H
#define LLVM_LIBC_SRC_SEARCH_HCREATE_H

#include <search.h>

namespace LIBC_NAMESPACE {
int hcreate(size_t capacity);
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_SEARCH_HCREATE_H
32 changes: 32 additions & 0 deletions libc/src/search/hcreate_r.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//===-- Implementation of hcreate_r -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/search/hcreate_r.h"
#include "src/__support/HashTable/randomness.h"
#include "src/__support/HashTable/table.h"
#include "src/errno/libc_errno.h"

namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(int, hcreate_r,
(size_t capacity, struct hsearch_data *htab)) {
if (htab == nullptr) {
libc_errno = EINVAL;
return 0;
}
uint64_t randomness = internal::randomness::next_random_seed();
internal::HashTable *table =
internal::HashTable::allocate(capacity, randomness);
if (table == nullptr) {
libc_errno = ENOMEM;
return 0;
}
htab->__opaque = table;
return 1;
}

} // namespace LIBC_NAMESPACE
18 changes: 18 additions & 0 deletions libc/src/search/hcreate_r.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for hcreate_r ---------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_SEARCH_HCREATE_R_H
#define LLVM_LIBC_SRC_SEARCH_HCREATE_R_H

#include <search.h>

namespace LIBC_NAMESPACE {
int hcreate_r(size_t capacity, struct hsearch_data *htab);
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_SEARCH_HCREATE_R_H
21 changes: 21 additions & 0 deletions libc/src/search/hdestroy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation of hdestroy ------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/search/hdestroy.h"
#include "src/__support/HashTable/table.h"
#include "src/__support/libc_assert.h"
#include "src/search/hsearch/global.h"

namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(void, hdestroy, (void)) {
LIBC_ASSERT(internal::global_hash_table != nullptr);
internal::HashTable::deallocate(internal::global_hash_table);
internal::global_hash_table = nullptr;
}

} // namespace LIBC_NAMESPACE
18 changes: 18 additions & 0 deletions libc/src/search/hdestroy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for hdestroy -----------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_SEARCH_HDESTROY_H
#define LLVM_LIBC_SRC_SEARCH_HDESTROY_H

#include <search.h>

namespace LIBC_NAMESPACE {
void hdestroy(void);
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_SEARCH_HDESTROY_H
25 changes: 25 additions & 0 deletions libc/src/search/hdestroy_r.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===-- Implementation of hdestroy_r ----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/search/hdestroy_r.h"
#include "src/__support/HashTable/table.h"
#include "src/errno/libc_errno.h"

namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(void, hdestroy_r, (struct hsearch_data * htab)) {
if (htab == nullptr) {
libc_errno = EINVAL;
return;
}
internal::HashTable *table =
static_cast<internal::HashTable *>(htab->__opaque);
internal::HashTable::deallocate(table);
htab->__opaque = nullptr;
}

} // namespace LIBC_NAMESPACE
18 changes: 18 additions & 0 deletions libc/src/search/hdestroy_r.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for hdestroy_r ---------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_SEARCH_HDESTROY_R_H
#define LLVM_LIBC_SRC_SEARCH_HDESTROY_R_H

#include <search.h>

namespace LIBC_NAMESPACE {
void hdestroy_r(struct hsearch_data *htab);
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_SEARCH_HDESTROY_R_H
36 changes: 36 additions & 0 deletions libc/src/search/hsearch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===-- Implementation of hsearch -------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/search/hsearch.h"
#include "src/__support/HashTable/table.h"
#include "src/__support/libc_assert.h"
#include "src/errno/libc_errno.h"
#include "src/search/hsearch/global.h"

namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(ENTRY *, hsearch, (ENTRY item, ACTION action)) {
ENTRY *result;
LIBC_ASSERT(internal::global_hash_table != nullptr);
switch (action) {
case FIND:
result = internal::global_hash_table->find(item.key);
if (result == nullptr) {
libc_errno = ESRCH;
}
break;
case ENTER:
result = internal::global_hash_table->insert(item);
if (result == nullptr) {
libc_errno = ENOMEM;
}
break;
}
return result;
}

} // namespace LIBC_NAMESPACE
18 changes: 18 additions & 0 deletions libc/src/search/hsearch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for hsearch -----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_SEARCH_HSEARCH_H
#define LLVM_LIBC_SRC_SEARCH_HSEARCH_H

#include <search.h> // ENTRY, ACTION

namespace LIBC_NAMESPACE {
ENTRY *hsearch(ENTRY item, ACTION action);
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_SEARCH_HSEARCH_H
7 changes: 7 additions & 0 deletions libc/src/search/hsearch/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
add_object_library(
global
SRCS
global.cpp
HDRS
global.h
)
13 changes: 13 additions & 0 deletions libc/src/search/hsearch/global.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//===-- Global hashtable implementation -----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

namespace LIBC_NAMESPACE {
namespace internal {
struct HashTable *global_hash_table = nullptr;
}
} // namespace LIBC_NAMESPACE
13 changes: 13 additions & 0 deletions libc/src/search/hsearch/global.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//===-- Global hashtable header -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

namespace LIBC_NAMESPACE {
namespace internal {
extern struct HashTable *global_hash_table;
}
} // namespace LIBC_NAMESPACE
42 changes: 42 additions & 0 deletions libc/src/search/hsearch_r.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===-- Implementation of hsearch_r -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/search/hsearch_r.h"
#include "src/__support/HashTable/table.h"
#include "src/errno/libc_errno.h"

namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(int, hsearch_r,
(ENTRY item, ACTION action, ENTRY **retval,
struct hsearch_data *htab)) {
if (htab == nullptr) {
libc_errno = EINVAL;
return 0;
}
internal::HashTable *table =
static_cast<internal::HashTable *>(htab->__opaque);
switch (action) {
case FIND:
*retval = table->find(item.key);
if (*retval == nullptr) {
libc_errno = ESRCH;
return 0;
}
break;
case ENTER:
*retval = table->insert(item);
if (*retval == nullptr) {
libc_errno = ENOMEM;
return 0;
}
break;
}
return 1;
}

} // namespace LIBC_NAMESPACE
19 changes: 19 additions & 0 deletions libc/src/search/hsearch_r.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===-- Implementation header for hsearch_r ---------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_SEARCH_HSEARCH_R_H
#define LLVM_LIBC_SRC_SEARCH_HSEARCH_R_H

#include <search.h> // ENTRY, ACTION

namespace LIBC_NAMESPACE {
int hsearch_r(ENTRY item, ACTION action, ENTRY **retval,
struct hsearch_data *htab);
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_SEARCH_HSEARCH_R_H
1 change: 1 addition & 0 deletions libc/test/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ add_subdirectory(stdlib)
add_subdirectory(inttypes)
add_subdirectory(stdio)
add_subdirectory(wchar)
add_subdirectory(search)

if(${LIBC_TARGET_OS} STREQUAL "linux")
add_subdirectory(fcntl)
Expand Down
27 changes: 27 additions & 0 deletions libc/test/src/__support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,32 @@ add_libc_test(
libc.src.__support.char_vector
)

add_libc_test(
hash_test
SUITE
libc-support-tests
SRCS
hash_test.cpp
DEPENDS
libc.src.__support.hash
libc.src.__support.CPP.new
libc.src.stdlib.rand
libc.src.stdlib.srand
libc.src.string.memset
UNIT_TEST_ONLY
# Aligned Allocation is not supported in hermetic builds.
)

add_libc_test(
memory_size_test
SUITE
libc-support-tests
SRCS
memory_size_test.cpp
DEPENDS
libc.src.__support.memory_size
)

add_executable(
libc_str_to_float_comparison_test
str_to_float_comparison_test.cpp
Expand Down Expand Up @@ -155,3 +181,4 @@ add_subdirectory(File)
add_subdirectory(RPC)
add_subdirectory(OSUtil)
add_subdirectory(FPUtil)
add_subdirectory(HashTable)
33 changes: 33 additions & 0 deletions libc/test/src/__support/HashTable/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
add_libc_test(
bitmask_test
SUITE
libc-support-tests
SRCS
bitmask_test.cpp
DEPENDS
libc.src.__support.HashTable.bitmask
)

add_libc_test(
table_test
SUITE
libc-support-tests
SRCS
table_test.cpp
DEPENDS
libc.src.__support.HashTable.randomness
libc.src.__support.HashTable.table
libc.src.__support.common
UNIT_TEST_ONLY
)

add_libc_test(
group_test
SUITE
libc-support-tests
SRCS
group_test.cpp
DEPENDS
libc.src.__support.HashTable.bitmask
libc.src.stdlib.rand
)
69 changes: 69 additions & 0 deletions libc/test/src/__support/HashTable/bitmask_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===-- Unittests for bitmask ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/HashTable/bitmask.h"
#include "test/UnitTest/Test.h"
namespace LIBC_NAMESPACE {
namespace internal {

using ShortBitMask = BitMaskAdaptor<uint16_t, 0xffff, 1>;
using LargeBitMask = BitMaskAdaptor<uint64_t, 0x80'80'80'80'80'80'80'80, 8>;

TEST(LlvmLibcHashTableBitMaskTest, SingleBitStrideLowestSetBit) {
uint16_t data = 0xffff;
for (size_t i = 0; i < 16; ++i) {
if (ShortBitMask{data}.any_bit_set()) {
ASSERT_EQ(ShortBitMask{data}.lowest_set_bit_nonzero(), i);
data <<= 1;
}
}
}

TEST(LlvmLibcHashTableBitMaskTest, MultiBitStrideLowestSetBit) {
uint64_t data = 0xffff'ffff'ffff'ffff;
for (size_t i = 0; i < 8; ++i) {
for (size_t j = 0; j < 8; ++j) {
if (LargeBitMask{data}.any_bit_set()) {
ASSERT_EQ(LargeBitMask{data}.lowest_set_bit_nonzero(), i);
data <<= 1;
}
}
}
}

TEST(LlvmLibcHashTableBitMaskTest, SingleBitStrideIteration) {
using Iter = IteratableBitMaskAdaptor<ShortBitMask>;
uint16_t data = 0xffff;
for (size_t i = 0; i < 16; ++i) {
Iter iter = {data};
size_t j = i;
for (auto x : iter) {
ASSERT_EQ(x, j);
j++;
}
ASSERT_EQ(j, size_t{16});
data <<= 1;
}
}

TEST(LlvmLibcHashTableBitMaskTest, MultiBitStrideIteration) {
using Iter = IteratableBitMaskAdaptor<LargeBitMask>;
uint64_t data = Iter::MASK;
for (size_t i = 0; i < 8; ++i) {
Iter iter = {data};
size_t j = i;
for (auto x : iter) {
ASSERT_EQ(x, j);
j++;
}
ASSERT_EQ(j, size_t{8});
data <<= Iter::STRIDE;
}
}
} // namespace internal
} // namespace LIBC_NAMESPACE
90 changes: 90 additions & 0 deletions libc/test/src/__support/HashTable/group_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//===-- Unittests for control group ---------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/HashTable/bitmask.h"

#include "src/stdlib/rand.h"
#include "test/UnitTest/Test.h"
#include <stdint.h>

namespace LIBC_NAMESPACE {
namespace internal {

struct ByteArray {
alignas(Group) uint8_t data[sizeof(Group) + 1]{};
};

TEST(LlvmLibcHashTableBitMaskTest, Match) {
// Any pair of targets have bit differences not only at the lowest bit.
// No False positive.
uint8_t targets[4] = {0x00, 0x11, 0xFF, 0x0F};
size_t count[4] = {0, 0, 0, 0};
size_t appearance[4][sizeof(Group)];
ByteArray array{};

union {
uintptr_t random;
int data[sizeof(uintptr_t) / sizeof(int)];
};

for (int &i : data)
i = rand();

for (size_t i = 0; i < sizeof(Group); ++i) {
size_t choice = random % 4;
random /= 4;
array.data[i] = targets[choice];
appearance[choice][count[choice]++] = i;
}

for (size_t t = 0; t < sizeof(targets); ++t) {
auto bitmask = Group::load(array.data).match_byte(targets[t]);
for (size_t i = 0; i < count[t]; ++i) {
size_t iterated = 0;
for (size_t position : bitmask) {
ASSERT_EQ(appearance[t][iterated], position);
iterated++;
}
ASSERT_EQ(count[t], iterated);
}
}
}

TEST(LlvmLibcHashTableBitMaskTest, MaskAvailable) {
uint8_t values[3] = {0x00, 0x0F, 0x80};

for (size_t i = 0; i < sizeof(Group); ++i) {
ByteArray array{};

union {
uintptr_t random;
int data[sizeof(uintptr_t) / sizeof(int)];
};

for (int &j : data)
j = rand();

ASSERT_FALSE(Group::load(array.data).mask_available().any_bit_set());

array.data[i] = 0x80;
for (size_t j = 0; j < sizeof(Group); ++j) {
if (i == j)
continue;
size_t sample_space = 2 + (j > i);
size_t choice = random % sample_space;
random /= sizeof(values);
array.data[j] = values[choice];
}

auto mask = Group::load(array.data).mask_available();
ASSERT_TRUE(mask.any_bit_set());
ASSERT_EQ(mask.lowest_set_bit_nonzero(), i);
}
}
} // namespace internal
} // namespace LIBC_NAMESPACE
77 changes: 77 additions & 0 deletions libc/test/src/__support/HashTable/table_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//===-- Unittests for table -----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/HashTable/randomness.h"
#include "src/__support/HashTable/table.h"
#include "test/UnitTest/Test.h"

namespace LIBC_NAMESPACE {
namespace internal {
TEST(LlvmLibcTableTest, AllocationAndDeallocation) {
size_t caps[] = {0, 1, 2, 3, 4, 7, 11, 37, 1024, 5261, 19999};
const char *keys[] = {"", "a", "ab", "abc",
"abcd", "abcde", "abcdef", "abcdefg",
"abcdefgh", "abcdefghi", "abcdefghij"};
for (size_t i : caps) {
HashTable *table = HashTable::allocate(i, 1);
ASSERT_NE(table, static_cast<HashTable *>(nullptr));
for (const char *key : keys) {
ASSERT_EQ(table->find(key), static_cast<ENTRY *>(nullptr));
}
HashTable::deallocate(table);
}
ASSERT_EQ(HashTable::allocate(-1, 0), static_cast<HashTable *>(nullptr));
HashTable::deallocate(nullptr);
}

TEST(LlvmLibcTableTest, Insertion) {
union key {
uint64_t value;
char bytes[8];
} keys[256];
for (size_t k = 0; k < 256; ++k) {
keys[k].value = LIBC_NAMESPACE::Endian::to_little_endian(k);
}
constexpr size_t CAP = next_power_of_two((sizeof(Group) + 1) * 8 / 7) / 8 * 7;
static_assert(CAP + 1 < 256, "CAP is too large for this test.");
HashTable *table =
HashTable::allocate(sizeof(Group) + 1, randomness::next_random_seed());
ASSERT_NE(table, static_cast<HashTable *>(nullptr));

// insert to full capacity.
for (size_t i = 0; i < CAP; ++i) {
ASSERT_NE(table->insert({keys[i].bytes, keys[i].bytes}),
static_cast<ENTRY *>(nullptr));
}

// one more insert should fail.
ASSERT_EQ(table->insert({keys[CAP + 1].bytes, keys[CAP + 1].bytes}),
static_cast<ENTRY *>(nullptr));

for (size_t i = 0; i < CAP; ++i) {
ASSERT_EQ(strcmp(table->find(keys[i].bytes)->key, keys[i].bytes), 0);
}
for (size_t i = CAP; i < 256; ++i) {
ASSERT_EQ(table->find(keys[i].bytes), static_cast<ENTRY *>(nullptr));
}

// do not replace old value
for (size_t i = 0; i < CAP; ++i) {
ASSERT_NE(table->insert({keys[i].bytes, reinterpret_cast<void *>(i)}),
static_cast<ENTRY *>(nullptr));
}
for (size_t i = 0; i < CAP; ++i) {
ASSERT_EQ(table->find(keys[i].bytes)->data,
reinterpret_cast<void *>(keys[i].bytes));
}

HashTable::deallocate(table);
}

} // namespace internal
} // namespace LIBC_NAMESPACE
49 changes: 49 additions & 0 deletions libc/test/src/__support/bit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,53 @@ TEST(LlvmLibcBlockBitTest, TODO) {
// TODO Implement me.
}

TEST(LlvmLibcBlockBitTest, OffsetTo) {
ASSERT_EQ(offset_to(0, 512), 0);
ASSERT_EQ(offset_to(1, 512), 511);
ASSERT_EQ(offset_to(2, 512), 510);
ASSERT_EQ(offset_to(13, 1), 0);
ASSERT_EQ(offset_to(13, 4), 3);
for (unsigned int i = 0; i < 31; ++i) {
ASSERT_EQ((offset_to(i, 1u << i) + i) % (1u << i), 0u);
}
}

TEST(LlvmLibcBlockBitTest, RotateLeft) {
{
unsigned current = 1;
for (unsigned i = 0; i < 8 * sizeof(unsigned); ++i) {
ASSERT_EQ(1u << i, current);
ASSERT_EQ(current, rotate_left(1u, i));
current = rotate_left(current, 1u);
}
ASSERT_EQ(current, 1u);
}
{
int current = 1;
for (int i = 0; i < 8 * static_cast<int>(sizeof(int)); ++i) {
ASSERT_EQ(1 << i, current);
ASSERT_EQ(current, rotate_left(1, i));
current = rotate_left(current, 1);
}
ASSERT_EQ(current, 1);
}
}

TEST(LlvmLibcBlockBitTest, NextPowerOfTwo) {
ASSERT_EQ(1u, next_power_of_two(0u));
for (unsigned int i = 0; i < 31; ++i) {
ASSERT_EQ(1u << (i + 1), next_power_of_two((1u << i) + 1));
ASSERT_EQ(1u << i, next_power_of_two(1u << i));
}
}

TEST(LlvmLibcBlockBitTest, IsPowerOfTwo) {
ASSERT_FALSE(is_power_of_two(0u));
ASSERT_TRUE(is_power_of_two(1u));
for (unsigned int i = 1; i < 31; ++i) {
ASSERT_TRUE(is_power_of_two(1u << i));
ASSERT_FALSE(is_power_of_two((1u << i) + 1));
}
}

} // namespace LIBC_NAMESPACE
140 changes: 140 additions & 0 deletions libc/test/src/__support/hash_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//===-- Unittests for hash ------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/CPP/new.h"
#include "src/__support/hash.h"
#include "src/stdlib/rand.h"
#include "src/stdlib/srand.h"
#include "src/string/memset.h"
#include "test/UnitTest/Test.h"

template <class T> struct AlignedMemory {
T *data;
size_t offset;
std::align_val_t alignment;
AlignedMemory(size_t size, size_t alignment, size_t offset)
: offset(offset), alignment{alignment} {
size_t sz = size * sizeof(T);
size_t aligned = sz + ((-sz) & (alignment - 1)) + alignment;
LIBC_NAMESPACE::AllocChecker ac;
data = static_cast<T *>(operator new(aligned, this->alignment, ac));
data += offset % alignment;
}
~AlignedMemory() { operator delete(data - offset, alignment); }
};

size_t sizes[] = {0, 1, 23, 59, 1024, 5261};
char values[] = {0, 1, 23, 59, 102, -1};

// Hash value should not change with different alignments.
TEST(LlvmLibcHashTest, SanityCheck) {
for (size_t sz : sizes) {
for (uint8_t val : values) {
uint64_t hash;
{
AlignedMemory<char> mem(sz, 64, 0);
LIBC_NAMESPACE::memset(mem.data, val, sz);
LIBC_NAMESPACE::internal::HashState state{0x1234567890abcdef};
state.update(mem.data, sz);
hash = state.finish();
}
for (size_t offset = 1; offset < 64; ++offset) {
AlignedMemory<char> mem(sz, 64, offset);
LIBC_NAMESPACE::memset(mem.data, val, sz);
LIBC_NAMESPACE::internal::HashState state{0x1234567890abcdef};
state.update(mem.data, sz);
ASSERT_EQ(hash, state.finish());
}
}
}
}

static inline size_t popcnt(uint64_t x) {
size_t count = 0;
while (x) {
count += x & 1;
x >>= 1;
}
return count;
}

// Mutate a single bit in a rather large input. The hash should change
// significantly. At least one fifth of the bits should not match.
TEST(LlvmLibcHashTest, Avalanche) {
for (size_t sz : sizes) {
for (uint8_t val : values) {
uint64_t hash;
AlignedMemory<char> mem(sz, 64, 0);
LIBC_NAMESPACE::memset(mem.data, val, sz);
{
LIBC_NAMESPACE::internal::HashState state{0xabcdef1234567890};
state.update(mem.data, sz);
hash = state.finish();
}
for (size_t i = 0; i < sz; ++i) {
for (size_t j = 0; j < 8; ++j) {
uint8_t mask = 1 << j;
mem.data[i] ^= mask;
{
LIBC_NAMESPACE::internal::HashState state{0xabcdef1234567890};
state.update(mem.data, sz);
uint64_t new_hash = state.finish();
ASSERT_GE(popcnt(hash ^ new_hash), size_t{13});
}
mem.data[i] ^= mask;
}
}
}
}
}

// Hash a random sequence of input. The LSB should be uniform enough such that
// values spread across the entire range.
TEST(LlvmLibcHashTest, UniformLSB) {
LIBC_NAMESPACE::srand(0xffffffff);
for (size_t sz : sizes) {
AlignedMemory<size_t> counters(sz, sizeof(size_t), 0);
LIBC_NAMESPACE::memset(counters.data, 0, sz * sizeof(size_t));
for (size_t i = 0; i < 200 * sz; ++i) {
int randomness[8] = {LIBC_NAMESPACE::rand(), LIBC_NAMESPACE::rand(),
LIBC_NAMESPACE::rand(), LIBC_NAMESPACE::rand(),
LIBC_NAMESPACE::rand(), LIBC_NAMESPACE::rand(),
LIBC_NAMESPACE::rand(), LIBC_NAMESPACE::rand()};
{
LIBC_NAMESPACE::internal::HashState state{0x1a2b3c4d5e6f7a8b};
state.update(randomness, sizeof(randomness));
uint64_t hash = state.finish();
counters.data[hash % sz]++;
}
}
for (size_t i = 0; i < sz; ++i) {
ASSERT_GE(counters.data[i], size_t{140});
ASSERT_LE(counters.data[i], size_t{260});
}
}
}

// Hash a low entropy sequence. The MSB should be uniform enough such that
// there is no significant bias even if the value range is small.
// Top 7 bits is examined because it will be used as a secondary key in
// the hash table.
TEST(LlvmLibcHashTest, UniformMSB) {
size_t sz = 1 << 7;
AlignedMemory<size_t> counters(sz, sizeof(size_t), 0);
LIBC_NAMESPACE::memset(counters.data, 0, sz * sizeof(size_t));
for (size_t i = 0; i < 200 * sz; ++i) {
LIBC_NAMESPACE::internal::HashState state{0xa1b2c3d4e5f6a7b8};
state.update(&i, sizeof(i));
uint64_t hash = state.finish();
counters.data[hash >> 57]++;
}
for (size_t i = 0; i < sz; ++i) {
ASSERT_GE(counters.data[i], size_t{140});
ASSERT_LE(counters.data[i], size_t{260});
}
}
85 changes: 85 additions & 0 deletions libc/test/src/__support/memory_size_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//===-- Unittests for MemorySize ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/memory_size.h"
#include "test/UnitTest/Test.h"

namespace LIBC_NAMESPACE {
namespace internal {
static inline constexpr size_t SAFE_MEM_SIZE_TEST_LIMIT =
static_cast<size_t>(cpp::numeric_limits<cpp::make_signed_t<size_t>>::max());

TEST(LlvmLibcMemSizeTest, Constuction) {
ASSERT_FALSE(SafeMemSize{static_cast<size_t>(-1)}.valid());
ASSERT_FALSE(SafeMemSize{static_cast<size_t>(-2)}.valid());
ASSERT_FALSE(SafeMemSize{static_cast<size_t>(-1024 + 33)}.valid());
ASSERT_FALSE(SafeMemSize{static_cast<size_t>(-1024 + 66)}.valid());
ASSERT_FALSE(SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT + 1}.valid());
ASSERT_FALSE(SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT + 13}.valid());

ASSERT_TRUE(SafeMemSize{static_cast<size_t>(1)}.valid());
ASSERT_TRUE(SafeMemSize{static_cast<size_t>(1024 + 13)}.valid());
ASSERT_TRUE(SafeMemSize{static_cast<size_t>(2048 - 13)}.valid());
ASSERT_TRUE(SafeMemSize{static_cast<size_t>(4096 + 1)}.valid());
ASSERT_TRUE(SafeMemSize{static_cast<size_t>(8192 - 1)}.valid());
ASSERT_TRUE(SafeMemSize{static_cast<size_t>(16384 + 15)}.valid());
ASSERT_TRUE(SafeMemSize{static_cast<size_t>(32768 * 3)}.valid());
ASSERT_TRUE(SafeMemSize{static_cast<size_t>(65536 * 13)}.valid());
ASSERT_TRUE(SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT}.valid());
ASSERT_TRUE(SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT - 1}.valid());
ASSERT_TRUE(SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT - 13}.valid());
}

TEST(LlvmLibcMemSizeTest, Addition) {
auto max = SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT};
auto half = SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT / 2};
auto third = SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT / 3};

ASSERT_TRUE(half.valid());
ASSERT_TRUE(third.valid());
ASSERT_TRUE((half + half).valid());
ASSERT_TRUE((third + third + third).valid());
ASSERT_TRUE((half + third).valid());

ASSERT_FALSE((max + SafeMemSize{static_cast<size_t>(1)}).valid());
ASSERT_FALSE((third + third + third + third).valid());
ASSERT_FALSE((half + half + half).valid());
}

TEST(LlvmLibcMemSizeTest, Multiplication) {
auto max = SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT};
auto half = SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT / 2};
auto third = SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT / 3};

ASSERT_TRUE((max * SafeMemSize{static_cast<size_t>(1)}).valid());
ASSERT_TRUE((max * SafeMemSize{static_cast<size_t>(0)}).valid());

ASSERT_FALSE((max * SafeMemSize{static_cast<size_t>(2)}).valid());
ASSERT_FALSE((half * half).valid());
ASSERT_FALSE((half * SafeMemSize{static_cast<size_t>(3)}).valid());
ASSERT_FALSE((third * SafeMemSize{static_cast<size_t>(4)}).valid());
}

TEST(LlvmLibcMemSizeTest, AlignUp) {
size_t sizes[] = {
0, 1, 8, 13, 60, 97, 128, 1024, 5124, 5120,
};
for (size_t i = 2; i <= 16; ++i) {
size_t alignment = 1 << i;
for (size_t size : sizes) {
auto safe_size = SafeMemSize{size};
auto safe_aligned_size = safe_size.align_up(alignment);
ASSERT_TRUE(safe_aligned_size.valid());
ASSERT_EQ(static_cast<size_t>(safe_aligned_size) % alignment, size_t{0});
}
}
auto max = SafeMemSize{SAFE_MEM_SIZE_TEST_LIMIT};
ASSERT_FALSE(max.align_up(8).valid());
}
} // namespace internal
} // namespace LIBC_NAMESPACE
16 changes: 16 additions & 0 deletions libc/test/src/search/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
add_custom_target(libc_search_unittests)
add_libc_unittest(
hsearch_test
SUITE
libc_search_unittests
SRCS
hsearch_test.cpp
DEPENDS
libc.src.search.hsearch_r
libc.src.search.hcreate_r
libc.src.search.hdestroy_r
libc.src.search.hsearch
libc.src.search.hcreate
libc.src.search.hdestroy
libc.src.errno.errno
)
124 changes: 124 additions & 0 deletions libc/test/src/search/hsearch_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//===-- Unittests for hsearch ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/HashTable/table.h"
#include "src/__support/bit.h"
#include "src/search/hcreate.h"
#include "src/search/hcreate_r.h"
#include "src/search/hdestroy.h"
#include "src/search/hdestroy_r.h"
#include "src/search/hsearch.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/Test.h"
#include <asm-generic/errno-base.h>

TEST(LlvmLibcHsearchTest, CreateTooLarge) {
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
struct hsearch_data hdata;
ASSERT_THAT(LIBC_NAMESPACE::hcreate(-1), Fails(ENOMEM, 0));
ASSERT_THAT(LIBC_NAMESPACE::hcreate_r(-1, &hdata), Fails(ENOMEM, 0));
}

TEST(LlvmLibcHSearchTest, CreateInvalid) {
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
ASSERT_THAT(LIBC_NAMESPACE::hcreate_r(16, nullptr), Fails(EINVAL, 0));
}

TEST(LlvmLibcHSearchTest, CreateValid) {
struct hsearch_data hdata;
ASSERT_GT(LIBC_NAMESPACE::hcreate_r(1, &hdata), 0);
LIBC_NAMESPACE::hdestroy_r(&hdata);

ASSERT_GT(LIBC_NAMESPACE::hcreate(1), 0);
LIBC_NAMESPACE::hdestroy();
}

char search_data[] = "1234567890abcdefghijklmnopqrstuvwxyz"
"1234567890abcdefghijklmnopqrstuvwxyz"
"1234567890abcdefghijklmnopqrstuvwxyz"
"1234567890abcdefghijklmnopqrstuvwxyz"
"1234567890abcdefghijklmnopqrstuvwxyz";
char search_data2[] =
"@@@@@@@@@@@@@@!!!!!!!!!!!!!!!!!###########$$$$$$$$$$^^^^^^&&&&&&&&";

constexpr size_t GROUP_SIZE = sizeof(LIBC_NAMESPACE::internal::Group);
constexpr size_t CAP =
LIBC_NAMESPACE::next_power_of_two((GROUP_SIZE + 1) * 8 / 7) / 8 * 7;
static_assert(CAP < sizeof(search_data), "CAP too large");

TEST(LlvmLibcHSearchTest, InsertTooMany) {
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
ASSERT_GT(LIBC_NAMESPACE::hcreate(GROUP_SIZE + 1), 0);

for (size_t i = 0; i < CAP; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch({&search_data[i], nullptr}, ENTER)->key,
&search_data[i]);
}
ASSERT_THAT(static_cast<void *>(
LIBC_NAMESPACE::hsearch({search_data2, nullptr}, ENTER)),
Fails(ENOMEM, static_cast<void *>(nullptr)));
LIBC_NAMESPACE::hdestroy();
}

TEST(LlvmLibcHSearchTest, NotFound) {
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
ASSERT_GT(LIBC_NAMESPACE::hcreate(GROUP_SIZE + 1), 0);
ASSERT_THAT(static_cast<void *>(
LIBC_NAMESPACE::hsearch({search_data2, nullptr}, FIND)),
Fails(ESRCH, static_cast<void *>(nullptr)));
for (size_t i = 0; i < CAP; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch({&search_data[i], nullptr}, ENTER)->key,
&search_data[i]);
}
ASSERT_THAT(static_cast<void *>(
LIBC_NAMESPACE::hsearch({search_data2, nullptr}, FIND)),
Fails(ESRCH, static_cast<void *>(nullptr)));
LIBC_NAMESPACE::hdestroy();
}

TEST(LlvmLibcHSearchTest, Found) {
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
ASSERT_GT(LIBC_NAMESPACE::hcreate(GROUP_SIZE + 1), 0);
for (size_t i = 0; i < CAP; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch(
{&search_data[i], reinterpret_cast<void *>(i)}, ENTER)
->key,
&search_data[i]);
}
for (size_t i = 0; i < CAP; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch({&search_data[i], nullptr}, FIND)->data,
reinterpret_cast<void *>(i));
}
LIBC_NAMESPACE::hdestroy();
}

TEST(LlvmLibcHSearchTest, OnlyInsertWhenNotFound) {
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
ASSERT_GT(LIBC_NAMESPACE::hcreate(GROUP_SIZE + 1), 0);
for (size_t i = 0; i < CAP / 7 * 5; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch(
{&search_data[i], reinterpret_cast<void *>(i)}, ENTER)
->key,
&search_data[i]);
}
for (size_t i = 0; i < CAP; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch(
{&search_data[i], reinterpret_cast<void *>(1000 + i)}, ENTER)
->key,
&search_data[i]);
}
for (size_t i = 0; i < CAP / 7 * 5; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch({&search_data[i], nullptr}, FIND)->data,
reinterpret_cast<void *>(i));
}
for (size_t i = CAP / 7 * 5; i < CAP; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::hsearch({&search_data[i], nullptr}, FIND)->data,
reinterpret_cast<void *>(1000 + i));
}
LIBC_NAMESPACE::hdestroy();
}