feat(stdlib): rand/div/qsort/bsearch
This commit is contained in:
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
* vlibc — rand/srand, abs/div, qsort/bsearch test (todo 12).
|
||||
*
|
||||
* Exercises the stdlib todo-12 additions end to end:
|
||||
*
|
||||
* 1. qsort on 0 and 1 element (the comparator is never called), 2048
|
||||
* pseudo-random ints, duplicate-heavy, reverse-sorted, and
|
||||
* all-equal arrays — each verified ascending with the element-sum
|
||||
* checksum preserved.
|
||||
* 2. bsearch finds a present key and returns NULL for an absent one.
|
||||
* 3. rand is deterministic per seed and stays inside [0, RAND_MAX];
|
||||
* div(7,3) == {2,1}, div(-7,3) == {-2,-1}, ldiv/lldiv spot checks;
|
||||
* abs/labs/llabs basics plus the INT_MIN/LONG_MIN/LLONG_MIN wrap
|
||||
* through volatile locals so the compiler cannot fold the negation.
|
||||
* 4. Level 2: rand_r determinism and independence from rand()'s
|
||||
* global state, srand48 reseed determinism, drand48 samples in
|
||||
* [0,1), lrand48 in [0,2^31), erand48 stepping the caller's xsubi.
|
||||
*
|
||||
* The instrumented comparator counts calls and records any call where
|
||||
* both arguments point at the same element; every scenario asserts the
|
||||
* self-compare flag stays clear.
|
||||
*
|
||||
* The -f mode stress-sorts three static-BSS arrays of 100000 ints
|
||||
* (reverse-sorted, already-sorted, all-equal) with the instrumented
|
||||
* comparator, verifies ascending order and an O(n log n) comparison
|
||||
* count (< 5.2M, far below the ~5e9 an O(n^2) sort would need), and
|
||||
* exits plainly — nothing here writes errno, so a normal return is
|
||||
* safe.
|
||||
*
|
||||
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude
|
||||
* the vlibc public headers shadow GCC's internal ones, so a host
|
||||
* <stdio.h> would not compile. The stdlib.h below is vlibc's own.
|
||||
*
|
||||
* Not part of the library proper; compiled manually for this todo (the
|
||||
* tests/ + make check wiring is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "../include/stdlib.h"
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||
static void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
/* Write v in decimal to fd. */
|
||||
static void
|
||||
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
char buf[24];
|
||||
int i = (int)sizeof(buf);
|
||||
|
||||
buf[--i] = '\0';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/* xorshift32; deterministic, allocation-independent. */
|
||||
static unsigned
|
||||
rng_next(unsigned *state)
|
||||
{
|
||||
unsigned x = *state;
|
||||
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
*state = x;
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Instrumented int comparator: counts calls and records self-compares. */
|
||||
static long compar_count;
|
||||
static int self_compare_seen;
|
||||
|
||||
static int
|
||||
int_cmp(const void *a, const void *b)
|
||||
{
|
||||
const int *pa = a;
|
||||
const int *pb = b;
|
||||
|
||||
compar_count++;
|
||||
if (a == b)
|
||||
{
|
||||
self_compare_seen = 1;
|
||||
}
|
||||
if (*pa < *pb)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (*pa > *pb)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sort arr (n ints) and verify ascending order and a preserved sum. */
|
||||
static int
|
||||
sort_and_verify(int *arr, size_t n, const char *what)
|
||||
{
|
||||
size_t i;
|
||||
long long before = 0;
|
||||
long long after = 0;
|
||||
int asc = 1;
|
||||
int ok = 1;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
before += arr[i];
|
||||
}
|
||||
compar_count = 0;
|
||||
qsort(arr, n, sizeof(int), int_cmp);
|
||||
for (i = 1; i < n; i++)
|
||||
{
|
||||
if (arr[i - 1] > arr[i])
|
||||
{
|
||||
asc = 0;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
after += arr[i];
|
||||
}
|
||||
if (!asc)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, " - not ascending\n");
|
||||
failures++;
|
||||
ok = 0;
|
||||
}
|
||||
if (before != after)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, " - element sum changed\n");
|
||||
failures++;
|
||||
ok = 0;
|
||||
}
|
||||
if (ok)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void
|
||||
qsort_scenarios(void)
|
||||
{
|
||||
enum
|
||||
{
|
||||
N = 2048
|
||||
};
|
||||
int arr[N];
|
||||
unsigned i;
|
||||
unsigned rng = 0x2545F491U;
|
||||
int one = 7;
|
||||
|
||||
compar_count = 0;
|
||||
qsort(arr, 0, sizeof(int), int_cmp);
|
||||
qsort(&one, 1, sizeof(int), int_cmp);
|
||||
check(compar_count == 0, "qsort(0) and qsort(1) never call the comparator");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = (int)rng_next(&rng);
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 pseudo-random ints sort");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = (int)(i % 7);
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 duplicate-heavy ints sort");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = (int)(N - 1 - i);
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 reverse-sorted ints sort");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = 5;
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 all-equal ints sort");
|
||||
|
||||
check(self_compare_seen == 0, "comparator never saw the same element twice");
|
||||
}
|
||||
|
||||
static void
|
||||
bsearch_tests(void)
|
||||
{
|
||||
int arr[16];
|
||||
unsigned i;
|
||||
int key;
|
||||
int *p;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
arr[i] = (int)(i * 2);
|
||||
}
|
||||
key = 14;
|
||||
p = bsearch(&key, arr, 16, sizeof(int), int_cmp);
|
||||
check(p != NULL && *p == key, "bsearch finds a present key");
|
||||
key = 13;
|
||||
p = bsearch(&key, arr, 16, sizeof(int), int_cmp);
|
||||
check(p == NULL, "bsearch returns NULL for an absent key");
|
||||
check(self_compare_seen == 0, "bsearch never self-compared");
|
||||
}
|
||||
|
||||
static void
|
||||
rand_tests(void)
|
||||
{
|
||||
int a[8];
|
||||
int b[8];
|
||||
unsigned i;
|
||||
int same = 1;
|
||||
int in_range = 1;
|
||||
|
||||
srand(1);
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
a[i] = rand();
|
||||
}
|
||||
srand(1);
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
b[i] = rand();
|
||||
}
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
same = 0;
|
||||
}
|
||||
}
|
||||
check(same, "srand(1) twice gives identical rand sequences");
|
||||
srand(12345);
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
int v = rand();
|
||||
|
||||
if (v < 0 || v > RAND_MAX)
|
||||
{
|
||||
in_range = 0;
|
||||
}
|
||||
}
|
||||
check(in_range, "rand values stay within [0, RAND_MAX]");
|
||||
}
|
||||
|
||||
static void
|
||||
div_tests(void)
|
||||
{
|
||||
div_t d = div(7, 3);
|
||||
ldiv_t ld = ldiv(17L, 5L);
|
||||
ldiv_t ldn = ldiv(-17L, 5L);
|
||||
lldiv_t lld = lldiv(1000000000000000LL, 3LL);
|
||||
|
||||
check(d.quot == 2 && d.rem == 1, "div(7,3) == {2,1}");
|
||||
d = div(-7, 3);
|
||||
check(d.quot == -2 && d.rem == -1, "div(-7,3) == {-2,-1}");
|
||||
check(ld.quot == 3L && ld.rem == 2L, "ldiv(17,5) == {3,2}");
|
||||
check(ldn.quot == -3L && ldn.rem == -2L, "ldiv(-17,5) == {-3,-2}");
|
||||
check(lld.quot == 333333333333333LL && lld.rem == 1LL, "lldiv(10^15,3) == {333333333333333,1}");
|
||||
}
|
||||
|
||||
/*
|
||||
* noipa proxies for the abs family: GCC knows abs/labs/llabs as const
|
||||
* builtins whose result lies in [0, type_MAX], so even with a volatile
|
||||
* argument its value-range analysis proves `abs(vi) == INT_MIN` false
|
||||
* and folds the check before the call. noipa blocks interprocedural
|
||||
* propagation through the proxy (the repo's established pattern, see
|
||||
* the SIZE_MAX-10 handling above); the volatile locals then force the
|
||||
* wrap values to exist at runtime.
|
||||
*/
|
||||
static __attribute__((noipa)) int
|
||||
abs_proxy(int n)
|
||||
{
|
||||
return abs(n);
|
||||
}
|
||||
|
||||
static __attribute__((noipa)) long
|
||||
labs_proxy(long n)
|
||||
{
|
||||
return labs(n);
|
||||
}
|
||||
|
||||
static __attribute__((noipa)) long long
|
||||
llabs_proxy(long long n)
|
||||
{
|
||||
return llabs(n);
|
||||
}
|
||||
|
||||
static void
|
||||
abs_tests(void)
|
||||
{
|
||||
volatile int vi = INT_MIN;
|
||||
volatile long vl = LONG_MIN;
|
||||
volatile long long vll = LLONG_MIN;
|
||||
|
||||
check(abs(-7) == 7 && abs(7) == 7, "abs basics");
|
||||
check(labs(-7L) == 7L, "labs basics");
|
||||
check(llabs(-7LL) == 7LL, "llabs basics");
|
||||
check(abs_proxy(vi) == INT_MIN, "abs(INT_MIN) wraps to INT_MIN");
|
||||
check(labs_proxy(vl) == LONG_MIN, "labs(LONG_MIN) wraps to LONG_MIN");
|
||||
check(llabs_proxy(vll) == LLONG_MIN, "llabs(LLONG_MIN) wraps to LLONG_MIN");
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
static void
|
||||
rand_r_tests(void)
|
||||
{
|
||||
unsigned s1 = 123;
|
||||
unsigned s2 = 123;
|
||||
unsigned s3 = 99;
|
||||
int r1 = rand_r(&s1);
|
||||
int r2 = rand_r(&s2);
|
||||
int before;
|
||||
int after;
|
||||
|
||||
check(r1 == r2 && s1 == s2, "rand_r: equal seeds give equal results and state");
|
||||
srand(7);
|
||||
before = rand();
|
||||
rand_r(&s3);
|
||||
rand();
|
||||
srand(7);
|
||||
after = rand();
|
||||
check(before == after, "rand_r does not disturb rand's global state");
|
||||
s1 = 5;
|
||||
s2 = 5;
|
||||
rand();
|
||||
rand();
|
||||
check(rand_r(&s1) == rand_r(&s2), "rand_r independent of interleaved rand() calls");
|
||||
}
|
||||
|
||||
static void
|
||||
drand48_tests(void)
|
||||
{
|
||||
double d1;
|
||||
double d2;
|
||||
unsigned i;
|
||||
int ok = 1;
|
||||
unsigned short xs[3] = {0x330E, 0xABCD, 0x1234};
|
||||
double e1 = erand48(xs);
|
||||
|
||||
srand48(42);
|
||||
d1 = drand48();
|
||||
srand48(42);
|
||||
d2 = drand48();
|
||||
check(d1 == d2, "srand48 reseed gives deterministic drand48");
|
||||
srand48(1);
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
double v = drand48();
|
||||
|
||||
if (!(v >= 0.0 && v < 1.0))
|
||||
{
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
check(ok, "drand48: 1000 samples in [0,1)");
|
||||
srand48(2);
|
||||
ok = 1;
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
long v = lrand48();
|
||||
|
||||
if (v < 0 || v >= 2147483648L)
|
||||
{
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
check(ok, "lrand48: 1000 samples in [0, 2^31)");
|
||||
check(!(xs[0] == 0x330E && xs[1] == 0xABCD && xs[2] == 0x1234),
|
||||
"erand48 steps the caller's xsubi in place");
|
||||
srand48(0x1234ABCDL);
|
||||
check(e1 == drand48(), "erand48(default xsubi) matches srand48(0x1234ABCD) drand48");
|
||||
}
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/* -f mode: three 100k-element stress sorts from static BSS (no malloc). */
|
||||
#define BIG_N 100000
|
||||
|
||||
static int big_array[BIG_N];
|
||||
|
||||
static int
|
||||
stress_case(const char *label)
|
||||
{
|
||||
unsigned i;
|
||||
int asc = 1;
|
||||
|
||||
compar_count = 0;
|
||||
qsort(big_array, BIG_N, sizeof(int), int_cmp);
|
||||
for (i = 1; i < BIG_N; i++)
|
||||
{
|
||||
if (big_array[i - 1] > big_array[i])
|
||||
{
|
||||
asc = 0;
|
||||
}
|
||||
}
|
||||
say(1, label);
|
||||
say(1, " comparisons: ");
|
||||
say_dec(1, (unsigned long)compar_count);
|
||||
say(1, "\n");
|
||||
if (!asc)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, label);
|
||||
say(2, " - not ascending\n");
|
||||
return 1;
|
||||
}
|
||||
if (compar_count >= 5200000L)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, label);
|
||||
say(2, " - comparisons >= 5200000 (not O(n log n))\n");
|
||||
return 1;
|
||||
}
|
||||
if (self_compare_seen)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, label);
|
||||
say(2, " - self-compare observed\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "PASS: ");
|
||||
say(1, label);
|
||||
say(1, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
stress_mode(void)
|
||||
{
|
||||
unsigned i;
|
||||
int rc = 0;
|
||||
|
||||
self_compare_seen = 0;
|
||||
for (i = 0; i < BIG_N; i++)
|
||||
{
|
||||
big_array[i] = (int)(BIG_N - 1 - i);
|
||||
}
|
||||
rc |= stress_case("100k reverse-sorted");
|
||||
|
||||
for (i = 0; i < BIG_N; i++)
|
||||
{
|
||||
big_array[i] = (int)i;
|
||||
}
|
||||
rc |= stress_case("100k already-sorted");
|
||||
|
||||
for (i = 0; i < BIG_N; i++)
|
||||
{
|
||||
big_array[i] = 42;
|
||||
}
|
||||
rc |= stress_case("100k all-equal");
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
return stress_mode();
|
||||
}
|
||||
|
||||
qsort_scenarios();
|
||||
bsearch_tests();
|
||||
rand_tests();
|
||||
div_tests();
|
||||
abs_tests();
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
rand_r_tests();
|
||||
drand48_tests();
|
||||
#endif
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all rand/div/qsort/bsearch tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user