Skip to content

Commit

Permalink
[libc][stdlib] Implement heap sort. (#98582)
Browse files Browse the repository at this point in the history
  • Loading branch information
lntue authored Jul 16, 2024
1 parent d4a89af commit a6d2da8
Show file tree
Hide file tree
Showing 18 changed files with 768 additions and 393 deletions.
13 changes: 12 additions & 1 deletion libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,21 @@ function(_get_compile_options_from_flags output_var)
set(${output_var} ${compile_options} PARENT_SCOPE)
endfunction(_get_compile_options_from_flags)

function(_get_compile_options_from_config output_var)
set(config_options "")

if(LIBC_CONF_QSORT_IMPL)
list(APPEND config_options "-DLIBC_QSORT_IMPL=${LIBC_CONF_QSORT_IMPL}")
endif()

set(${output_var} ${config_options} PARENT_SCOPE)
endfunction(_get_compile_options_from_config)

function(_get_common_compile_options output_var flags)
_get_compile_options_from_flags(compile_flags ${flags})
_get_compile_options_from_config(config_flags)

set(compile_options ${LIBC_COMPILE_OPTIONS_DEFAULT} ${compile_flags})
set(compile_options ${LIBC_COMPILE_OPTIONS_DEFAULT} ${compile_flags} ${config_flags})

if(LLVM_COMPILER_IS_GCC_COMPATIBLE)
list(APPEND compile_options "-fpie")
Expand Down
5 changes: 5 additions & 0 deletions libc/config/baremetal/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@
"LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE": {
"value": 102400
}
},
"qsort": {
"LIBC_CONF_QSORT_IMPL": {
"value": "LIBC_QSORT_HEAP_SORT"
}
}
}
6 changes: 6 additions & 0 deletions libc/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,11 @@
"value": 0,
"doc": "Configures optimizations for math functions. Values accepted are LIBC_MATH_SKIP_ACCURATE_PASS, LIBC_MATH_SMALL_TABLES, LIBC_MATH_NO_ERRNO, LIBC_MATH_NO_EXCEPT, and LIBC_MATH_FAST."
}
},
"qsort": {
"LIBC_CONF_QSORT_IMPL": {
"value": "LIBC_QSORT_QUICK_SORT",
"doc": "Configures sorting algorithm for qsort and qsort_r. Values accepted are LIBC_QSORT_QUICK_SORT, LIBC_QSORT_HEAP_SORT."
}
}
}
2 changes: 2 additions & 0 deletions libc/docs/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ to learn about the defaults for your platform and target.
- ``LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a mutex is in contention (default to 100).
- ``LIBC_CONF_RWLOCK_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a rwlock is in contention (default to 100).
- ``LIBC_CONF_TIMEOUT_ENSURE_MONOTONICITY``: Automatically adjust timeout to CLOCK_MONOTONIC (default to true). POSIX API may require CLOCK_REALTIME, which can be unstable and leading to unexpected behavior. This option will convert the real-time timestamp to monotonic timestamp relative to the time of call.
* **"qsort" options**
- ``LIBC_CONF_QSORT_IMPL``: Configures sorting algorithm for qsort and qsort_r. Values accepted are LIBC_QSORT_QUICK_SORT, LIBC_QSORT_HEAP_SORT.
* **"scanf" options**
- ``LIBC_CONF_SCANF_DISABLE_FLOAT``: Disable parsing floating point values in scanf and friends.
- ``LIBC_CONF_SCANF_DISABLE_INDEX_MODE``: Disable index mode in the scanf format string.
Expand Down
4 changes: 4 additions & 0 deletions libc/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,13 @@ add_entrypoint_object(
add_header_library(
qsort_util
HDRS
qsort_data.h
qsort_util.h
heap_sort.h
quick_sort.h
DEPENDS
libc.include.stdlib
libc.src.__support.CPP.cstddef
)

add_entrypoint_object(
Expand Down
61 changes: 61 additions & 0 deletions libc/src/stdlib/heap_sort.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//===-- Implementation of heap sort -----------------------------*- 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_STDLIB_HEAP_SORT_H
#define LLVM_LIBC_SRC_STDLIB_HEAP_SORT_H

#include "src/__support/CPP/cstddef.h"
#include "src/stdlib/qsort_data.h"

namespace LIBC_NAMESPACE_DECL {
namespace internal {

// A simple in-place heapsort implementation.
// Follow the implementation in https://en.wikipedia.org/wiki/Heapsort.

LIBC_INLINE void heap_sort(const Array &array) {
size_t end = array.size();
size_t start = end / 2;

auto left_child = [](size_t i) -> size_t { return 2 * i + 1; };

while (end > 1) {
if (start > 0) {
// Select the next unheapified element to sift down.
--start;
} else {
// Extract the max element of the heap, moving a leaf to root to be sifted
// down.
--end;
array.swap(0, end);
}

// Sift start down the heap.
size_t root = start;
while (left_child(root) < end) {
size_t child = left_child(root);
// If there are two children, set child to the greater.
if (child + 1 < end &&
array.elem_compare(child, array.get(child + 1)) < 0)
++child;

// If the root is less than the greater child
if (array.elem_compare(root, array.get(child)) >= 0)
break;

// Swap the root with the greater child and continue sifting down.
array.swap(root, child);
root = child;
}
}
}

} // namespace internal
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_STDLIB_HEAP_SORT_H
7 changes: 5 additions & 2 deletions libc/src/stdlib/qsort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ LLVM_LIBC_FUNCTION(void, qsort,
if (array == nullptr || array_size == 0 || elem_size == 0)
return;
internal::Comparator c(compare);
internal::quicksort(internal::Array(reinterpret_cast<uint8_t *>(array),
array_size, elem_size, c));

auto arr = internal::Array(reinterpret_cast<uint8_t *>(array), array_size,
elem_size, c);

internal::sort(arr);
}

} // namespace LIBC_NAMESPACE_DECL
102 changes: 102 additions & 0 deletions libc/src/stdlib/qsort_data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//===-- Data structures for sorting routines --------------------*- 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_STDLIB_QSORT_DATA_H
#define LLVM_LIBC_SRC_STDLIB_QSORT_DATA_H

#include "src/__support/CPP/cstddef.h"
#include "src/__support/macros/config.h"

#include <stdint.h>

namespace LIBC_NAMESPACE_DECL {
namespace internal {

using Compare = int(const void *, const void *);
using CompareWithState = int(const void *, const void *, void *);

enum class CompType { COMPARE, COMPARE_WITH_STATE };

struct Comparator {
union {
Compare *comp_func;
CompareWithState *comp_func_r;
};
const CompType comp_type;

void *arg;

Comparator(Compare *func)
: comp_func(func), comp_type(CompType::COMPARE), arg(nullptr) {}

Comparator(CompareWithState *func, void *arg_val)
: comp_func_r(func), comp_type(CompType::COMPARE_WITH_STATE),
arg(arg_val) {}

#if defined(__clang__)
// Recent upstream changes to -fsanitize=function find more instances of
// function type mismatches. One case is with the comparator passed to this
// class. Libraries will tend to pass comparators that take pointers to
// varying types while this comparator expects to accept const void pointers.
// Ideally those tools would pass a function that strictly accepts const
// void*s to avoid UB, or would use qsort_r to pass their own comparator.
[[clang::no_sanitize("function")]]
#endif
int comp_vals(const void *a, const void *b) const {
if (comp_type == CompType::COMPARE) {
return comp_func(a, b);
} else {
return comp_func_r(a, b, arg);
}
}
};

class Array {
uint8_t *array;
size_t array_size;
size_t elem_size;
Comparator compare;

public:
Array(uint8_t *a, size_t s, size_t e, Comparator c)
: array(a), array_size(s), elem_size(e), compare(c) {}

uint8_t *get(size_t i) const { return array + i * elem_size; }

void swap(size_t i, size_t j) const {
uint8_t *elem_i = get(i);
uint8_t *elem_j = get(j);
for (size_t b = 0; b < elem_size; ++b) {
uint8_t temp = elem_i[b];
elem_i[b] = elem_j[b];
elem_j[b] = temp;
}
}

int elem_compare(size_t i, const uint8_t *other) const {
// An element must compare equal to itself so we don't need to consult the
// user provided comparator.
if (get(i) == other)
return 0;
return compare.comp_vals(get(i), other);
}

size_t size() const { return array_size; }

// Make an Array starting at index |i| and size |s|.
Array make_array(size_t i, size_t s) const {
return Array(get(i), s, elem_size, compare);
}
};

using SortingRoutine = void(const Array &);

} // namespace internal
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_STDLIB_QSORT_DATA_H
6 changes: 4 additions & 2 deletions libc/src/stdlib/qsort_r.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ LLVM_LIBC_FUNCTION(void, qsort_r,
if (array == nullptr || array_size == 0 || elem_size == 0)
return;
internal::Comparator c(compare, arg);
internal::quicksort(internal::Array(reinterpret_cast<uint8_t *>(array),
array_size, elem_size, c));
auto arr = internal::Array(reinterpret_cast<uint8_t *>(array), array_size,
elem_size, c);

internal::sort(arr);
}

} // namespace LIBC_NAMESPACE_DECL
Loading

0 comments on commit a6d2da8

Please sign in to comment.