Skip to content

[libc][stdlib] Implement heap sort. #98582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,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 @@ -17,5 +17,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 @@ -76,5 +76,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 @@ -42,6 +42,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
Loading