[libc-commits] [libc] [libc][tsearch] add tsearch functions (PR #172625)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Wed Dec 17 19:11:06 PST 2025
https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/172625
>From 1588de4b6f0da428155f4175f23043c292fb6575 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Mon, 15 Dec 2025 03:09:11 -0500
Subject: [PATCH 1/2] [libc][tsearch] scaffold WAVL header
---
libc/src/__support/CMakeLists.txt | 9 +++++++++
libc/src/__support/weak_avl.h | 19 +++++++++++++++++++
2 files changed, 28 insertions(+)
create mode 100644 libc/src/__support/weak_avl.h
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index c7f127d6934a0..99a1641d629f3 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -391,6 +391,15 @@ add_header_library(
libc.src.__support.macros.attributes
)
+add_header_library(
+ weak_avl
+ HDRS
+ weak_avl.h
+ DEPENDS
+ libc.src.__support.CPP.new
+ libc.src.__support.macros.config
+)
+
add_subdirectory(FPUtil)
add_subdirectory(OSUtil)
add_subdirectory(StringUtil)
diff --git a/libc/src/__support/weak_avl.h b/libc/src/__support/weak_avl.h
new file mode 100644
index 0000000000000..817146e8b9ab9
--- /dev/null
+++ b/libc/src/__support/weak_avl.h
@@ -0,0 +1,19 @@
+//===-- Implementation header for weak AVL tree -----------------*- 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_WEAK_AVL_H
+#define LLVM_LIBC_SRC___SUPPORT_WEAK_AVL_H
+
+#include "src/__support/CPP/new.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_WEAK_AVL_H
>From ef1d782dd05a0a3c80fe86bdfd0f12dc9c187e81 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Wed, 17 Dec 2025 22:10:22 -0500
Subject: [PATCH 2/2] [libc][tsearch] add tsearch functions
---
libc/config/linux/aarch64/entrypoints.txt | 6 +
libc/config/linux/x86_64/entrypoints.txt | 6 +
libc/fuzzing/__support/CMakeLists.txt | 8 +
libc/fuzzing/__support/weak_avl_fuzz.cpp | 94 +++
libc/hdr/types/CMakeLists.txt | 18 +
libc/hdr/types/VISIT.h | 22 +
libc/hdr/types/posix_tnode.h | 28 +
libc/include/CMakeLists.txt | 4 +
libc/include/llvm-libc-types/CMakeLists.txt | 16 +
.../llvm-libc-types/__action_closure_fn_t.h | 17 +
libc/include/llvm-libc-types/__action_fn_t.h | 17 +
libc/include/llvm-libc-types/__free_fn_t.h | 14 +
libc/include/llvm-libc-types/posix_tnode.h | 17 +
libc/include/search.yaml | 48 ++
libc/src/__support/CMakeLists.txt | 5 +
libc/src/__support/weak_avl.h | 575 ++++++++++++++++++
libc/src/search/CMakeLists.txt | 68 +++
libc/src/search/tdelete.cpp | 36 ++
libc/src/search/tdelete.h | 20 +
libc/src/search/tdestroy.cpp | 28 +
libc/src/search/tdestroy.h | 19 +
libc/src/search/tfind.cpp | 27 +
libc/src/search/tfind.h | 20 +
libc/src/search/tsearch.cpp | 27 +
libc/src/search/tsearch.h | 20 +
libc/src/search/twalk.cpp | 34 ++
libc/src/search/twalk.h | 21 +
libc/src/search/twalk_r.cpp | 34 ++
libc/src/search/twalk_r.h | 22 +
libc/test/src/__support/CMakeLists.txt | 10 +
libc/test/src/__support/weak_avl_test.cpp | 291 +++++++++
libc/test/src/search/CMakeLists.txt | 15 +
libc/test/src/search/tsearch_test.cpp | 137 +++++
33 files changed, 1724 insertions(+)
create mode 100644 libc/fuzzing/__support/weak_avl_fuzz.cpp
create mode 100644 libc/hdr/types/VISIT.h
create mode 100644 libc/hdr/types/posix_tnode.h
create mode 100644 libc/include/llvm-libc-types/__action_closure_fn_t.h
create mode 100644 libc/include/llvm-libc-types/__action_fn_t.h
create mode 100644 libc/include/llvm-libc-types/__free_fn_t.h
create mode 100644 libc/include/llvm-libc-types/posix_tnode.h
create mode 100644 libc/src/search/tdelete.cpp
create mode 100644 libc/src/search/tdelete.h
create mode 100644 libc/src/search/tdestroy.cpp
create mode 100644 libc/src/search/tdestroy.h
create mode 100644 libc/src/search/tfind.cpp
create mode 100644 libc/src/search/tfind.h
create mode 100644 libc/src/search/tsearch.cpp
create mode 100644 libc/src/search/tsearch.h
create mode 100644 libc/src/search/twalk.cpp
create mode 100644 libc/src/search/twalk.h
create mode 100644 libc/src/search/twalk_r.cpp
create mode 100644 libc/src/search/twalk_r.h
create mode 100644 libc/test/src/__support/weak_avl_test.cpp
create mode 100644 libc/test/src/search/tsearch_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 970c825bbfc96..f2a634ea4afa1 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1116,6 +1116,12 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.search.lfind
libc.src.search.lsearch
libc.src.search.remque
+ libc.src.search.tdelete
+ libc.src.search.tdestroy
+ libc.src.search.tfind
+ libc.src.search.tsearch
+ libc.src.search.twalk
+ libc.src.search.twalk_r
# threads.h entrypoints
libc.src.threads.call_once
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 9399b284fa2da..a6ce8a3715d51 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1298,6 +1298,12 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.search.lfind
libc.src.search.lsearch
libc.src.search.remque
+ libc.src.search.tdelete
+ libc.src.search.tdestroy
+ libc.src.search.tfind
+ libc.src.search.tsearch
+ libc.src.search.twalk
+ libc.src.search.twalk_r
# threads.h entrypoints
libc.src.threads.call_once
diff --git a/libc/fuzzing/__support/CMakeLists.txt b/libc/fuzzing/__support/CMakeLists.txt
index 9c674d2fb0d65..f9b49cc1b0a4b 100644
--- a/libc/fuzzing/__support/CMakeLists.txt
+++ b/libc/fuzzing/__support/CMakeLists.txt
@@ -25,6 +25,14 @@ add_libc_fuzzer(
-D__LIBC_EXPLICIT_SIMD_OPT
)
+add_libc_fuzzer(
+ weak_avl_fuzz
+ SRCS
+ weak_avl_fuzz.cpp
+ DEPENDS
+ libc.src.__support.weak_avl
+)
+
# TODO: FreeListHeap uses the _end symbol which conflicts with the _end symbol
# defined by GPU start.cpp files so for now we exclude this fuzzer on GPU.
if(LLVM_LIBC_FULL_BUILD AND NOT LIBC_TARGET_OS_IS_GPU)
diff --git a/libc/fuzzing/__support/weak_avl_fuzz.cpp b/libc/fuzzing/__support/weak_avl_fuzz.cpp
new file mode 100644
index 0000000000000..7c6e50d1fa252
--- /dev/null
+++ b/libc/fuzzing/__support/weak_avl_fuzz.cpp
@@ -0,0 +1,94 @@
+//===-- weak_avl_fuzz.cpp -------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// Fuzzing test for llvm-libc weak AVL implementations.
+///
+//===----------------------------------------------------------------------===//
+#include "hdr/types/ENTRY.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// A sequence of actions:
+// - Erase: a single byte valued (5, 6 mod 7) followed by an int
+// - Find: a single byte valued (4 mod 7) followed by an int
+// - FindOrInsert: a single byte valued (0,1,2,3 mod 7) followed by an int
+extern "C" size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size);
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size,
+ size_t max_size, unsigned int seed) {
+ size = LLVMFuzzerMutate(data, size, max_size);
+ return size / (1 + sizeof(int)) * (1 + sizeof(int));
+}
+
+class AVLTree {
+ using Node = WeakAVLNode<int>;
+ Node *root = nullptr;
+ bool reversed = false;
+ static int compare(int a, int b) { return (a > b) - (a < b); }
+ static int reverse_compare(int a, int b) { return (b > a) - (b < a); }
+
+public:
+ AVLTree(bool reversed = false) : reversed(reversed) {}
+ bool find(int key) {
+ return Node::find(root, key, reversed ? reverse_compare : compare);
+ }
+ bool find_or_insert(int key) {
+ return Node::find_or_insert(root, key,
+ reversed ? reverse_compare : compare);
+ }
+ bool erase(int key) {
+ Node *node = Node::find(root, key, reversed ? reverse_compare : compare);
+ if (node)
+ Node::erase(root, node);
+ return node;
+ }
+ ~AVLTree() { Node::destroy(root); }
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ AVLTree tree1;
+ AVLTree tree2(true);
+ for (size_t i = 0; i + (1 + sizeof(int)) <= size; i += 1 + sizeof(int)) {
+ uint8_t action = data[i];
+ int key;
+ __builtin_memcpy(&key, data + i + 1, sizeof(int));
+ if (action % 7 == 4) {
+ // Find
+ bool res1 = tree1.find(key);
+ bool res2 = tree2.find(key);
+ if (res1 != res2)
+ __builtin_trap();
+
+ } else if (action % 7 == 5 || action % 7 == 6) {
+ // Erase
+ bool res1 = tree1.erase(key);
+ bool res2 = tree2.erase(key);
+ if (res1 != res2)
+ __builtin_trap();
+ if (tree1.find(key))
+ __builtin_trap();
+ if (tree2.find(key))
+ __builtin_trap();
+ } else {
+ // FindOrInsert
+ bool res1 = tree1.find_or_insert(key);
+ bool res2 = tree2.find_or_insert(key);
+ if (res1 != res2)
+ __builtin_trap();
+ if (!tree1.find(key))
+ __builtin_trap();
+ if (!tree2.find(key))
+ __builtin_trap();
+ }
+ }
+ return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 433c47b174766..aed50e64aa2df 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -487,3 +487,21 @@ add_proxy_header_library(
FULL_BUILD_DEPENDS
libc.include.llvm-libc-types.gid_t
)
+
+add_proxy_header_library(
+ posix_tnode
+ HDRS
+ posix_tnode.h
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-types.posix_tnode
+ libc.include.search
+)
+
+add_proxy_header_library(
+ VISIT
+ HDRS
+ VISIT.h
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-types.VISIT
+ libc.include.search
+)
diff --git a/libc/hdr/types/VISIT.h b/libc/hdr/types/VISIT.h
new file mode 100644
index 0000000000000..e8377c233267c
--- /dev/null
+++ b/libc/hdr/types/VISIT.h
@@ -0,0 +1,22 @@
+//===-- Definition of macros from VISIT -----------------------------------===//
+//
+// 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_HDR_TYPES_VISIT_H
+#define LLVM_LIBC_HDR_TYPES_VISIT_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/VISIT.h"
+
+#else // Overlay mode
+
+#include <search.h>
+
+#endif // LLVM_LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_VISIT_H
diff --git a/libc/hdr/types/posix_tnode.h b/libc/hdr/types/posix_tnode.h
new file mode 100644
index 0000000000000..0b2ecfd488217
--- /dev/null
+++ b/libc/hdr/types/posix_tnode.h
@@ -0,0 +1,28 @@
+//===-- Definition of macros from posix_tnode -----------------------------===//
+//
+// 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_HDR_TYPES_POSIX_TNODE_H
+#define LLVM_LIBC_HDR_TYPES_POSIX_TNODE_H
+
+// posix_tnode is introduced in POSIX.1-2024.
+// this may result in problems when overlaying on older systems.
+// internal code should use __llvm_libc_tnode.
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/posix_tnode.h"
+#define __llvm_libc_tnode posix_tnode
+
+#else // Overlay mode
+
+#include <search.h>
+typedef void __llvm_libc_tnode;
+
+#endif // LLVM_LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_POSIX_TNODE_H
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 96c3a88f3fcc8..c72bb5a6eb280 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -273,9 +273,13 @@ add_header_macro(
.llvm-libc-types.ACTION
.llvm-libc-types.ENTRY
.llvm-libc-types.VISIT
+ .llvm-libc-types.posix_tnode
.llvm-libc-types.__search_compare_t
.llvm-libc-types.size_t
.llvm-libc-types.struct_hsearch_data
+ .llvm-libc-types.__action_closure_fn_t
+ .llvm-libc-types.__action_fn_t
+ .llvm-libc-types.__free_fn_t
.llvm_libc_common_h
)
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index fcd0e1245a4a8..0c6d4d8e870d3 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -52,6 +52,7 @@ add_header(off_t HDR off_t.h)
add_header(once_flag HDR once_flag.h DEPENDS .__futex_word)
add_header(posix_spawn_file_actions_t HDR posix_spawn_file_actions_t.h)
add_header(posix_spawnattr_t HDR posix_spawnattr_t.h)
+add_header(posix_tnode HDR posix_tnode.h)
add_header(pthread_attr_t HDR pthread_attr_t.h DEPENDS .size_t)
add_header(pthread_condattr_t HDR pthread_condattr_t.h DEPENDS .clockid_t)
add_header(pthread_key_t HDR pthread_key_t.h)
@@ -310,3 +311,18 @@ add_header(EFI_SYSTEM_TABLE
.EFI_TABLE_HEADER
.char16_t
)
+add_header(__action_closure_fn_t
+ HDR
+ __action_closure_fn_t.h
+ DEPENDS
+ .posix_tnode
+ .VISIT
+)
+add_header(__action_fn_t
+ HDR
+ __action_fn_t.h
+ DEPENDS
+ .posix_tnode
+ .VISIT
+)
+add_header(__free_fn_t HDR __free_fn_t.h)
diff --git a/libc/include/llvm-libc-types/__action_closure_fn_t.h b/libc/include/llvm-libc-types/__action_closure_fn_t.h
new file mode 100644
index 0000000000000..18d91a3f53925
--- /dev/null
+++ b/libc/include/llvm-libc-types/__action_closure_fn_t.h
@@ -0,0 +1,17 @@
+//===-- Definition of type __action_closure_fn_t --------------------------===//
+//
+// 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_TYPES___ACTION_CLOSURE_FN_T_H
+#define LLVM_LIBC_TYPES___ACTION_CLOSURE_FN_T_H
+
+#include "VISIT.h"
+#include "posix_tnode.h"
+
+typedef void (*__action_closure_fn_t)(const posix_tnode *, VISIT, void *);
+
+#endif // LLVM_LIBC_TYPES___ACTION_CLOSURE_FN_T_H
diff --git a/libc/include/llvm-libc-types/__action_fn_t.h b/libc/include/llvm-libc-types/__action_fn_t.h
new file mode 100644
index 0000000000000..de3dc02e4eacd
--- /dev/null
+++ b/libc/include/llvm-libc-types/__action_fn_t.h
@@ -0,0 +1,17 @@
+//===-- Definition of type __action_fn_t ----------------------------------===//
+//
+// 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_TYPES___ACTION_FN_T_H
+#define LLVM_LIBC_TYPES___ACTION_FN_T_H
+
+#include "VISIT.h"
+#include "posix_tnode.h"
+
+typedef void (*__action_fn_t)(const posix_tnode *, VISIT, int);
+
+#endif // LLVM_LIBC_TYPES___ACTION_FN_T_H
diff --git a/libc/include/llvm-libc-types/__free_fn_t.h b/libc/include/llvm-libc-types/__free_fn_t.h
new file mode 100644
index 0000000000000..72f00f3df5796
--- /dev/null
+++ b/libc/include/llvm-libc-types/__free_fn_t.h
@@ -0,0 +1,14 @@
+//===-- Definition of type __free_fn_t ------------------------------------===//
+//
+// 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_TYPES___FREE_FN_T_H
+#define LLVM_LIBC_TYPES___FREE_FN_T_H
+
+typedef void (*__free_fn_t)(void *);
+
+#endif // LLVM_LIBC_TYPES___FREE_FN_T_H
diff --git a/libc/include/llvm-libc-types/posix_tnode.h b/libc/include/llvm-libc-types/posix_tnode.h
new file mode 100644
index 0000000000000..0f7336ac2ad89
--- /dev/null
+++ b/libc/include/llvm-libc-types/posix_tnode.h
@@ -0,0 +1,17 @@
+//===-- Definition of type posix_tnode--------------- ---------------------===//
+//
+// 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_TYPES_POSIX_TNODE_H
+#define LLVM_LIBC_TYPES_POSIX_TNODE_H
+
+// Following POSIX.1-2024 and Austin Group Defect Report 1011:
+// https://austingroupbugs.net/view.php?id=1011
+// Define posix_tnode as void to provide both type safety and compatibility.
+typedef void posix_tnode;
+
+#endif // LLVM_LIBC_TYPES_POSIX_TNODE_H
diff --git a/libc/include/search.yaml b/libc/include/search.yaml
index 8a3a0c50af60f..50f317820e1b5 100644
--- a/libc/include/search.yaml
+++ b/libc/include/search.yaml
@@ -7,6 +7,10 @@ types:
- type_name: VISIT
- type_name: __search_compare_t
- type_name: struct_hsearch_data
+ - type_name: posix_tnode
+ - type_name: __action_closure_fn_t
+ - type_name: __action_fn_t
+ - type_name: __free_fn_t
enums: []
objects: []
functions:
@@ -80,3 +84,47 @@ functions:
- type: size_t *
- type: size_t
- type: __search_compare_t
+ - name: tdelete
+ standards:
+ - posix
+ return_type: void *
+ arguments:
+ - type: const void *__restrict
+ - type: posix_tnode **__restrict
+ - type: __search_compare_t
+ - name: tfind
+ standards:
+ - posix
+ return_type: posix_tnode *
+ arguments:
+ - type: const void *
+ - type: posix_tnode * const *
+ - type: __search_compare_t
+ - name: tsearch
+ standards:
+ - posix
+ return_type: posix_tnode *
+ arguments:
+ - type: const void *
+ - type: posix_tnode **
+ - type: __search_compare_t
+ - name: twalk
+ standards:
+ - posix
+ return_type: void
+ arguments:
+ - type: const posix_tnode *
+ - type: __action_fn_t
+ - name: twalk_r
+ standards: gnu
+ return_type: void
+ arguments:
+ - type: const posix_tnode *
+ - type: __action_closure_fn_t
+ - type: void *
+ - name: tdestroy
+ standards: gnu
+ return_type: void
+ arguments:
+ - type: posix_tnode *
+ - type: __free_fn_t
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index 99a1641d629f3..bde12abaa291e 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -396,7 +396,12 @@ add_header_library(
HDRS
weak_avl.h
DEPENDS
+ libc.hdr.stdint_proxy
+ libc.src.__support.CPP.bit
libc.src.__support.CPP.new
+ libc.src.__support.CPP.utility
+ libc.src.__support.libc_assert
+ libc.src.__support.macros.attributes
libc.src.__support.macros.config
)
diff --git a/libc/src/__support/weak_avl.h b/libc/src/__support/weak_avl.h
index 817146e8b9ab9..8504c77e8f2b9 100644
--- a/libc/src/__support/weak_avl.h
+++ b/libc/src/__support/weak_avl.h
@@ -9,11 +9,586 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_WEAK_AVL_H
#define LLVM_LIBC_SRC___SUPPORT_WEAK_AVL_H
+#include "hdr/stdint_proxy.h"
+#include "src/__support/CPP/bit.h"
#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/utility/move.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/attributes.h"
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
+// A general self-balancing binary search tree where the node pointer can
+// be used as stable handles to the stored values.
+//
+// The self-balancing strategy is the Weak AVL (WAVL) tree, based on the
+// following foundational references:
+// 1. https://maskray.me/blog/2025-12-14-weak-avl-tree
+// 2. https://reviews.freebsd.org/D25480
+// 3. https://ics.uci.edu/~goodrich/teach/cs165/notes/WeakAVLTrees.pdf
+// 4. https://dl.acm.org/doi/10.1145/2689412 (Rank-Balanced Trees)
+//
+// WAVL trees belong to the rank-balanced binary search tree framework
+// (reference 4), alongside AVL and Red-Black trees.
+//
+// Key Properties of WAVL Trees:
+// 1. Relationship to Red-Black Trees: A WAVL tree can always be colored as a
+// Red-Black tree.
+// 2. Relationship to AVL Trees: An AVL tree meets all the requirements of a
+// WAVL tree. Insertion-only WAVL trees maintain the same structure as AVL
+// trees.
+//
+// Rank-Based Balancing:
+// In rank-balanced trees, each node is assigned a rank (conceptually similar
+// to height). The rank difference between a parent and its child is
+// strictly enforced to be either **1** or **2**.
+//
+// - **AVL Trees:** Rank is equivalent to height. The strict condition is that
+// there are no 2-2 nodes (a parent with rank difference 2 to both children).
+// - **WAVL Trees:** The no 2-2 node rule is relaxed for internal nodes during
+// the deletion fixup process, making WAVL trees less strictly balanced than
+// AVL trees but easier to maintain than Red-Black trees.
+//
+// Balancing Mechanics (Promotion/Demotion):
+// - **Null nodes** are considered to have rank -1.
+// - **External/leaf nodes** have rank 0.
+// - **Insertion:** Inserting a node may create a situation where a parent and
+// child have the same rank (difference 0). This is fixed by **promoting**
+// the rank of the parent and propagating the fix upwards using at most two
+// rotations (trinode fixup).
+// - **Deletion:** Deleting a node may result in a parent being 3 ranks higher
+// than a child (difference 3). This is fixed by **demoting** the parent's
+// rank and propagating the fix upwards.
+//
+// Implementation Detail:
+// The rank is **implicitly** maintained. We never store the full rank. Instead,
+// a 2-bit tag is used on each node to record the rank difference to each child:
+// - Bit cleared (0) -> Rank difference is **1**.
+// - Bit set (1) -> Rank difference is **2**.
+template <typename T> class WeakAVLNode {
+ // Data
+ T data;
+
+ // Parent pointer
+ WeakAVLNode *parent;
+
+ // Children pointers
+ WeakAVLNode *children[2];
+
+ // Flags
+ unsigned char left_rank_diff_2 : 1;
+ unsigned char right_rank_diff_2 : 1;
+
+ LIBC_INLINE bool is_leaf() const {
+ return (children[0] == nullptr) && (children[1] == nullptr);
+ }
+
+ LIBC_INLINE void toggle_rank_diff_2(bool is_right) {
+ if (is_right)
+ right_rank_diff_2 ^= 1;
+ else
+ left_rank_diff_2 ^= 1;
+ }
+
+ LIBC_INLINE bool both_flags_set() const {
+ return left_rank_diff_2 && right_rank_diff_2;
+ }
+
+ LIBC_INLINE bool any_flag_set() const {
+ return left_rank_diff_2 || right_rank_diff_2;
+ }
+
+ LIBC_INLINE void clear_flags() {
+ left_rank_diff_2 = 0;
+ right_rank_diff_2 = 0;
+ }
+
+ LIBC_INLINE void set_both_flags() {
+ left_rank_diff_2 = 1;
+ right_rank_diff_2 = 1;
+ }
+
+ LIBC_INLINE WeakAVLNode(T data)
+ : data(cpp::move(data)), parent(nullptr), children{nullptr, nullptr},
+ left_rank_diff_2(0), right_rank_diff_2(0) {}
+
+ LIBC_INLINE static WeakAVLNode *create(T value) {
+ AllocChecker ac;
+ WeakAVLNode *res = new (ac) WeakAVLNode(value);
+ if (ac)
+ return res;
+ return nullptr;
+ }
+
+ // Unlink a node from tree. The corresponding flag is not updated. The node is
+ // not deleted and its pointers are not cleared.
+ // FixupSite is the lowest surviving node from which rank/flag invariants may
+ // be violated.
+ // Our tree requires value to stay in their node to maintain stable addresses.
+ // This complicates the unlink operation as the successor transplanting needs
+ // to update all the pointers and flags.
+ struct FixupSite {
+ WeakAVLNode *parent;
+ bool is_right;
+ };
+ LIBC_INLINE static FixupSite unlink(WeakAVLNode *&root, WeakAVLNode *node) {
+ bool has_left = node->children[0] != nullptr;
+ bool has_right = node->children[1] != nullptr;
+
+ // Case 0: no children
+ if (!has_left && !has_right) {
+ if (!node->parent) {
+ root = nullptr;
+ return {nullptr, false};
+ }
+ FixupSite site = {node->parent, node->parent->children[1] == node};
+ site.parent->children[site.is_right] = nullptr;
+ return site;
+ }
+
+ // Case 1: one child
+ if (has_left != has_right) {
+ WeakAVLNode *child = node->children[has_right];
+ if (!node->parent) {
+ root = child;
+ child->parent = nullptr;
+ return {nullptr, false};
+ }
+ FixupSite site = {node->parent, node->parent->children[1] == node};
+ site.parent->children[site.is_right] = child;
+ child->parent = site.parent;
+ return site;
+ }
+
+ // Case 2: two children: replace by successor (leftmost in right subtree)
+ WeakAVLNode *succ = node->children[1];
+ while (succ->children[0])
+ succ = succ->children[0];
+
+ WeakAVLNode *succ_parent = succ->parent;
+ // succ and node may be adjacent to each other, so we
+ // still need to check the exact direction of the successor.
+ bool succ_was_right = succ_parent->children[1] == succ;
+ WeakAVLNode *succ_rchild = succ->children[1];
+
+ // 1) Splice successor out of its old position (flags intentionally
+ // unchanged)
+ FixupSite site = {succ_parent, succ_was_right};
+ succ_parent->children[succ_was_right] = succ_rchild;
+ if (succ_rchild)
+ succ_rchild->parent = succ_parent;
+
+ // 2) Transplant successor into node's position
+ succ->parent = node->parent;
+ succ->left_rank_diff_2 = node->left_rank_diff_2;
+ succ->right_rank_diff_2 = node->right_rank_diff_2;
+
+ succ->children[0] = node->children[0];
+ succ->children[1] = node->children[1];
+ if (succ->children[0])
+ succ->children[0]->parent = succ;
+ if (succ->children[1])
+ succ->children[1]->parent = succ;
+
+ if (succ->parent) {
+ bool node_was_right = succ->parent->children[1] == node;
+ succ->parent->children[node_was_right] = succ;
+ } else
+ root = succ;
+
+ // 3) If the physical removal was under `node`, fixup parent must be the
+ // successor (since `node` is deleted and successor now occupies that
+ // spot).
+ if (site.parent == node)
+ site.parent = succ;
+
+ return site;
+ }
+
+public:
+ LIBC_INLINE const WeakAVLNode *get_left() const { return children[0]; }
+ LIBC_INLINE const WeakAVLNode *get_right() const { return children[1]; }
+ LIBC_INLINE const WeakAVLNode *get_parent() const { return parent; }
+ LIBC_INLINE const T &get_data() const { return data; }
+ LIBC_INLINE bool has_rank_diff_2(bool is_right) const {
+ return is_right ? right_rank_diff_2 : left_rank_diff_2;
+ }
+
+ // Destroy the subtree rooted at node
+ LIBC_INLINE static void destroy(WeakAVLNode *node) {
+ if (!node)
+ return;
+ destroy(node->children[0]);
+ destroy(node->children[1]);
+ ::delete node;
+ }
+
+ // Destroy the subtree rooted at node with finalizer
+ template <typename Finalizer>
+ LIBC_INLINE static void destroy(WeakAVLNode *node, Finalizer finalizer) {
+ if (!node)
+ return;
+ destroy(node->children[0], finalizer);
+ destroy(node->children[1], finalizer);
+ finalizer(node->data);
+ ::delete node;
+ }
+ // Rotate the subtree rooted at node in the given direction.
+ //
+ // Illustration for is_right = true (Left Rotation):
+ //
+ // (Node) (Pivot)
+ // / \ / \
+ // A (Pivot) => (Node) C
+ // / \ / \
+ // B C A B
+ //
+ LIBC_INLINE static WeakAVLNode *rotate(WeakAVLNode *&root, WeakAVLNode *node,
+ bool is_right) {
+ WeakAVLNode *pivot = node->children[is_right];
+ // Handover pivot's child
+ WeakAVLNode *grandchild = pivot->children[!is_right];
+ node->children[is_right] = grandchild;
+ if (grandchild)
+ grandchild->parent = node;
+ pivot->parent = node->parent;
+ // Pivot becomes the new root of the subtree
+ if (!node->parent)
+ root = pivot;
+ else {
+ bool node_is_right = node->parent->children[1] == node;
+ node->parent->children[node_is_right] = pivot;
+ }
+ pivot->children[!is_right] = node;
+ node->parent = pivot;
+ return pivot;
+ }
+
+ // Find data in the subtree rooted at root. If not found, returns nullptr.
+ // `Compare` returns integer values for ternary comparison.
+ template <typename Compare>
+ LIBC_INLINE static WeakAVLNode *find(WeakAVLNode *root, T data,
+ Compare comp) {
+ WeakAVLNode *cursor = root;
+ while (cursor != nullptr) {
+ int comp_result = comp(cursor->data, data);
+ if (comp_result == 0)
+ return cursor; // Node found
+ bool is_right = comp_result < 0;
+ cursor = cursor->children[is_right];
+ }
+ return nullptr; // Node not found
+ }
+ // Insert data into the subtree rooted at root.
+ // Returns the node if insertion is successful or the node exists in
+ // the tree.
+ // Returns nullptr if memory allocation fails.
+ // `Compare` returns integer values for ternary comparison.
+ template <typename Compare>
+ LIBC_INLINE static WeakAVLNode *find_or_insert(WeakAVLNode *&root, T data,
+ Compare comp) {
+ WeakAVLNode *parent = nullptr, *cursor = root;
+ bool is_right = false;
+ while (cursor != nullptr) {
+ parent = cursor;
+ int comp_result = comp(parent->data, data);
+ if (comp_result == 0)
+ return parent; // Node already exists
+ is_right = comp_result < 0;
+ cursor = cursor->children[is_right];
+ }
+ WeakAVLNode *allocated = create(cpp::move(data));
+ if (!allocated)
+ return nullptr;
+ WeakAVLNode *node = allocated;
+ node->parent = parent;
+
+ // Case 0: inserting into an empty tree
+ if (!parent) {
+ root = node; // Tree was empty
+ return node;
+ }
+
+ parent->children[is_right] = node;
+ // Rebalance process
+ // Case 1: both node and its sibling have rank-difference 1. So after the
+ // insertion, the node is at the same level as the parent. Promoting parent
+ // will fix the conflict of the trinodes but we may need to continue on
+ // parent.
+ //
+ // (GP) (GP)
+ // | Promote | x - 1
+ // | x -----> (P)
+ // 0 | / 1 / \
+ // (N) --- (P) ---- (N) \ 2
+ // \ 1 \
+ // (S) (S)
+ while (parent && !parent->any_flag_set()) {
+ parent->toggle_rank_diff_2(!is_right);
+ node = parent;
+ parent = node->parent;
+ if (parent)
+ is_right = (parent->children[1] == node);
+ continue;
+ }
+ // We finish if node has reaches the root -- otherwise, we end up with
+ // two more cases.
+ if (!parent)
+ return allocated;
+
+ // Case 2: parent does not need to be promoted as node is lower
+ // than the parent by 2 ranks.
+ // (P) (P)
+ // / \ / \
+ // 2 1 => 1 1
+ // / \ / \
+ // (N) (*) (N) (*)
+ if (parent->has_rank_diff_2(is_right))
+ parent->toggle_rank_diff_2(is_right);
+ else {
+ LIBC_ASSERT(!node->both_flags_set() &&
+ "there should be no 2-2 node along the insertion fixup path");
+
+ LIBC_ASSERT((node == allocated || node->any_flag_set()) &&
+ "Internal node must have a child with rank-difference 2, "
+ "otherwise it should have already been handled.");
+
+ // Case 3: node's sibling has rank-difference 2. And node has a 1-node
+ // along the same direction. We can do a single rotation to fix the
+ // trinode.
+ // (GP) (GP)
+ // 0 | X Rotate |
+ // (N) ----- (P) => (N)
+ // 1 / \ 2 \ 2 1 / \ 1
+ // (C1) \ \ (C1) (P)
+ // (C2) (S) 1 / \ 1
+ // (C2) (S)
+ if (node->has_rank_diff_2(!is_right)) {
+ WeakAVLNode *new_subroot = rotate(root, parent, is_right);
+ new_subroot->clear_flags();
+ parent->clear_flags();
+ }
+ // Case 4: node's sibling has rank-difference 2. And node has a 1-node
+ // along the opposite direction. We need a double rotation to fix the
+ // trinode.
+ // (GP) (GP)
+ // 0 | X Zig-Zag | X
+ // (N) ----- (P) => (C1)
+ // 2 / \ 1 \ 2 1 / \ 1
+ // / (C1) \ (N) (P)
+ // (C2) L / \ R (S) 1 / \ L R / \ 1
+ // (A) (B) (C2) (A)(B) (S)
+ // (mirrored)
+ // (GP) (GP)
+ // X | 0 Zig-Zag | X
+ // (P) ----- (N) => (C1)
+ // 2 / 1 / \ 2 1 / \ 1
+ // / (C1) \ (P) (N)
+ // (S) L / \ R (C2) 1 / \ L R / \ 1
+ // (A) (B) (S)(A) (B)(C2)
+ else {
+ WeakAVLNode *subroot1 = rotate(root, node, !is_right); // First rotation
+ [[maybe_unused]] WeakAVLNode *subroot2 =
+ rotate(root, parent, is_right); // Second rotation
+ LIBC_ASSERT(subroot1 == subroot2 &&
+ "Subroots after double rotation should be the same");
+ bool subroot_left_diff_2 = subroot1->left_rank_diff_2;
+ bool subroot_right_diff_2 = subroot1->right_rank_diff_2;
+ node->clear_flags();
+ parent->clear_flags();
+ subroot1->clear_flags();
+ // Select destinations
+ WeakAVLNode *dst_left = is_right ? parent : node;
+ WeakAVLNode *dst_right = is_right ? node : parent;
+ // Masked toggles
+ if (subroot_left_diff_2)
+ dst_left->toggle_rank_diff_2(true);
+
+ if (subroot_right_diff_2)
+ dst_right->toggle_rank_diff_2(false);
+ }
+ }
+
+ return allocated;
+ }
+
+ // Erase the node from the tree rooted at root.
+ LIBC_INLINE static void erase(WeakAVLNode *&root, WeakAVLNode *node) {
+ // Unlink the node from the tree
+ auto [cursor, is_right] = unlink(root, node);
+ delete node;
+ WeakAVLNode *sibling = nullptr;
+ while (cursor) {
+ // Case 0. cursor previously had rank-difference 1 on the side of the
+ // deleted node. We can simply update the rank-difference and stop.
+ // Notice that this step may create 2-2 nodes, thus deviate from "strong"
+ // AVL tree.
+ //
+ // (C) (C)
+ // X / \ 1 => X / \
+ // (*) (D) (*) \ 2
+ // (D)
+ if (!cursor->has_rank_diff_2(is_right)) {
+ cursor->toggle_rank_diff_2(is_right);
+ // If we created a 2-2 leaf, we must demote it and continue.
+ // Otherwise, we are done as the internal node becomes a 2-2 node and
+ // there is no further violation upwards.
+ if (!cursor->both_flags_set() || !cursor->is_leaf())
+ return;
+ // Clear flags for demotion.
+ cursor->clear_flags();
+ }
+
+ // Case 1. cursor previously had rank-difference 2 on the side of the
+ // deleted node. Now it has rank-difference 3, which violates the
+ // weak-AVL property. We found that we have a sibling with rank-difference
+ // 2, so we can demote cursor and continue upwards.
+ //
+ // (P) (P)
+ // | X | (X + 1)
+ // (C) |
+ // / \ => (C)
+ // 2 / \ 1 / \
+ // (*) \ 3 (*) \ 2
+ // (D) (D)
+ else if (cursor->has_rank_diff_2(!is_right))
+ cursor->toggle_rank_diff_2(!is_right);
+
+ // Case 2. continue from Case 1; but the sibling has rank-difference 1.
+ // However, we found that the sibling is a 2-2 node. We demote both
+ // sibling and cursor, and continue upwards. We break for other cases if
+ // sibling cannot be demoted.
+ //
+ // (P) (P)
+ // | X | (X + 1)
+ // (C) |
+ // 1 / \ => (C)
+ // (S) \ 1 / \
+ // / \ \ 3 (S) \ 2
+ // 2 / \ 2 (D) 1 / \ 1 (D)
+ // (*) (*) (*) (*)
+ else {
+ sibling = cursor->children[!is_right];
+ LIBC_ASSERT(sibling && "rank-difference 1 sibling cannot be empty");
+ if (sibling->both_flags_set())
+ sibling->clear_flags();
+ else
+ break;
+ }
+
+ // Update cursor to move upwards
+ if (cursor->parent)
+ is_right = (cursor->parent->children[1] == cursor);
+ cursor = cursor->parent;
+ }
+
+ // Either cursor is nullptr (we reached the root), or sibling has
+ // rank-difference 1.
+ if (!cursor)
+ return;
+ LIBC_ASSERT(sibling && "rank-difference 1 sibling must exist");
+ bool sibling_is_right = !is_right; // Rename for clarity
+
+ // Case 3. continue from Case 2; but the sibling cannot be demoted.
+ // Sibling has a node T along the same direction with rank-difference 1.
+ //
+ // (P) (P)
+ // | X | X
+ // (C) (S)
+ // 1 / \ Rotate 2 / \ 1
+ // (S) \ => / (C)
+ // 1 / \ Y \ 3 (T) Y / \ 2
+ // (T) \ (D) (*) \
+ // (*) (D)
+ if (!sibling->has_rank_diff_2(sibling_is_right)) {
+ WeakAVLNode *new_subroot = rotate(root, cursor, sibling_is_right);
+ LIBC_ASSERT(new_subroot == sibling &&
+ "sibling should become the subtree root");
+ // Update flags
+ bool sibling_alter_child_has_rank_diff_2 =
+ new_subroot->has_rank_diff_2(!sibling_is_right);
+ new_subroot->clear_flags();
+ new_subroot->toggle_rank_diff_2(sibling_is_right);
+
+ // Cursor only needs to be updated if it become a 2-2 node
+ if (sibling_alter_child_has_rank_diff_2) {
+ // Demote a 2-2 cursor if it is a leaf
+ if (cursor->is_leaf()) {
+ cursor->clear_flags();
+ new_subroot->toggle_rank_diff_2(!sibling_is_right);
+ LIBC_ASSERT(new_subroot->both_flags_set() &&
+ "sibling should become a 2-2 node.");
+ } else {
+ cursor->toggle_rank_diff_2(sibling_is_right);
+ LIBC_ASSERT(cursor->both_flags_set() &&
+ "cursor should become a 2-2 node.");
+ }
+ }
+ }
+ // Case 4. continue from Case 3; but rank-difference 1 child T of sibling
+ // is on the opposite direction.
+ //
+ // (P) (P)
+ // | X | X
+ // (C) Zig-Zag (T)
+ // 1 / \ => / \
+ // (S) \ 2 / \ 2
+ // / \ 1 \ 3 (S) (C)
+ // 2 / (T) (D) 1 / Y \ / Z \ 1
+ // (*) Y / \ Z (*) (A)(B) (D)
+ // (A) (B)
+ else {
+ WeakAVLNode *target_child = rotate(root, sibling, !sibling_is_right);
+ bool subtree_left_diff_2 = target_child->left_rank_diff_2;
+ bool subtree_right_diff_2 = target_child->right_rank_diff_2;
+ WeakAVLNode *new_subroot = rotate(root, cursor, sibling_is_right);
+ LIBC_ASSERT(new_subroot == target_child &&
+ "target_child should become the subtree root");
+ // Set flags
+ target_child->set_both_flags();
+ cursor->clear_flags();
+ sibling->clear_flags();
+ // Select destinations
+ WeakAVLNode *dst_left = sibling_is_right ? cursor : sibling;
+ WeakAVLNode *dst_right = sibling_is_right ? sibling : cursor;
+ // Masked toggles
+ if (subtree_left_diff_2)
+ dst_left->toggle_rank_diff_2(true);
+ if (subtree_right_diff_2)
+ dst_right->toggle_rank_diff_2(false);
+ }
+ }
+
+ enum struct WalkType {
+ PreOrder,
+ InOrder,
+ PostOrder,
+ Leaf,
+ };
+ template <typename Func>
+ LIBC_INLINE static void walk(const WeakAVLNode *node, Func func,
+ int depth = 0) {
+ if (!node)
+ return;
+
+ if (node->is_leaf()) {
+ func(node, WalkType::Leaf, depth);
+ return;
+ }
+
+ func(node, WalkType::PreOrder, depth);
+ if (node->children[0])
+ walk(node->children[0], func, depth + 1);
+
+ func(node, WalkType::InOrder, depth);
+
+ if (node->children[1])
+ walk(node->children[1], func, depth + 1);
+ func(node, WalkType::PostOrder, depth);
+ }
+};
+
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC___SUPPORT_WEAK_AVL_H
diff --git a/libc/src/search/CMakeLists.txt b/libc/src/search/CMakeLists.txt
index 0ed513e648ed1..2cdab57396bd1 100644
--- a/libc/src/search/CMakeLists.txt
+++ b/libc/src/search/CMakeLists.txt
@@ -125,3 +125,71 @@ add_entrypoint_object(
libc.src.__support.memory_size
libc.src.string.memory_utils.inline_memcpy
)
+
+add_entrypoint_object(
+ tdelete
+ SRCS
+ tdelete.cpp
+ HDRS
+ tdelete.h
+ DEPENDS
+ libc.hdr.types.posix_tnode
+ libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+ tfind
+ SRCS
+ tfind.cpp
+ HDRS
+ tfind.h
+ DEPENDS
+ libc.hdr.types.posix_tnode
+ libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+ tsearch
+ SRCS
+ tsearch.cpp
+ HDRS
+ tsearch.h
+ DEPENDS
+ libc.hdr.types.posix_tnode
+ libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+ twalk
+ SRCS
+ twalk.cpp
+ HDRS
+ twalk.h
+ DEPENDS
+ libc.hdr.types.posix_tnode
+ libc.hdr.types.VISIT
+ libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+ twalk_r
+ SRCS
+ twalk_r.cpp
+ HDRS
+ twalk_r.h
+ DEPENDS
+ libc.hdr.types.posix_tnode
+ libc.hdr.types.VISIT
+ libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+ tdestroy
+ SRCS
+ tdestroy.cpp
+ HDRS
+ tdestroy.h
+ DEPENDS
+ libc.hdr.types.posix_tnode
+ libc.src.__support.weak_avl
+)
diff --git a/libc/src/search/tdelete.cpp b/libc/src/search/tdelete.cpp
new file mode 100644
index 0000000000000..60c4a330753c9
--- /dev/null
+++ b/libc/src/search/tdelete.cpp
@@ -0,0 +1,36 @@
+//===-- Implementation of tdelete -------------------------------*- 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/tdelete.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// The tdelete() function shall return a pointer to the parent of the deleted
+// node, or an unspecified non-null pointer if the deleted node was the root
+// node, or a null pointer if the node is not found.
+LLVM_LIBC_FUNCTION(void *, tdelete,
+ (const void *key, posix_tnode **rootp,
+ int (*compar)(const void *, const void *))) {
+ if (!rootp)
+ return nullptr;
+ using Node = WeakAVLNode<const void *>;
+ Node *&root = *reinterpret_cast<Node **>(rootp);
+ Node *node = Node::find(root, key, compar);
+ if (!node)
+ return nullptr;
+ void *result = const_cast<Node *>(node->get_parent());
+ if (!result)
+ result = cpp::bit_cast<void *>(uintptr_t(-1));
+ Node::erase(root, node);
+ return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tdelete.h b/libc/src/search/tdelete.h
new file mode 100644
index 0000000000000..168860304dbca
--- /dev/null
+++ b/libc/src/search/tdelete.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for tdelete -----------------------*- 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_TDELETE_H
+#define LLVM_LIBC_SRC_SEARCH_TDELETE_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void *tdelete(const void *key, __llvm_libc_tnode **rootp,
+ int (*compar)(const void *, const void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TDELETE_H
diff --git a/libc/src/search/tdestroy.cpp b/libc/src/search/tdestroy.cpp
new file mode 100644
index 0000000000000..3b93313879ae0
--- /dev/null
+++ b/libc/src/search/tdestroy.cpp
@@ -0,0 +1,28 @@
+//===-- Implementation of tdestroy ------------------------------*- 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/tdestroy.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, tdestroy,
+ (__llvm_libc_tnode * root, void (*free_node)(void *))) {
+ if (!root)
+ return;
+ using Node = WeakAVLNode<const void *>;
+ Node *node = reinterpret_cast<Node *>(root);
+ Node::destroy(node, [free_node](const void *&data) {
+ if (free_node)
+ free_node(const_cast<void *>(data));
+ });
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tdestroy.h b/libc/src/search/tdestroy.h
new file mode 100644
index 0000000000000..fd2305b815079
--- /dev/null
+++ b/libc/src/search/tdestroy.h
@@ -0,0 +1,19 @@
+//===-- Implementation header for tdestroy ----------------------*- 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_TDESTROY_H
+#define LLVM_LIBC_SRC_SEARCH_TDESTROY_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void tdestroy(__llvm_libc_tnode *root, void (*free_node)(void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TDESTROY_H
diff --git a/libc/src/search/tfind.cpp b/libc/src/search/tfind.cpp
new file mode 100644
index 0000000000000..638076c67289d
--- /dev/null
+++ b/libc/src/search/tfind.cpp
@@ -0,0 +1,27 @@
+//===-- Implementation of tfind ---------------------------------*- 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/tfind.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(__llvm_libc_tnode *, tfind,
+ (const void *key, __llvm_libc_tnode *const *rootp,
+ int (*compar)(const void *, const void *))) {
+ if (!rootp)
+ return nullptr;
+ using Node = WeakAVLNode<const void *>;
+ Node *root = reinterpret_cast<Node *>(*rootp);
+ Node *node = Node::find(root, key, compar);
+ return reinterpret_cast<__llvm_libc_tnode *>(node);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tfind.h b/libc/src/search/tfind.h
new file mode 100644
index 0000000000000..f44ec83c701dc
--- /dev/null
+++ b/libc/src/search/tfind.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for tfind -------------------------*- 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_TFIND_H
+#define LLVM_LIBC_SRC_SEARCH_TFIND_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+__llvm_libc_tnode *tfind(const void *key, __llvm_libc_tnode *const *rootp,
+ int (*compar)(const void *, const void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TFIND_H
diff --git a/libc/src/search/tsearch.cpp b/libc/src/search/tsearch.cpp
new file mode 100644
index 0000000000000..32688915e84ef
--- /dev/null
+++ b/libc/src/search/tsearch.cpp
@@ -0,0 +1,27 @@
+//===-- Implementation of tsearch -------------------------------*- 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/tsearch.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(__llvm_libc_tnode *, tsearch,
+ (const void *key, __llvm_libc_tnode **rootp,
+ int (*compar)(const void *, const void *))) {
+ if (!rootp)
+ return nullptr;
+ using Node = WeakAVLNode<const void *>;
+ Node *&root = *reinterpret_cast<Node **>(rootp);
+ Node *node = Node::find_or_insert(root, key, compar);
+ return reinterpret_cast<__llvm_libc_tnode *>(node);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tsearch.h b/libc/src/search/tsearch.h
new file mode 100644
index 0000000000000..859c39832c1ec
--- /dev/null
+++ b/libc/src/search/tsearch.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for tsearch -----------------------*- 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_TSEARCH_H
+#define LLVM_LIBC_SRC_SEARCH_TSEARCH_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+__llvm_libc_tnode *tsearch(const void *key, __llvm_libc_tnode **rootp,
+ int (*compar)(const void *, const void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TSEARCH_H
diff --git a/libc/src/search/twalk.cpp b/libc/src/search/twalk.cpp
new file mode 100644
index 0000000000000..4b7ad98e3bc5a
--- /dev/null
+++ b/libc/src/search/twalk.cpp
@@ -0,0 +1,34 @@
+//===-- Implementation of twalk --------------------------------*- 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/twalk.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+using Node = WeakAVLNode<const void *>;
+
+LLVM_LIBC_FUNCTION(void, twalk,
+ (const __llvm_libc_tnode *root,
+ void (*action)(const __llvm_libc_tnode *, VISIT, int))) {
+ if (!root || !action)
+ return;
+ const Node *node = reinterpret_cast<const Node *>(root);
+ Node::walk(node, [action](const Node *n, Node::WalkType type, int depth) {
+ VISIT v = (type == Node::WalkType::PreOrder) ? preorder
+ : (type == Node::WalkType::InOrder) ? postorder
+ : (type == Node::WalkType::PostOrder) ? endorder
+ : leaf;
+ action(reinterpret_cast<const __llvm_libc_tnode *>(n), v, depth);
+ });
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/twalk.h b/libc/src/search/twalk.h
new file mode 100644
index 0000000000000..2353b6bf36191
--- /dev/null
+++ b/libc/src/search/twalk.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for twalk -------------------------*- 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_TWALK_H
+#define LLVM_LIBC_SRC_SEARCH_TWALK_H
+
+#include "hdr/types/VISIT.h"
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void twalk(const __llvm_libc_tnode *root,
+ void (*action)(const __llvm_libc_tnode *, VISIT, int));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TWALK_H
diff --git a/libc/src/search/twalk_r.cpp b/libc/src/search/twalk_r.cpp
new file mode 100644
index 0000000000000..0f88becc2617f
--- /dev/null
+++ b/libc/src/search/twalk_r.cpp
@@ -0,0 +1,34 @@
+//===-- Implementation of twalk_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/twalk_r.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+using Node = WeakAVLNode<const void *>;
+
+LLVM_LIBC_FUNCTION(void, twalk_r,
+ (const __llvm_libc_tnode *root,
+ void (*action)(const __llvm_libc_tnode *, VISIT, void *),
+ void *closure)) {
+ if (!root || !action)
+ return;
+ const Node *node = reinterpret_cast<const Node *>(root);
+ Node::walk(node, [action, closure](const Node *n, Node::WalkType type, int) {
+ VISIT v = (type == Node::WalkType::PreOrder) ? preorder
+ : (type == Node::WalkType::InOrder) ? postorder
+ : (type == Node::WalkType::PostOrder) ? endorder
+ : leaf;
+ action(reinterpret_cast<const __llvm_libc_tnode *>(n), v, closure);
+ });
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/twalk_r.h b/libc/src/search/twalk_r.h
new file mode 100644
index 0000000000000..95a29256d8256
--- /dev/null
+++ b/libc/src/search/twalk_r.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for twalk_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_TWALK_R_H
+#define LLVM_LIBC_SRC_SEARCH_TWALK_R_H
+
+#include "hdr/types/VISIT.h"
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void twalk_r(const __llvm_libc_tnode *root,
+ void (*action)(const __llvm_libc_tnode *, VISIT, void *),
+ void *closure);
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TWALK_R_H
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 138866b4cc869..97ee42347c9f3 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -280,6 +280,16 @@ add_libc_test(
libc.src.__support.CPP.bit
)
+add_libc_test(
+ weak_avl_test
+ SUITE
+ libc-support-tests
+ SRCS
+ weak_avl_test.cpp
+ DEPENDS
+ libc.src.__support.weak_avl
+)
+
add_subdirectory(CPP)
add_subdirectory(File)
add_subdirectory(RPC)
diff --git a/libc/test/src/__support/weak_avl_test.cpp b/libc/test/src/__support/weak_avl_test.cpp
new file mode 100644
index 0000000000000..b8291b6c78b15
--- /dev/null
+++ b/libc/test/src/__support/weak_avl_test.cpp
@@ -0,0 +1,291 @@
+//===-- Unittests for WeakAVL ---------------------------------------------===//
+//
+// 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/weak_avl.h"
+#include "test/UnitTest/Test.h"
+
+using Node = LIBC_NAMESPACE::WeakAVLNode<int>;
+
+namespace {
+constexpr int TEST_SIZE = 128;
+// Validate weak-AVL rank-difference invariant assuming **pure insertion only**
+// (i.e. no erasure has occurred).
+//
+// NOTE: This validator is intentionally *not* correct after erase(), because
+// weak-AVL allows transient or permanent 2-2 configurations during deletion
+// fixup.
+bool validate_pure_insertion(const Node *node) {
+ if (!node)
+ return true;
+ bool left_2 = node->has_rank_diff_2(false);
+ bool right_2 = node->has_rank_diff_2(true);
+ return (!left_2 || !right_2) && validate_pure_insertion(node->get_left()) &&
+ validate_pure_insertion(node->get_right());
+}
+
+// Insert according to pattern `next(i)`
+using NextFn = int (*)(int);
+
+static Node *build_tree(NextFn next, int N, int (*compare)(int, int)) {
+ Node *root = nullptr;
+ for (int i = 0; i < N; ++i)
+ Node::find_or_insert(root, next(i), compare);
+ return root;
+}
+
+// Insertion patterns
+static int seq(int i) { return i; }
+
+static int rev(int i) {
+ constexpr int N = TEST_SIZE;
+ return N - 1 - i;
+}
+
+// Coprime stride permutation: i -> (i * X) % N
+static int stride(int i, int prime = 7919) {
+ constexpr int N = TEST_SIZE;
+ return (i * prime) % N;
+}
+
+// Thin wrappers to make test intent explicit.
+template <typename Compare>
+static Node *find(Node *root, int value, Compare &&comp) {
+ return Node::find(root, value, comp);
+}
+
+static void erase(Node *&root, Node *node) { Node::erase(root, node); }
+
+} // namespace
+
+TEST(LlvmLibcWeakAVLTest, SimpleInsertion) {
+ Node *root = nullptr;
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+
+ Node *node10 = Node::find_or_insert(root, 10, compare);
+ ASSERT_TRUE(node10 != nullptr);
+ ASSERT_EQ(root, node10);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ Node *node5 = Node::find_or_insert(root, 5, compare);
+ ASSERT_TRUE(node5 != nullptr);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ Node *node15 = Node::find_or_insert(root, 15, compare);
+ ASSERT_TRUE(node15 != nullptr);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ Node *node10_again = Node::find_or_insert(root, 10, compare);
+ ASSERT_EQ(node10, node10_again);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ Node::destroy(root);
+}
+
+TEST(LlvmLibcWeakAVLTest, SequentialInsertion) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+ constexpr int N = TEST_SIZE;
+
+ Node *root = build_tree(seq, N, compare);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ for (int i = 0; i < N; ++i) {
+ Node *node = Node::find_or_insert(root, i, compare);
+ ASSERT_TRUE(node != nullptr);
+ ASSERT_EQ(node->get_data(), i);
+ }
+
+ ASSERT_TRUE(validate_pure_insertion(root));
+ Node::destroy(root);
+}
+
+TEST(LlvmLibcWeakAVLTest, ReversedInsertion) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+ constexpr int N = TEST_SIZE;
+
+ Node *root = build_tree(rev, N, compare);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ for (int i = 0; i < N; ++i) {
+ Node *node = Node::find_or_insert(root, i, compare);
+ ASSERT_TRUE(node != nullptr);
+ ASSERT_EQ(node->get_data(), i);
+ }
+
+ ASSERT_TRUE(validate_pure_insertion(root));
+ Node::destroy(root);
+}
+
+TEST(LlvmLibcWeakAVLTest, StridedInsertion) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+ constexpr int N = TEST_SIZE;
+
+ Node *root = build_tree([](int i) { return stride(i); }, N, compare);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ for (int i = 0; i < N; ++i) {
+ Node *node = Node::find_or_insert(root, i, compare);
+ ASSERT_TRUE(node != nullptr);
+ ASSERT_EQ(node->get_data(), i);
+ }
+
+ ASSERT_TRUE(validate_pure_insertion(root));
+ Node::destroy(root);
+}
+
+TEST(LlvmLibcWeakAVLTest, FindExistingAndMissing) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+ constexpr int N = TEST_SIZE;
+
+ Node *root = build_tree(seq, N, compare);
+ ASSERT_TRUE(validate_pure_insertion(root));
+
+ for (int i = 0; i < N; ++i) {
+ Node *node = find(root, i, compare);
+ ASSERT_TRUE(node != nullptr);
+ ASSERT_EQ(node->get_data(), i);
+ }
+
+ ASSERT_TRUE(find(root, -1, compare) == nullptr);
+ ASSERT_TRUE(find(root, N, compare) == nullptr);
+ ASSERT_TRUE(find(root, 2 * N, compare) == nullptr);
+
+ Node::destroy(root);
+}
+
+TEST(LlvmLibcWeakAVLTest, SequentialErase) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+ constexpr int N = TEST_SIZE;
+
+ Node *root = build_tree(seq, N, compare);
+
+ for (int i = 0; i < N; ++i) {
+ Node *node = find(root, i, compare);
+ ASSERT_TRUE(node != nullptr);
+
+ erase(root, node);
+ ASSERT_TRUE(find(root, i, compare) == nullptr);
+ }
+
+ ASSERT_TRUE(root == nullptr);
+}
+
+TEST(LlvmLibcWeakAVLTest, ReverseErase) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+ constexpr int N = TEST_SIZE;
+
+ Node *root = build_tree(seq, N, compare);
+
+ for (int i = N - 1; i >= 0; --i) {
+ Node *node = find(root, i, compare);
+ ASSERT_TRUE(node != nullptr);
+
+ erase(root, node);
+ ASSERT_TRUE(find(root, i, compare) == nullptr);
+ }
+
+ ASSERT_TRUE(root == nullptr);
+}
+
+TEST(LlvmLibcWeakAVLTest, StridedErase) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+ constexpr int N = TEST_SIZE;
+
+ Node *root = build_tree(seq, N, compare);
+
+ for (int i = 0; i < N; ++i) {
+ int key = stride(i, 5261);
+ Node *node = find(root, key, compare);
+ ASSERT_TRUE(node != nullptr);
+
+ erase(root, node);
+ ASSERT_TRUE(find(root, key, compare) == nullptr);
+ }
+
+ ASSERT_TRUE(root == nullptr);
+}
+
+TEST(LlvmLibcWeakAVLTest, EraseStructuralCases) {
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+
+ Node *root = nullptr;
+ int keys[] = {10, 5, 15, 3, 7, 12, 18};
+
+ for (int k : keys)
+ Node::find_or_insert(root, k, compare);
+
+ // Erase leaf.
+ erase(root, find(root, 3, compare));
+ ASSERT_TRUE(find(root, 3, compare) == nullptr);
+
+ // Erase internal nodes.
+ erase(root, find(root, 5, compare));
+ ASSERT_TRUE(find(root, 5, compare) == nullptr);
+
+ erase(root, find(root, 10, compare));
+ ASSERT_TRUE(find(root, 10, compare) == nullptr);
+
+ int attempts[] = {7, 12, 15, 18};
+ for (int k : attempts) {
+ Node *n = find(root, k, compare);
+ ASSERT_TRUE(n != nullptr);
+ ASSERT_EQ(n->get_data(), k);
+ }
+
+ Node::destroy(root);
+}
+
+TEST(LlvmLibcTreeWalk, EraseStructuralCases) {
+ using WeakAVLNode = LIBC_NAMESPACE::WeakAVLNode<int>;
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+
+ WeakAVLNode *root = nullptr;
+ int keys[] = {10, 5, 15, 3, 7, 12, 18};
+
+ for (int k : keys)
+ WeakAVLNode::find_or_insert(root, k, compare);
+
+ // Erase leaf.
+ erase(root, find(root, 3, compare));
+ ASSERT_TRUE(find(root, 3, compare) == nullptr);
+
+ // Erase internal nodes.
+ erase(root, find(root, 5, compare));
+ ASSERT_TRUE(find(root, 5, compare) == nullptr);
+
+ erase(root, find(root, 10, compare));
+ ASSERT_TRUE(find(root, 10, compare) == nullptr);
+
+ int attempts[] = {7, 12, 15, 18};
+ for (int k : attempts) {
+ WeakAVLNode *n = find(root, k, compare);
+ ASSERT_TRUE(n != nullptr);
+ ASSERT_EQ(n->get_data(), k);
+ }
+
+ WeakAVLNode::destroy(root);
+}
+
+TEST(LlvmLibcTreeWalk, InOrderTraversal) {
+ using WeakAVLNode = LIBC_NAMESPACE::WeakAVLNode<int>;
+ auto compare = [](int a, int b) { return (a > b) - (a < b); };
+
+ WeakAVLNode *root =
+ build_tree([](int x) { return stride(x, 1007); }, TEST_SIZE, compare);
+ int data[TEST_SIZE];
+ int counter = 0;
+ WeakAVLNode::walk(
+ root, [&](const WeakAVLNode *node, WeakAVLNode::WalkType type, int) {
+ if (type == WeakAVLNode::WalkType::InOrder ||
+ type == WeakAVLNode::WalkType::Leaf)
+ data[counter++] = node->get_data();
+ });
+
+ for (int i = 0; i < TEST_SIZE; ++i)
+ ASSERT_EQ(data[i], i);
+ WeakAVLNode::destroy(root);
+}
diff --git a/libc/test/src/search/CMakeLists.txt b/libc/test/src/search/CMakeLists.txt
index 9f34d4d3fd259..273ce9722148b 100644
--- a/libc/test/src/search/CMakeLists.txt
+++ b/libc/test/src/search/CMakeLists.txt
@@ -45,3 +45,18 @@ add_libc_unittest(
DEPENDS
libc.src.search.lsearch
)
+
+add_libc_unittest(
+ tsearch_test
+ SUITE
+ libc_search_unittests
+ SRCS
+ tsearch_test.cpp
+ DEPENDS
+ libc.src.search.tsearch
+ libc.src.search.tfind
+ libc.src.search.tdelete
+ libc.src.search.twalk
+ libc.src.search.twalk_r
+ libc.src.search.tdestroy
+)
diff --git a/libc/test/src/search/tsearch_test.cpp b/libc/test/src/search/tsearch_test.cpp
new file mode 100644
index 0000000000000..609b6207f842b
--- /dev/null
+++ b/libc/test/src/search/tsearch_test.cpp
@@ -0,0 +1,137 @@
+//===-- Unittests for tsearch ---------------------------------------------===//
+//
+// 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 "hdr/types/posix_tnode.h"
+#include "src/search/tdelete.h"
+#include "src/search/tdestroy.h"
+#include "src/search/tfind.h"
+#include "src/search/tsearch.h"
+#include "src/search/twalk.h"
+#include "src/search/twalk_r.h"
+#include "test/UnitTest/Test.h"
+#include <search.h>
+
+static void *encode(int val) {
+ return reinterpret_cast<void *>(static_cast<intptr_t>(val));
+}
+
+static int decode(const void *val) {
+ return static_cast<int>(reinterpret_cast<intptr_t>(val));
+}
+
+static int read_and_decode(const __llvm_libc_tnode *val) {
+ return static_cast<int>(*static_cast<const intptr_t *>(val));
+}
+
+static int compare(const void *a, const void *b) {
+ int x = decode(a);
+ int y = decode(b);
+ return (x > y) - (x < y);
+}
+
+TEST(LlvmLibcTSearchTest, TSearch) {
+ void *root = nullptr;
+ void *result;
+ int key = 10;
+
+ // Insert 10
+ result = LIBC_NAMESPACE::tsearch(encode(key), &root, compare);
+ ASSERT_NE(result, nullptr);
+ ASSERT_EQ(read_and_decode(result), key);
+
+ // Find 10
+ result = LIBC_NAMESPACE::tfind(encode(key), &root, compare);
+ ASSERT_NE(result, nullptr);
+ ASSERT_EQ(read_and_decode(result), key);
+
+ // Insert 20
+ int key2 = 20;
+ result = LIBC_NAMESPACE::tsearch(encode(key2), &root, compare);
+ ASSERT_NE(result, nullptr);
+ ASSERT_EQ(read_and_decode(result), key2);
+
+ // Find 20
+ result = LIBC_NAMESPACE::tfind(encode(key2), &root, compare);
+ ASSERT_NE(result, nullptr);
+ ASSERT_EQ(read_and_decode(result), key2);
+
+ // Delete 10
+ result = LIBC_NAMESPACE::tdelete(encode(key), &root, compare);
+ ASSERT_NE(result, nullptr);
+
+ // Find 10 should fail
+ result = LIBC_NAMESPACE::tfind(encode(key), &root, compare);
+ ASSERT_EQ(result, nullptr);
+
+ // Delete 20
+ result = LIBC_NAMESPACE::tdelete(encode(key2), &root, compare);
+ ASSERT_NE(result, nullptr);
+ // Tree should be empty
+ ASSERT_EQ(root, nullptr);
+}
+
+static void free_node(void *) {
+ // Do nothing for integer keys
+}
+
+TEST(LlvmLibcTSearchTest, TDestroy) {
+ void *root = nullptr;
+ for (int i = 0; i < 10; ++i)
+ LIBC_NAMESPACE::tsearch(encode(i), &root, compare);
+
+ LIBC_NAMESPACE::tdestroy(root, free_node);
+}
+
+static int walk_sum = 0;
+static void action(const __llvm_libc_tnode *node, VISIT visit, int) {
+ if (visit == leaf || visit == postorder)
+ walk_sum += read_and_decode(node);
+}
+
+TEST(LlvmLibcTSearchTest, TWalk) {
+ void *root = nullptr;
+ int sum = 0;
+ for (int i = 1; i <= 5; ++i) {
+ LIBC_NAMESPACE::tsearch(encode(i), &root, compare);
+ sum += i;
+ }
+
+ walk_sum = 0;
+ LIBC_NAMESPACE::twalk(root, action);
+ ASSERT_EQ(walk_sum, sum);
+
+ LIBC_NAMESPACE::tdestroy(root, free_node);
+}
+
+static void action_closure(const __llvm_libc_tnode *node, VISIT visit,
+ void *closure) {
+ if (visit == leaf || visit == postorder) {
+ int **cursor = static_cast<int **>(closure);
+ **cursor = read_and_decode(node);
+ *cursor += 1;
+ }
+}
+
+TEST(LlvmLibcTSearchTest, TWalkR) {
+ void *root = nullptr;
+ constexpr int LIMIT = 64;
+ int sorted[LIMIT];
+ for (int i = 0; i < LIMIT; ++i) {
+ int current = (i * 37) % LIMIT; // pseudo-random insertion order
+ LIBC_NAMESPACE::tsearch(encode(current), &root, compare);
+ }
+
+ walk_sum = 0;
+ int *cursor = &sorted[0];
+ LIBC_NAMESPACE::twalk_r(root, action_closure, &cursor);
+
+ for (int i = 0; i < LIMIT; ++i)
+ ASSERT_EQ(sorted[i], i);
+
+ LIBC_NAMESPACE::tdestroy(root, free_node);
+}
More information about the libc-commits
mailing list