From c84e0769c18c4dabf9ed6d94c9d47f340fa30362 Mon Sep 17 00:00:00 2001 From: B-Howell <116104752+B-Howell@users.noreply.github.com> Date: Wed, 28 Oct 2026 01:54:25 -0400 Subject: [PATCH 1/3] fix(libc): sort with FreeBSD 11.4's merge.c in mergesort The console's libSceLibcInternal mergesort checks its arguments as FreeBSD's merge.c does (size under 4 is EINVAL, a scratch block of count times size plus 8 bytes), but the guest export was a plain bottom-up merge, so it called the comparator in a different order and number of times. It now runs merge.c's natural/pairwise setup with insertion sort of the last four or five elements, the linked runs kept inside both buffers, and the switch to exponential search after six linear wins. guest_qsort checks the comparator call sequence for four inputs against libbsd's copy of merge.c, and 12-byte elements on an unaligned base. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/CMakeLists.txt | 1 + core/libs/prx/libc/src/FileAndHeap.cpp | 34 ---- core/libs/prx/libc/src/Mergesort.cpp | 241 +++++++++++++++++++++++++ core/libs/tests/GuestQsort.cpp | 75 ++++++++ docs/dev/debt/libc.md | 2 +- 5 files changed, 318 insertions(+), 35 deletions(-) create mode 100644 core/libs/prx/libc/src/Mergesort.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 1ca7ec0b990..fd0f1abee20 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -78,6 +78,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/TimeExtras.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AtomicBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Mergesort.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HostErrno.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileLock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/StdioExtras.cpp diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 9078a5fe41f..3350e49cc43 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -727,40 +727,6 @@ void* APS5_VABI bsearch_nid_postfix(const void* key, const void* base, size_t co return nullptr; } -int APS5_VABI mergesort_nid_postfix(void* base, size_t count, size_t size, GuestCompare compare) { - if (size < 4) { - errno = 22; - return -1; - } - if (count == 0) return 0; - if (!compare) throw std::invalid_argument("mergesort: null comparator"); - if (!base) throw std::invalid_argument("mergesort: null base"); - if (count > (std::numeric_limits::max() - 8) / size) throw std::overflow_error("mergesort: array size overflow"); - auto* const scratch = static_cast(malloc_nid_postfix(count * size + 8)); - if (!scratch) return -1; - auto* source = static_cast(base); - auto* target = scratch; - for (size_t width = 1; width < count; width *= 2) { - for (size_t low = 0; low < count; low += 2 * width) { - const size_t middle = std::min(low + width, count); - const size_t high = std::min(low + 2 * width, count); - size_t left = low, right = middle, out = low; - while (left < middle && right < high) { - const size_t taken = compare(source + left * size, source + right * size) <= 0 ? left++ : right++; - std::memcpy(target + out++ * size, source + taken * size, size); - } - std::memcpy(target + out * size, source + left * size, (middle - left) * size); - out += middle - left; - std::memcpy(target + out * size, source + right * size, (high - right) * size); - } - std::swap(source, target); - } - if (source != base) std::memcpy(base, source, count * size); - free_nid_postfix(scratch); - - return 0; -} - int APS5_VABI heapsort_nid_postfix(void* base, size_t count, size_t size, GuestCompare compare) { if (count <= 1) return 0; if (size == 0) { diff --git a/core/libs/prx/libc/src/Mergesort.cpp b/core/libs/prx/libc/src/Mergesort.cpp new file mode 100644 index 00000000000..7c7d92362d4 --- /dev/null +++ b/core/libs/prx/libc/src/Mergesort.cpp @@ -0,0 +1,241 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::size_t LinkSize = sizeof(unsigned char*); +constexpr std::size_t NaturalRunThreshold = 16; +constexpr std::size_t GallopAfter = 6; + +unsigned char* LinkSlot(unsigned char* position) { + const auto address = reinterpret_cast(position); + return reinterpret_cast((address + LinkSize - 1) & ~static_cast(LinkSize - 1)); +} + +unsigned char* LoadLink(unsigned char* position) { + unsigned char* link; + std::memcpy(&link, LinkSlot(position), sizeof(link)); + return link; +} + +void StoreLink(unsigned char* position, unsigned char* link) { + std::memcpy(LinkSlot(position), &link, sizeof(link)); +} + +class MergeSorter { +public: + MergeSorter(std::size_t size, GuestCompare compare) : size(size), compare(compare) {} + + unsigned char* Sort(unsigned char* base, std::size_t count, unsigned char* scratch) { + unsigned char* list1 = base; + unsigned char* list2 = scratch; + Setup(list1, list2, count); + unsigned char* last = list2 + count * size; + while (LoadLink(list2) != last) { + MergePass(list1, list2, last); + std::swap(list1, list2); + last = list2 + count * size; + } + if (list2 == base) { + std::memmove(list2, list1, count * size); + list2 = list1; + } + return list2; + } + +private: + int Compare(const unsigned char* left, const unsigned char* right) const { + return compare(left, right); + } + + void SwapElements(unsigned char* left, unsigned char* right) const { + std::swap_ranges(left, left + size, right); + } + + void Reverse(unsigned char* bottom, unsigned char* top) const { + do { + SwapElements(bottom, top); + bottom += size; + top -= size; + } while (bottom < top); + } + + void InsertionSort(unsigned char* first, std::size_t count) const { + for (unsigned char* inserted = first + size; --count >= 1; inserted += size) { + for (unsigned char* current = inserted; current > first; current -= size) { + unsigned char* previous = current - size; + if (Compare(previous, current) <= 0) break; + SwapElements(previous, current); + } + } + } + + static void Link(unsigned char*& tail, unsigned char* next) { + StoreLink(tail, next); + tail = next; + } + + void Setup(unsigned char* list1, unsigned char* list2, std::size_t count) const { + const std::size_t pair = size * 2; + if (count <= 5) { + InsertionSort(list1, count); + StoreLink(list2, list2 + count * size); + return; + } + const std::size_t tailCount = 4 + (count & 1); + InsertionSort(list1 + (count - tailCount) * size, tailCount); + unsigned char* const last = list1 + size * (count - tailCount); + StoreLink(list2 + (last - list1), list2 + count * size); + unsigned char* tail = list2; + unsigned char* first = list1; + bool descending = Compare(first, first + size) > 0; + for (; first < last; descending = !descending) { + std::size_t length = 2; + unsigned char* next = first + pair; + for (; next < last; next += pair) { + if ((Compare(next, next + size) > 0) != descending) break; + length += 2; + } + if (length < NaturalRunThreshold) { + do { + Link(tail, list2 + (first + pair - list1)); + if (descending) SwapElements(first, first + size); + } while ((first += pair) < next); + continue; + } + unsigned char* const end = next; + for (next = first + pair; next < end; next += pair) { + if ((Compare(next - size, next) > 0) != descending) { + Link(tail, list2 + (next - list1)); + if (descending) Reverse(first, next - size); + first = next; + } + } + if (descending) Reverse(first, next - size); + first = next; + if (next < last || Compare(next - size, next) > 0) Link(tail, list2 + (next - list1)); + else Link(tail, list2 + count * size); + } + } + + void Gallop(const unsigned char* key, unsigned char*& low, unsigned char*& high, int sense, bool& galloping) const { + std::size_t step = size; + for (;; step <<= 1) { + if (step >= static_cast(high - low)) { + unsigned char* const probe = high - size; + if (probe > low && Compare(key, probe) <= sense) high = probe; + else low = probe; + while (high > low + size) { + unsigned char* const middle = low + (((high - low) / size) >> 1) * size; + if (Compare(key, middle) <= sense) high = middle; + else low = middle; + } + return; + } + unsigned char* const probe = low + step; + if (Compare(key, probe) <= sense) { + high = probe; + if (step == size) galloping = false; + break; + } + low = probe; + } + while (step > size) { + step >>= 1; + unsigned char* const probe = low + step; + if (Compare(key, probe) <= sense) high = probe; + else low = probe; + } + } + + void MergePass(unsigned char* list1, unsigned char* list2, unsigned char* last) { + unsigned char* end2 = list1; + unsigned char* runLink = list1; + unsigned char* target = list2; + auto copyRun = [&target](unsigned char*& from, const unsigned char* to) { + const auto bytes = static_cast(to - from); + std::memcpy(target, from, bytes); + target += bytes; + from += bytes; + }; + for (unsigned char* link = list2; link != last; runLink = end2) { + link = LoadLink(link); + unsigned char* first1 = end2; + unsigned char* const end1 = list1 + (link - list2); + unsigned char* first2 = end1; + if (link != last) link = LoadLink(link); + end2 = list1 + (link - list2); + while (first1 < end1 && first2 < end2) { + const unsigned char* key; + unsigned char* low; + unsigned char* high; + int sense; + if (Compare(first1, first2) <= 0) { + key = first2; + low = first1; + high = end1; + sense = -1; + } else { + key = first1; + low = first2; + high = end2; + sense = 0; + } + bool gallop = galloping; + if (!galloping) { + while ((low += size) < high && Compare(key, low) > sense) { + if (++linearWins == GallopAfter) { + galloping = true; + gallop = true; + break; + } + } + } + if (gallop) { + Gallop(key, low, high, sense, galloping); + low = high; + } + linearWins = 0; + if (key == first1) { + copyRun(first2, low); + copyRun(first1, first1 + size); + } else { + copyRun(first1, low); + copyRun(first2, first2 + size); + } + } + if (first2 < end2) copyRun(first2, end2); + else if (first1 < end1) copyRun(first1, end1); + StoreLink(runLink, end2); + } + } + + std::size_t size; + GuestCompare compare; + std::size_t linearWins = 0; + bool galloping = false; +}; + +} + +int APS5_VABI mergesort_nid_postfix(void* base, size_t count, size_t size, GuestCompare compare) { + if (size < LinkSize / 2) { + errno = 22; + return -1; + } + if (count == 0) return 0; + if (!compare) throw std::invalid_argument("mergesort: null comparator"); + if (!base) throw std::invalid_argument("mergesort: null base"); + if (count > (std::numeric_limits::max() - LinkSize) / size) throw std::overflow_error("mergesort: array size overflow"); + auto* const scratch = static_cast(malloc_nid_postfix(count * size + LinkSize)); + if (!scratch) return -1; + MergeSorter sorter(size, compare); + free_nid_postfix(sorter.Sort(static_cast(base), count, scratch)); + return 0; +} diff --git a/core/libs/tests/GuestQsort.cpp b/core/libs/tests/GuestQsort.cpp index 17a380e5c76..9d6f516df94 100644 --- a/core/libs/tests/GuestQsort.cpp +++ b/core/libs/tests/GuestQsort.cpp @@ -211,6 +211,79 @@ bool CheckMergesort() { return correct; } +std::vector mergesortCalls; + +int APS5_VABI RecordKeys(const void* left, const void* right) { + const auto* first = static_cast(left); + const auto* second = static_cast(right); + mergesortCalls.push_back(first->tag * 100 + second->tag); + return (first->key > second->key) - (first->key < second->key); +} + +bool CheckMergesortComparisons() { + InitializeHeap(); + struct Case { + std::vector keys; + std::vector calls; + }; + const std::vector cases = { + {{3, 1, 2, 5, 4}, {1, 2, 102, 3, 304, 4}}, + {{9, 4, 7, 1, 8, 2, 6, 3, 5, 0, 9, 4, 7, 1, 8, 2, 6, 3, 5, 0, 11}, + {1617, 1618, 1718, 1619, 1819, 1719, 1620, 1, 203, 405, 607, 809, 1011, 1213, 1415, 102, 304, 506, 708, 910, 1112, 1314, 1419, 103, 102, 2, 507, 704, + 406, 911, 1108, 810, 1315, 1512, 1214, 305, 501, 107, 106, 206, 204, 4, 913, 1311, 1115, 1112, 812, 1210, 1014, 309, 313, 513, 515, 715, 711, 111, + 1106, 608, 612, 212, 1204, 414, 1400, 10, 919, 1903, 317, 1713, 1705, 1715, 1707, 1701, 118, 1811, 1808, 1806, 616, 1602, 220, 2012, 2004, 2014, 2000, + 2010}}, + {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0, 0, 30, 29, 28, 27}, + {2223, 2224, 2324, 2225, 2325, 2425, 1, 203, 405, 607, 809, 1011, 1213, 1415, 1617, 1819, 2021, 102, 304, 506, 708, 910, 1112, 1314, 1516, 1718, + 1920, 2125, 20, 21, 25, 125, 2502, 2503, 2504, 2505, 2506, 2507, 2508, 2510, 2514, 2519}}, + {{40, 41, 42, 43, 44, 45, 46, 47, 0, 1, 2, 3, 4, 5, 6, 7, 48, 49, 50, 51, 52, 53, 54, 55, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + {3031, 3132, 3233, 1, 203, 405, 607, 809, 1011, 1213, 1415, 1617, 1819, 2021, 2223, 2425, 2627, 2829, 102, 304, 506, 708, 910, 1112, 1314, 1516, 1718, + 1920, 2122, 2324, 2526, 2728, 2930, 8, 9, 10, 11, 12, 13, 14, 15, 17, 16, 116, 1602, 1604, 1607, 824, 2409, 2411, 2415, 2407, 2403, 2401, 2400, 25, 26, + 28, 32, 33}}, + }; + bool correct = true; + for (const auto& sample : cases) { + std::vector elements; + for (std::size_t index = 0; index < sample.keys.size(); ++index) elements.push_back({sample.keys[index], static_cast(index)}); + mergesortCalls.clear(); + correct &= mergesort_nid_postfix(elements.data(), elements.size(), sizeof(Element), RecordKeys) == 0; + if (mergesortCalls != sample.calls) { + std::fprintf(stderr, "mergesort of %zu elements made %zu comparisons in a different order than FreeBSD's merge.c (%zu expected)\n", + sample.keys.size(), mergesortCalls.size(), sample.calls.size()); + correct = false; + } + for (std::size_t index = 1; index < elements.size(); ++index) { + correct &= elements[index - 1].key < elements[index].key || + (elements[index - 1].key == elements[index].key && elements[index - 1].tag < elements[index].tag); + } + } + return correct; +} + +bool CheckMergesortUnalignedElements() { + InitializeHeap(); + constexpr std::size_t Size = 12; + constexpr std::size_t Count = 1000; + std::vector storage(Count * Size + 8); + unsigned char* const base = storage.data() + 4; + for (std::size_t index = 0; index < Count; ++index) { + const Element element{static_cast((index / 50) % 2 ? Count - index : (index * 7919) % 13), static_cast(index)}; + std::memcpy(base + index * Size, &element, sizeof(element)); + std::memset(base + index * Size + sizeof(element), static_cast(index & 0xff), Size - sizeof(element)); + } + bool correct = mergesort_nid_postfix(base, Count, Size, CompareKeys) == 0; + Element previous{}; + for (std::size_t index = 0; index < Count; ++index) { + Element element; + std::memcpy(&element, base + index * Size, sizeof(element)); + correct &= base[index * Size + sizeof(element)] == static_cast(element.tag & 0xff); + if (index != 0) correct &= previous.key < element.key || (previous.key == element.key && previous.tag < element.tag); + previous = element; + } + if (!correct) std::fprintf(stderr, "mergesort of unaligned 12-byte elements failed\n"); + return correct; +} + bool CheckHeapsort() { bool correct = true; for (const std::size_t count : {0, 1, 2, 3, 7, 16, 33, 1000}) { @@ -252,6 +325,8 @@ int main() { for (const std::size_t count : {0, 1, 2, 6, 7, 8, 40, 41, 1000}) correct &= ExpectSorted(size, count); correct &= CheckMergesort(); + correct &= CheckMergesortComparisons(); + correct &= CheckMergesortUnalignedElements(); correct &= CheckHeapsort(); return correct ? 0 : 1; } diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 3ed6fb9b402..6c24e99ea45 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -149,7 +149,7 @@ - [atanhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `atanhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_atanhf.c`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_atanhf.c) calling msun's [`log1pf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_log1pf.c), whose paths for the positive arguments it passes are inlined here; the exported `log1pf` is still the host's. For |x| > 1, the infinities and NaN, Sony's error helper raises `FE_INVALID` and sets `errno` to `EDOM` before returning `(x - x) / (x - x)`, and for |x| = 1 it raises `FE_DIVBYZERO` and sets `errno` to `ERANGE` before returning `x / 0`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits, `errno` and floating-point exceptions as this implementation for all 2^32 arguments; it was not run on Linux. That the system `libSceLibcInternal`, which Dusklight (PPSA99305) imports while bundling a zero-return `libc.prx`, has the same code is assumed - [j0, j1, jn, y0, y1 and yn](../../../core/libs/prx/libc/src/Bessel.cpp) (libc) call the host's `sin`, `cos`, `sincos` and `log`, so the last bits can differ from the console. The rest follows the system `libc.prx` built as `J03093718` (disassembly), which given the console's own `sin`, `cos`, `sincos` and `log` gave the same bits and exception flags for 90 million arguments: msun's [`e_j0.c`, `e_j1.c` and `e_jn.c`](https://github.com/freebsd/freebsd-src/tree/release/10.0.0/lib/msun/src) with each multiply-add the console fuses done as one FMA. As in that release, before FreeBSD commit `186f620727`, `y0`, `y1` and `yn` return `-inf` for ±0 and `0x7ff8000000000000` for a negative argument without raising an exception. The order wraps at 32 bits as on the console: `jn(1 << 30, x)` never returns for 2^-29 <= |x| < 2^30 - [getenv, setenv, unsetenv, putenv](../../../core/libs/prx/libc/src/Environment.cpp) (libc) keep one guest environment that starts empty, like the `environ` libkernel exports, and never reads or changes the host's. A title sees only the variables it sets itself; host variables such as `HOME` and `PATH` name host paths a guest cannot use (PS5MC, PPSA99420, exits with `XBMC_Run returned -1` when it gets the host's `HOME`). Whether the console starts a title with any variables set is not measured -- [mergesort](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libSceLibcInternal, implemented in libc) checks its arguments like the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x762D0): an element size under 4 sets `errno` to `EINVAL` (22) and returns -1, an empty array returns 0, and a failed `malloc` of the count times the size plus 8 bytes returns -1. The sort is a stable bottom-up merge, not FreeBSD's natural merge with its insertion sort for short runs, so the comparator is called in a different order and number of times; the result is the same for a consistent comparator. A null base or comparator and a size product that overflows, which the console does not check, throw +- [mergesort](../../../core/libs/prx/libc/src/Mergesort.cpp) (libSceLibcInternal, implemented in libc) - a null base or comparator and a size product that overflows throw, where the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x762D0) does not check them. The sort follows FreeBSD 11.4's [`merge.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/libc/stdlib/merge.c), whose argument checks that library matches (an element size under 4 sets `errno` to `EINVAL` and returns -1, a failed `malloc` of the count times the size plus 8 bytes returns -1); its order of comparator calls is checked against libbsd's copy of `merge.c`, not on a console - [Exception runtime on macOS](../../../core/libs/prx/libc/src/exception/Exports.cpp) (libc) - on macOS an exception that a library throws with a plain `throw` and that nothing catches skips the guest's `std::set_terminate` handler and the guest runtime's report: it ends in libc++abi's terminate, because the host keeps libc++abi there, while on Linux and Windows libc's runtime takes these throws. The `SIGALRM` expiry of `alarm` and `setitimer` throws through the guest runtime on macOS; `NotImplemented_nid_no_patch` cannot, since host code catches its `std::runtime_error`, so an unimplemented call inside a `noexcept` export (`mmap` on an `app0.zar` file) bypasses the guest handler - On macOS the [write watch](../../../core/libs/prx/libc/src/GuestWriteWatch.cpp) write-protects watched pages and records the first write to each from a SIGBUS/SIGSEGV handler, because macOS has no counterpart to `userfaultfd` write-protect or `MEM_WRITE_WATCH`. The protection is real, so: - a system call that writes into a protected page fails with `EFAULT` instead of faulting. `GuestArena::HostWrite` lifts the protection for the calls it brackets (file reads and socket receives), as [Host writes into guest memory](../CONVENTIONS.md#host-writes-into-guest-memory) requires; a host call that writes guest memory without it (`clock_gettime` and the like into a watched page) fails; From 6c42e7428236bdc4a2852de462c0ab312126bb0a Mon Sep 17 00:00:00 2001 From: B-Howell <116104752+B-Howell@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:59:27 -0400 Subject: [PATCH 2/3] fix(libc): retain FreeBSD merge.c copyright and license notice in mergesort Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Mergesort.cpp | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/libs/prx/libc/src/Mergesort.cpp b/core/libs/prx/libc/src/Mergesort.cpp index 7c7d92362d4..a81eece9f97 100644 --- a/core/libs/prx/libc/src/Mergesort.cpp +++ b/core/libs/prx/libc/src/Mergesort.cpp @@ -1,3 +1,33 @@ +// SPDX-FileCopyrightText: Copyright (c) 1992, 1993 The Regents of the University of California. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause +// Adapted from FreeBSD release/11.4.0 lib/libc/stdlib/merge.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/libc/stdlib/merge.c +// +// This code is derived from software contributed to Berkeley by Peter McIlroy. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the University nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. + #include "prx/libc/include/Api.hpp" #include #include From e80f7038fdaa6809ad361cb309ded3265d855d32 Mon Sep 17 00:00:00 2001 From: B-Howell <116104752+B-Howell@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:06:24 -0400 Subject: [PATCH 3/3] fix(libc): keep merge.c's notice block verbatim in mergesort Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Mergesort.cpp | 57 +++++++++++++++------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/core/libs/prx/libc/src/Mergesort.cpp b/core/libs/prx/libc/src/Mergesort.cpp index a81eece9f97..fa06c44ed14 100644 --- a/core/libs/prx/libc/src/Mergesort.cpp +++ b/core/libs/prx/libc/src/Mergesort.cpp @@ -1,32 +1,37 @@ // SPDX-FileCopyrightText: Copyright (c) 1992, 1993 The Regents of the University of California. All rights reserved. // SPDX-License-Identifier: BSD-3-Clause // Adapted from FreeBSD release/11.4.0 lib/libc/stdlib/merge.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/libc/stdlib/merge.c -// -// This code is derived from software contributed to Berkeley by Peter McIlroy. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// 3. Neither the name of the University nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -// SUCH DAMAGE. +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ #include "prx/libc/include/Api.hpp" #include