summaryrefslogtreecommitdiffstats
path: root/regressions/ck_ec
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-07-23 11:29:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-07-23 11:37:28 +0000
commita7283ab143d4e95e8f5f22b58c61cb4e2f604749 (patch)
tree3ec5165ac7f1299f5c0dc3e41d7560a06e6267f5 /regressions/ck_ec
parentAdding debian version 0.6.0-2. (diff)
downloadck-a7283ab143d4e95e8f5f22b58c61cb4e2f604749.tar.xz
ck-a7283ab143d4e95e8f5f22b58c61cb4e2f604749.zip
Merging upstream version 0.7.1 (Closes: #991419).
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'regressions/ck_ec')
-rw-r--r--regressions/ck_ec/benchmark/Makefile18
-rw-r--r--regressions/ck_ec/benchmark/ck_ec.c484
-rw-r--r--regressions/ck_ec/validate/Makefile73
-rw-r--r--regressions/ck_ec/validate/ck_ec_smoke_test.c450
-rw-r--r--regressions/ck_ec/validate/fuzz_harness.h95
-rw-r--r--regressions/ck_ec/validate/prop_test_slow_wakeup.c110
-rw-r--r--regressions/ck_ec/validate/prop_test_timeutil_add.c101
-rw-r--r--regressions/ck_ec/validate/prop_test_timeutil_add_ns.c88
-rw-r--r--regressions/ck_ec/validate/prop_test_timeutil_cmp.c99
-rw-r--r--regressions/ck_ec/validate/prop_test_timeutil_scale.c41
-rw-r--r--regressions/ck_ec/validate/prop_test_value.c150
-rw-r--r--regressions/ck_ec/validate/prop_test_wakeup.c193
12 files changed, 1902 insertions, 0 deletions
diff --git a/regressions/ck_ec/benchmark/Makefile b/regressions/ck_ec/benchmark/Makefile
new file mode 100644
index 0000000..c266023
--- /dev/null
+++ b/regressions/ck_ec/benchmark/Makefile
@@ -0,0 +1,18 @@
+.PHONY: check clean distribution
+
+OBJECTS=ck_ec
+
+all: $(OBJECTS)
+
+ck_ec: ck_ec.c ../../../include/ck_ec.h
+ $(CC) $(CFLAGS) ../../../src/ck_ec.c -o ck_ec ck_ec.c
+
+check: all
+ ./ck_ec $(CORES) 1
+
+clean:
+ rm -rf *~ *.o $(OBJECTS) *.dSYM *.exe
+
+include ../../../build/regressions.build
+CFLAGS+=-D_GNU_SOURCE
+
diff --git a/regressions/ck_ec/benchmark/ck_ec.c b/regressions/ck_ec/benchmark/ck_ec.c
new file mode 100644
index 0000000..655f9d8
--- /dev/null
+++ b/regressions/ck_ec/benchmark/ck_ec.c
@@ -0,0 +1,484 @@
+/*
+ * Copyright 2018 Paul Khuong.
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <assert.h>
+#include <ck_cc.h>
+#include <ck_ec.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "../../common.h"
+
+#ifndef STEPS
+#define STEPS (65536 * 64)
+#endif
+
+static int gettime(const struct ck_ec_ops *, struct timespec *out);
+static void wake32(const struct ck_ec_ops *, const uint32_t *);
+static void wait32(const struct ck_ec_wait_state *,
+ const uint32_t *, uint32_t, const struct timespec *);
+static void wake64(const struct ck_ec_ops *, const uint64_t *);
+static void wait64(const struct ck_ec_wait_state *,
+ const uint64_t *, uint64_t, const struct timespec *);
+
+static const struct ck_ec_ops test_ops = {
+ .gettime = gettime,
+ .wait32 = wait32,
+ .wait64 = wait64,
+ .wake32 = wake32,
+ .wake64 = wake64
+};
+
+#ifndef __linux__
+static int gettime(const struct ck_ec_ops *ops, struct timespec *out)
+{
+ (void)out;
+
+ assert(ops == &test_ops);
+ return -1;
+}
+
+static void wait32(const struct ck_ec_wait_state *state,
+ const uint32_t *address, uint32_t expected,
+ const struct timespec *deadline)
+{
+ (void)address;
+ (void)expected;
+ (void)deadline;
+
+ assert(state->ops == &test_ops);
+ return;
+}
+
+static void wait64(const struct ck_ec_wait_state *state,
+ const uint64_t *address, uint64_t expected,
+ const struct timespec *deadline)
+{
+ (void)address;
+ (void)expected;
+ (void)deadline;
+
+ assert(state->ops == &test_ops);
+ return;
+}
+
+static void wake32(const struct ck_ec_ops *ops, const uint32_t *address)
+{
+ (void)address;
+
+ assert(ops == &test_ops);
+ return;
+}
+
+static void wake64(const struct ck_ec_ops *ops, const uint64_t *address)
+{
+ (void)address;
+
+ assert(ops == &test_ops);
+ return;
+}
+#else
+#include <linux/futex.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+static int gettime(const struct ck_ec_ops *ops, struct timespec *out)
+{
+ assert(ops == &test_ops);
+ return clock_gettime(CLOCK_MONOTONIC, out);
+}
+
+static void wait32(const struct ck_ec_wait_state *state,
+ const uint32_t *address, uint32_t expected,
+ const struct timespec *deadline)
+{
+ assert(state->ops == &test_ops);
+ syscall(SYS_futex, address,
+ FUTEX_WAIT_BITSET, expected, deadline,
+ NULL, FUTEX_BITSET_MATCH_ANY, 0);
+ return;
+}
+
+static void wait64(const struct ck_ec_wait_state *state,
+ const uint64_t *address, uint64_t expected,
+ const struct timespec *deadline)
+{
+ const void *low_half;
+
+ assert(state->ops == &test_ops);
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ low_half = address;
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ low_half = (uintptr_t)address + sizeof(uint32_t);
+#else
+# error "__BYTE_ORDER__ must be defined."
+#endif
+
+ syscall(SYS_futex, low_half,
+ FUTEX_WAIT_BITSET, (uint32_t)expected, deadline,
+ NULL, FUTEX_BITSET_MATCH_ANY, 0);
+ return;
+}
+
+static void wake32(const struct ck_ec_ops *ops, const uint32_t *address)
+{
+ assert(ops == &test_ops);
+ syscall(SYS_futex, address,
+ FUTEX_WAKE, INT_MAX,
+ /* ignored arguments */NULL, NULL, 0);
+ return;
+}
+
+static void wake64(const struct ck_ec_ops *ops, const uint64_t *address)
+{
+ const void *low_half;
+
+ assert(ops == &test_ops);
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ low_half = address;
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ low_half = (uintptr_t)address + sizeof(uint32_t);
+#else
+# error "__BYTE_ORDER__ must be defined."
+#endif
+
+ syscall(SYS_futex, low_half,
+ FUTEX_WAKE, INT_MAX,
+ /* ignored arguments */NULL, NULL, 0);
+ return;
+}
+#endif /* __linux__ */
+
+static const struct ck_ec_mode sp = {
+ .ops = &test_ops,
+ .single_producer = true
+};
+
+static const struct ck_ec_mode mp = {
+ .ops = &test_ops,
+ .single_producer = false
+};
+
+static CK_CC_FORCE_INLINE void bench32(const struct ck_ec_mode mode)
+{
+ ck_ec32_t ec CK_CC_CACHELINE = CK_EC_INITIALIZER;
+ uint64_t a;
+ uint64_t baseline = 1000 * 1000;
+ uint32_t value;
+
+ for (size_t i = 0; i < STEPS; i++) {
+ uint64_t s = rdtsc();
+ uint64_t elapsed = rdtsc() - s;
+
+ if (elapsed < baseline) {
+ baseline = elapsed;
+ }
+ }
+
+ /* Read value. */
+ a = 0;
+ value = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ value ^= ck_ec32_value(&ec);
+ value ^= ck_ec32_value(&ec);
+ value ^= ck_ec32_value(&ec);
+ value ^= ck_ec32_value(&ec);
+
+ __asm__ volatile("" :: "r"(value));
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec32_value: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Wait (fast path). */
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ ck_ec32_wait(&ec, &mode, 1, NULL);
+ ck_ec32_wait(&ec, &mode, 1, NULL);
+ ck_ec32_wait(&ec, &mode, 1, NULL);
+ ck_ec32_wait(&ec, &mode, 1, NULL);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec32_wait fast: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* trywait. */
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ struct timespec past = { .tv_sec = 0 };
+ uint64_t s = rdtsc();
+
+ ck_ec32_wait(&ec, &mode, 0, &past);
+ ck_ec32_wait(&ec, &mode, 0, &past);
+ ck_ec32_wait(&ec, &mode, 0, &past);
+ ck_ec32_wait(&ec, &mode, 0, &past);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec32_wait timeout: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Inc (no waiter). */
+ assert(!ck_ec32_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ ck_ec32_inc(&ec, &mode);
+ ck_ec32_inc(&ec, &mode);
+ ck_ec32_inc(&ec, &mode);
+ ck_ec32_inc(&ec, &mode);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec32_inc: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Inc (with waiter). */
+ assert(!ck_ec32_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS; i++) {
+ struct timespec past = { .tv_sec = 1 };
+ uint64_t s;
+
+ ck_ec32_wait(&ec, &mode, ck_ec32_value(&ec), &past);
+ assert(ck_ec32_has_waiters(&ec));
+
+ s = rdtsc();
+ ck_ec32_inc(&ec, &mode);
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec32_inc slow: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Add (no waiter). */
+ assert(!ck_ec32_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ ck_ec32_add(&ec, &mode, i + 1);
+ ck_ec32_add(&ec, &mode, i + 2);
+ ck_ec32_add(&ec, &mode, i + 3);
+ ck_ec32_add(&ec, &mode, i + 4);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec32_add: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ assert(!ck_ec32_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS; i++) {
+ struct timespec past = { .tv_sec = 1 };
+ uint64_t s;
+
+ ck_ec32_wait(&ec, &mode, ck_ec32_value(&ec), &past);
+ assert(ck_ec32_has_waiters(&ec));
+
+ s = rdtsc();
+ ck_ec32_add(&ec, &mode, i + 1);
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec32_add slow: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+ return;
+}
+
+#ifdef CK_F_EC64
+static CK_CC_FORCE_INLINE void bench64(const struct ck_ec_mode mode)
+{
+ ck_ec64_t ec CK_CC_CACHELINE = CK_EC_INITIALIZER;
+ uint64_t a;
+ uint64_t baseline = 1000 * 1000;
+ uint64_t value;
+
+ for (size_t i = 0; i < STEPS; i++) {
+ uint64_t s = rdtsc();
+ uint64_t elapsed = rdtsc() - s;
+
+ if (elapsed < baseline) {
+ baseline = elapsed;
+ }
+ }
+
+ /* Read value. */
+ a = 0;
+ value = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ value ^= ck_ec64_value(&ec);
+ value ^= ck_ec64_value(&ec);
+ value ^= ck_ec64_value(&ec);
+ value ^= ck_ec64_value(&ec);
+
+ __asm__ volatile("" :: "r"(value));
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec64_value: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Wait (fast path). */
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ ck_ec64_wait(&ec, &mode, 1, NULL);
+ ck_ec64_wait(&ec, &mode, 1, NULL);
+ ck_ec64_wait(&ec, &mode, 1, NULL);
+ ck_ec64_wait(&ec, &mode, 1, NULL);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec64_wait fast: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* trywait. */
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ struct timespec past = { .tv_sec = 0 };
+ uint64_t s = rdtsc();
+
+ ck_ec64_wait(&ec, &mode, 0, &past);
+ ck_ec64_wait(&ec, &mode, 0, &past);
+ ck_ec64_wait(&ec, &mode, 0, &past);
+ ck_ec64_wait(&ec, &mode, 0, &past);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec64_wait timeout: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Inc (no waiter). */
+ assert(!ck_ec64_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ ck_ec64_inc(&ec, &mode);
+ ck_ec64_inc(&ec, &mode);
+ ck_ec64_inc(&ec, &mode);
+ ck_ec64_inc(&ec, &mode);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec64_inc: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Inc (with waiter). */
+ assert(!ck_ec64_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS; i++) {
+ struct timespec past = { .tv_sec = 1 };
+ uint64_t s;
+
+ ck_ec64_wait(&ec, &mode, ck_ec64_value(&ec), &past);
+ assert(ck_ec64_has_waiters(&ec));
+
+ s = rdtsc();
+ ck_ec64_inc(&ec, &mode);
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec64_inc slow: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ /* Add (no waiter). */
+ assert(!ck_ec64_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS / 4; i++) {
+ uint64_t s = rdtsc();
+
+ ck_ec64_add(&ec, &mode, i + 1);
+ ck_ec64_add(&ec, &mode, i + 2);
+ ck_ec64_add(&ec, &mode, i + 3);
+ ck_ec64_add(&ec, &mode, i + 4);
+
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec64_add: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+
+ assert(!ck_ec64_has_waiters(&ec));
+ a = 0;
+ for (size_t i = 0; i < STEPS; i++) {
+ struct timespec past = { .tv_sec = 1 };
+ uint64_t s;
+
+ ck_ec64_wait(&ec, &mode, ck_ec64_value(&ec), &past);
+ assert(ck_ec64_has_waiters(&ec));
+
+ s = rdtsc();
+ ck_ec64_add(&ec, &mode, i + 1);
+ a += rdtsc() - s - baseline;
+ }
+
+ printf("%s ec64_add slow: %" PRIu64 "\n",
+ (mode.single_producer ? "SP" : "MP"), a / STEPS);
+ return;
+}
+#endif /* CK_F_EC64 */
+
+int
+main(void)
+{
+ printf("SP ec32\n");
+ bench32(sp);
+ printf("\nMP ec32\n");
+ bench32(mp);
+
+#ifdef CK_F_EC64
+ printf("\nSP ec64\n");
+ bench64(sp);
+ printf("\nMP ec64\n");
+ bench64(mp);
+#endif /* CK_F_EC64 */
+
+ return 0;
+}
diff --git a/regressions/ck_ec/validate/Makefile b/regressions/ck_ec/validate/Makefile
new file mode 100644
index 0000000..f03f493
--- /dev/null
+++ b/regressions/ck_ec/validate/Makefile
@@ -0,0 +1,73 @@
+.PHONY: check clean distribution
+
+FUZZER ?= none
+
+FUZZ_CFLAGS ?=
+
+# See http://gallium.inria.fr/blog/portable-conditionals-in-makefiles/ for
+# the portable conditional technique below.
+none_fuzz_cflags =
+libfuzzer_fuzz_cflags = -DUSE_LIBFUZZER -fsanitize=fuzzer,memory,undefined
+
+FUZZ_CFLAGS += ${${FUZZER}_fuzz_cflags}
+
+OBJECTS = ck_ec_smoke_test \
+ prop_test_timeutil_add \
+ prop_test_timeutil_add_ns \
+ prop_test_timeutil_cmp \
+ prop_test_timeutil_scale \
+ prop_test_value \
+ prop_test_wakeup \
+ prop_test_slow_wakeup
+
+all: $(OBJECTS)
+
+check: all
+ ./ck_ec_smoke_test
+ # the command line arguments are only consumed by libfuzzer.
+ ./prop_test_slow_wakeup -max_total_time=60
+ ./prop_test_timeutil_add -max_total_time=60
+ ./prop_test_timeutil_add_ns -max_total_time=60
+ ./prop_test_timeutil_cmp -max_total_time=60
+ ./prop_test_timeutil_scale -max_total_time=60
+ ./prop_test_value -max_total_time=60
+ ./prop_test_wakeup -max_total_time=60
+
+quickfuzz: all
+ ./prop_test_slow_wakeup -max_total_time=5
+ ./prop_test_timeutil_add -max_total_time=5
+ ./prop_test_timeutil_add_ns -max_total_time=5
+ ./prop_test_timeutil_cmp -max_total_time=5
+ ./prop_test_timeutil_scale -max_total_time=5
+ ./prop_test_value -max_total_time=5
+ ./prop_test_wakeup -max_total_time=5
+
+ck_ec_smoke_test: ../../../src/ck_ec.c ck_ec_smoke_test.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h
+ $(CC) $(CFLAGS) -std=gnu11 ../../../src/ck_ec.c -o ck_ec_smoke_test ck_ec_smoke_test.c
+
+prop_test_slow_wakeup: ../../../src/ck_ec.c prop_test_slow_wakeup.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h fuzz_harness.h
+ $(CC) $(CFLAGS) $(FUZZ_CFLAGS) ../../../src/ck_ec.c -o prop_test_slow_wakeup prop_test_slow_wakeup.c
+
+prop_test_timeutil_add: ../../../src/ck_ec.c prop_test_timeutil_add.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h fuzz_harness.h
+ $(CC) $(CFLAGS) $(FUZZ_CFLAGS) ../../../src/ck_ec.c -o prop_test_timeutil_add prop_test_timeutil_add.c
+
+prop_test_timeutil_add_ns: ../../../src/ck_ec.c prop_test_timeutil_add_ns.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h fuzz_harness.h
+ $(CC) $(CFLAGS) $(FUZZ_CFLAGS) ../../../src/ck_ec.c -o prop_test_timeutil_add_ns prop_test_timeutil_add_ns.c
+
+prop_test_timeutil_cmp: ../../../src/ck_ec.c prop_test_timeutil_cmp.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h fuzz_harness.h
+ $(CC) $(CFLAGS) $(FUZZ_CFLAGS) ../../../src/ck_ec.c -o prop_test_timeutil_cmp prop_test_timeutil_cmp.c
+
+prop_test_timeutil_scale: ../../../src/ck_ec.c prop_test_timeutil_scale.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h fuzz_harness.h
+ $(CC) $(CFLAGS) $(FUZZ_CFLAGS) ../../../src/ck_ec.c -o prop_test_timeutil_scale prop_test_timeutil_scale.c
+
+prop_test_value: ../../../src/ck_ec.c prop_test_value.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h fuzz_harness.h
+ $(CC) $(CFLAGS) $(FUZZ_CFLAGS) ../../../src/ck_ec.c -o prop_test_value prop_test_value.c
+
+prop_test_wakeup: ../../../src/ck_ec.c prop_test_wakeup.c ../../../src/ck_ec_timeutil.h ../../../include/ck_ec.h fuzz_harness.h
+ $(CC) $(CFLAGS) $(FUZZ_CFLAGS) ../../../src/ck_ec.c -o prop_test_wakeup prop_test_wakeup.c
+
+clean:
+ rm -rf *~ *.o *.dSYM *.exe $(OBJECTS)
+
+include ../../../build/regressions.build
+CFLAGS+=$(PTHREAD_CFLAGS) -D_GNU_SOURCE
diff --git a/regressions/ck_ec/validate/ck_ec_smoke_test.c b/regressions/ck_ec/validate/ck_ec_smoke_test.c
new file mode 100644
index 0000000..3aca162
--- /dev/null
+++ b/regressions/ck_ec/validate/ck_ec_smoke_test.c
@@ -0,0 +1,450 @@
+#include <assert.h>
+#include <ck_ec.h>
+#include <ck_limits.h>
+#include <ck_stdbool.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#define TIME_MAX ((time_t)((1ULL << ((sizeof(time_t) * CHAR_BIT) - 1)) - 1))
+
+#ifndef __linux__
+/* Zero-initialize to mark the ops as unavailable. */
+static const struct ck_ec_ops test_ops;
+#else
+#include <linux/futex.h>
+#include <sys/syscall.h>
+#include <time.h>
+
+static int gettime(const struct ck_ec_ops *, struct timespec *out);
+static void wake32(const struct ck_ec_ops *, const uint32_t *);
+static void wait32(const struct ck_ec_wait_state *, const uint32_t *,
+ uint32_t, const struct timespec *);
+static void wake64(const struct ck_ec_ops *, const uint64_t *);
+static void wait64(const struct ck_ec_wait_state *, const uint64_t *,
+ uint64_t, const struct timespec *);
+
+static const struct ck_ec_ops test_ops = {
+ .gettime = gettime,
+ .wait32 = wait32,
+ .wait64 = wait64,
+ .wake32 = wake32,
+ .wake64 = wake64
+};
+
+static int gettime(const struct ck_ec_ops *ops, struct timespec *out)
+{
+ assert(ops == &test_ops);
+ return clock_gettime(CLOCK_MONOTONIC, out);
+}
+
+static void wait32(const struct ck_ec_wait_state *state,
+ const uint32_t *address, uint32_t expected,
+ const struct timespec *deadline)
+{
+ assert(state->ops == &test_ops);
+ syscall(SYS_futex, address,
+ FUTEX_WAIT_BITSET, expected, deadline,
+ NULL, FUTEX_BITSET_MATCH_ANY, 0);
+ return;
+}
+
+static void wait64(const struct ck_ec_wait_state *state,
+ const uint64_t *address, uint64_t expected,
+ const struct timespec *deadline)
+{
+ const void *low_half;
+
+ assert(state->ops == &test_ops);
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ low_half = address;
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ low_half = (uintptr_t)address + sizeof(uint32_t);
+#else
+# error "__BYTE_ORDER__ must be defined."
+#endif
+
+ syscall(SYS_futex, low_half,
+ FUTEX_WAIT_BITSET, (uint32_t)expected, deadline,
+ NULL, FUTEX_BITSET_MATCH_ANY, 0);
+ return;
+}
+
+static void wake32(const struct ck_ec_ops *ops, const uint32_t *address)
+{
+ assert(ops == &test_ops);
+ syscall(SYS_futex, address,
+ FUTEX_WAKE, INT_MAX,
+ /* ignored arguments */NULL, NULL, 0);
+ return;
+}
+
+static void wake64(const struct ck_ec_ops *ops, const uint64_t *address)
+{
+ const void *low_half;
+
+ assert(ops == &test_ops);
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ low_half = address;
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ low_half = (uintptr_t)address + sizeof(uint32_t);
+#else
+# error "__BYTE_ORDER__ must be defined."
+#endif
+
+ syscall(SYS_futex, low_half,
+ FUTEX_WAKE, INT_MAX,
+ /* ignored arguments */NULL, NULL, 0);
+ return;
+}
+#endif /* __linux__ */
+
+static const struct ck_ec_mode sp = {
+ .ops = &test_ops,
+ .single_producer = true
+};
+
+static const struct ck_ec_mode mp = {
+ .ops = &test_ops,
+ .single_producer = false
+};
+
+static void test_update_counter_32(const struct ck_ec_mode *mode)
+{
+ struct ck_ec32 ec = CK_EC_INITIALIZER;
+
+ assert(ck_ec_value(&ec) == 0);
+
+ ck_ec_inc(&ec, mode);
+ assert(ck_ec_value(&ec) == 1);
+
+ uint32_t old = ck_ec_add(&ec, mode, 42);
+ assert(old == 1);
+ assert(ck_ec_value(&ec) == 43);
+ return;
+}
+
+#ifdef CK_F_EC64
+static void test_update_counter_64(const struct ck_ec_mode *mode)
+{
+ struct ck_ec64 ec = CK_EC_INITIALIZER;
+
+ assert(ck_ec_value(&ec) == 0);
+
+ ck_ec_inc(&ec, mode);
+ assert(ck_ec_value(&ec) == 1);
+
+ uint64_t old = ck_ec_add(&ec, mode, 42);
+ assert(old == 1);
+ assert(ck_ec_value(&ec) == 43);
+ return;
+}
+#endif
+
+static void test_deadline(void)
+{
+ struct timespec deadline;
+
+ assert(ck_ec_deadline(&deadline, &sp, NULL) == 0);
+ assert(deadline.tv_sec == TIME_MAX);
+
+ {
+ const struct timespec timeout = {
+ .tv_sec = 1,
+ .tv_nsec = 1000
+ };
+ const struct timespec no_timeout = {
+ .tv_sec = 0
+ };
+ struct timespec now;
+
+ assert(ck_ec_deadline(&deadline, &sp, &timeout) == 0);
+ assert(ck_ec_deadline(&now, &sp, &no_timeout) == 0);
+
+ double now_sec = now.tv_sec + 1e-9 * now.tv_nsec;
+ double deadline_sec = deadline.tv_sec + 1e-9 * deadline.tv_nsec;
+ assert(now_sec < deadline_sec);
+ assert(deadline_sec <= now_sec + 1 + 1000e-9);
+ }
+
+ {
+ const struct timespec timeout = {
+ .tv_sec = TIME_MAX - 1,
+ .tv_nsec = 1000
+ };
+
+ assert(ck_ec_deadline(&deadline, &sp, &timeout) == 0);
+ assert(deadline.tv_sec == TIME_MAX);
+ }
+
+ return;
+}
+
+static void test_wait_32(void)
+{
+ struct timespec deadline = { .tv_sec = 0 };
+ struct ck_ec32 ec;
+
+ ck_ec_init(&ec, 1);
+ assert(ck_ec_value(&ec) == 1);
+ assert(ck_ec_wait(&ec, &sp, 2, NULL) == 0);
+ assert(ck_ec_wait(&ec, &sp, 1, &deadline) == -1);
+
+ {
+ const struct timespec timeout = { .tv_nsec = 1 };
+
+ assert(ck_ec_deadline(&deadline, &sp, &timeout) == 0);
+ assert(ck_ec_wait(&ec, &sp, 1, &deadline) == -1);
+ assert(ck_ec_has_waiters(&ec));
+ }
+
+ return;
+}
+
+#ifdef CK_F_EC64
+static void test_wait_64(void)
+{
+ struct timespec deadline = { .tv_sec = 0 };
+ struct ck_ec64 ec;
+
+ ck_ec_init(&ec, 0);
+ assert(ck_ec_value(&ec) == 0);
+ assert(ck_ec_wait(&ec, &sp, 1, NULL) == 0);
+ assert(ck_ec_wait(&ec, &sp, 0, &deadline) == -1);
+
+ {
+ const struct timespec timeout = { .tv_nsec = 1 };
+
+ assert(ck_ec_deadline(&deadline, &sp, &timeout) == 0);
+ assert(ck_ec_wait(&ec, &sp, 0, &deadline) == -1);
+ assert(ck_ec_has_waiters(&ec));
+ }
+
+ return;
+}
+#endif
+
+static int pred(const struct ck_ec_wait_state *state,
+ struct timespec *deadline)
+{
+ double initial_ts = state->start.tv_sec +
+ 1e-9 * state->start.tv_nsec;
+ int *count = state->data;
+
+ printf("pred wait: %f\n",
+ deadline->tv_sec + 1e-9 * deadline->tv_nsec - initial_ts);
+
+ if ((*count)++ < 3) {
+ return 0;
+ }
+
+ return (*count)++;
+}
+
+/*
+ * Check that pred's return value is correctly bubbled up,
+ * and that the event count is marked as having waiters.
+ */
+static void test_wait_pred_32(void)
+{
+ struct ck_ec32 ec = CK_EC_INITIALIZER;
+ int count = 0;
+
+ assert(!ck_ec_has_waiters(&ec));
+ assert(ck_ec_wait_pred(&ec, &sp, 0, pred, &count, NULL) == 4);
+ assert(ck_ec_has_waiters(&ec));
+ assert(count == 5);
+ return;
+}
+
+#ifdef CK_F_EC64
+static int pred2(const struct ck_ec_wait_state *state,
+ struct timespec *deadline)
+{
+ double initial_ts = state->start.tv_sec +
+ 1e-9 * state->start.tv_nsec;
+ int *count = state->data;
+
+ printf("pred2 wait: %f\n",
+ deadline->tv_sec + 1e-9 * deadline->tv_nsec - initial_ts);
+
+ *deadline = state->now;
+ deadline->tv_sec++;
+
+ (*count)++;
+ return 0;
+}
+
+/*
+ * wait_pred_64 is nearly identical to _32. Now check that deadline
+ * overriding works.
+ */
+static void test_wait_pred_64(void)
+{
+ const struct timespec timeout = { .tv_sec = 5 };
+ struct timespec deadline;
+ struct ck_ec64 ec = CK_EC_INITIALIZER;
+ int count = 0;
+
+ assert(!ck_ec_has_waiters(&ec));
+ assert(ck_ec_deadline(&deadline, &sp, &timeout) == 0);
+ assert(ck_ec_wait_pred(&ec, &sp, 0, pred2, &count, &deadline) == -1);
+ assert(ck_ec_has_waiters(&ec));
+ assert(count == 5);
+ return;
+}
+#endif
+
+static int woken = 0;
+
+static void *test_threaded_32_waiter(void *data)
+{
+ struct ck_ec32 *ec = data;
+
+ ck_ec_wait(ec, &sp, 0, NULL);
+ ck_pr_store_int(&woken, 1);
+ return NULL;
+}
+
+static void test_threaded_inc_32(const struct ck_ec_mode *mode)
+{
+ struct ck_ec32 ec = CK_EC_INITIALIZER;
+ pthread_t waiter;
+
+ ck_pr_store_int(&woken, 0);
+
+ pthread_create(&waiter, NULL, test_threaded_32_waiter, &ec);
+ usleep(10000);
+
+ assert(ck_pr_load_int(&woken) == 0);
+ ck_ec_inc(&ec, mode);
+
+ pthread_join(waiter, NULL);
+ assert(ck_pr_load_int(&woken) == 1);
+ return;
+}
+
+static void test_threaded_add_32(const struct ck_ec_mode *mode)
+{
+ struct ck_ec32 ec = CK_EC_INITIALIZER;
+ pthread_t waiter;
+
+ ck_pr_store_int(&woken, 0);
+
+ pthread_create(&waiter, NULL, test_threaded_32_waiter, &ec);
+ usleep(10000);
+
+ assert(ck_pr_load_int(&woken) == 0);
+ ck_ec_add(&ec, mode, 4);
+
+ pthread_join(waiter, NULL);
+ assert(ck_pr_load_int(&woken) == 1);
+ return;
+}
+
+#ifdef CK_F_EC64
+static void *test_threaded_64_waiter(void *data)
+{
+ struct ck_ec64 *ec = data;
+
+ ck_ec_wait(ec, &sp, 0, NULL);
+ ck_pr_store_int(&woken, 1);
+ return NULL;
+}
+
+static void test_threaded_inc_64(const struct ck_ec_mode *mode)
+{
+ struct ck_ec64 ec = CK_EC_INITIALIZER;
+ pthread_t waiter;
+
+ ck_pr_store_int(&woken, 0);
+
+ pthread_create(&waiter, NULL, test_threaded_64_waiter, &ec);
+ usleep(10000);
+
+ assert(ck_pr_load_int(&woken) == 0);
+ ck_ec_inc(&ec, mode);
+
+ pthread_join(waiter, NULL);
+ assert(ck_pr_load_int(&woken) == 1);
+ return;
+}
+
+static void test_threaded_add_64(const struct ck_ec_mode *mode)
+{
+ struct ck_ec64 ec = CK_EC_INITIALIZER;
+ pthread_t waiter;
+
+ ck_pr_store_int(&woken, 0);
+
+ pthread_create(&waiter, NULL, test_threaded_64_waiter, &ec);
+ usleep(10000);
+
+ assert(ck_pr_load_int(&woken) == 0);
+ ck_ec_add(&ec, mode, 4);
+
+ pthread_join(waiter, NULL);
+ assert(ck_pr_load_int(&woken) == 1);
+ return;
+}
+#endif
+
+int main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ if (test_ops.gettime == NULL ||
+ test_ops.wake32 == NULL ||
+ test_ops.wait32 == NULL) {
+ printf("No ck_ec ops for this platform. Trivial success.\n");
+ return 0;
+ }
+
+ test_update_counter_32(&sp);
+#ifdef CK_F_EC64
+ test_update_counter_64(&sp);
+#endif
+ printf("test_update_counter SP passed.\n");
+
+ test_update_counter_32(&mp);
+#ifdef CK_F_EC64
+ test_update_counter_64(&mp);
+#endif
+ printf("test_update_counter MP passed.\n");
+
+ test_deadline();
+ printf("test_deadline passed.\n");
+
+ test_wait_32();
+#ifdef CK_F_EC64
+ test_wait_64();
+#endif
+ printf("test_wait passed.\n");
+
+ test_wait_pred_32();
+#ifdef CK_F_EC64
+ test_wait_pred_64();
+#endif
+ printf("test_wait_pred passed.\n");
+
+ test_threaded_inc_32(&sp);
+ test_threaded_add_32(&sp);
+#ifdef CK_F_EC64
+ test_threaded_inc_64(&sp);
+ test_threaded_add_64(&sp);
+#endif
+ printf("test_threaded SP passed.\n");
+
+ test_threaded_inc_32(&mp);
+ test_threaded_add_32(&mp);
+#ifdef CK_F_EC64
+ test_threaded_inc_64(&mp);
+ test_threaded_add_64(&mp);
+#endif
+ printf("test_threaded MP passed.\n");
+ return 0;
+}
diff --git a/regressions/ck_ec/validate/fuzz_harness.h b/regressions/ck_ec/validate/fuzz_harness.h
new file mode 100644
index 0000000..8ba6ebe
--- /dev/null
+++ b/regressions/ck_ec/validate/fuzz_harness.h
@@ -0,0 +1,95 @@
+#ifndef FUZZ_HARNESS_H
+#define FUZZ_HARNESS_H
+#include <assert.h>
+#include <ck_stddef.h>
+#include <ck_string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#if defined(USE_LIBFUZZER)
+#define TEST(function, examples) \
+ void LLVMFuzzerInitialize(int *argcp, char ***argvp); \
+ int LLVMFuzzerTestOneInput(const void *data, size_t n); \
+ \
+ void LLVMFuzzerInitialize(int *argcp, char ***argvp) \
+ { \
+ static char size[128]; \
+ static char *argv[1024]; \
+ int argc = *argcp; \
+ \
+ assert(argc < 1023); \
+ \
+ int r = snprintf(size, sizeof(size), \
+ "-max_len=%zu", sizeof(examples[0])); \
+ assert((size_t)r < sizeof(size)); \
+ \
+ memcpy(argv, *argvp, argc * sizeof(argv[0])); \
+ argv[argc++] = size; \
+ \
+ *argcp = argc; \
+ *argvp = argv; \
+ \
+ for (size_t i = 0; \
+ i < sizeof(examples) / sizeof(examples[0]); \
+ i++) { \
+ assert(function(&examples[i]) == 0); \
+ } \
+ \
+ return; \
+ } \
+ \
+ int LLVMFuzzerTestOneInput(const void *data, size_t n) \
+ { \
+ char buf[sizeof(examples[0])]; \
+ \
+ memset(buf, 0, sizeof(buf)); \
+ if (n < sizeof(buf)) { \
+ memcpy(buf, data, n); \
+ } else { \
+ memcpy(buf, data, sizeof(buf)); \
+ } \
+ \
+ assert(function((const void *)buf) == 0); \
+ return 0; \
+ }
+#elif defined(USE_AFL)
+#define TEST(function, examples) \
+ int main(int argc, char **argv) \
+ { \
+ char buf[sizeof(examples[0])]; \
+ \
+ (void)argc; \
+ (void)argv; \
+ for (size_t i = 0; \
+ i < sizeof(examples) / sizeof(examples[0]); \
+ i++) { \
+ assert(function(&examples[i]) == 0); \
+ } \
+ \
+ \
+ while (__AFL_LOOP(10000)) { \
+ memset(buf, 0, sizeof(buf)); \
+ read(0, buf, sizeof(buf)); \
+ \
+ assert(function((const void *)buf) == 0); \
+ } \
+ \
+ return 0; \
+ }
+#else
+#define TEST(function, examples) \
+ int main(int argc, char **argv) \
+ { \
+ (void)argc; \
+ (void)argv; \
+ \
+ for (size_t i = 0; \
+ i < sizeof(examples) / sizeof(examples[0]); \
+ i++) { \
+ assert(function(&examples[i]) == 0); \
+ } \
+ \
+ return 0; \
+ }
+#endif
+#endif /* !FUZZ_HARNESS_H */
diff --git a/regressions/ck_ec/validate/prop_test_slow_wakeup.c b/regressions/ck_ec/validate/prop_test_slow_wakeup.c
new file mode 100644
index 0000000..d172676
--- /dev/null
+++ b/regressions/ck_ec/validate/prop_test_slow_wakeup.c
@@ -0,0 +1,110 @@
+#include <assert.h>
+#include <ck_ec.h>
+
+#include "fuzz_harness.h"
+
+static int gettime(const struct ck_ec_ops *, struct timespec *out);
+static void wake32(const struct ck_ec_ops *, const uint32_t *);
+static void wait32(const struct ck_ec_wait_state *, const uint32_t *,
+ uint32_t, const struct timespec *);
+static void wake64(const struct ck_ec_ops *, const uint64_t *);
+static void wait64(const struct ck_ec_wait_state *, const uint64_t *,
+ uint64_t, const struct timespec *);
+
+static const struct ck_ec_ops test_ops = {
+ .gettime = gettime,
+ .wait32 = wait32,
+ .wait64 = wait64,
+ .wake32 = wake32,
+ .wake64 = wake64
+};
+
+static int gettime(const struct ck_ec_ops *ops, struct timespec *out)
+{
+ (void)out;
+
+ assert(ops == &test_ops);
+ return -1;
+}
+
+static void wait32(const struct ck_ec_wait_state *wait_state,
+ const uint32_t *addr, uint32_t expected,
+ const struct timespec *deadline)
+{
+ (void)addr;
+ (void)expected;
+ (void)deadline;
+
+ assert(wait_state->ops == &test_ops);
+ return;
+}
+
+static void wait64(const struct ck_ec_wait_state *wait_state,
+ const uint64_t *addr, uint64_t expected,
+ const struct timespec *deadline)
+{
+ (void)addr;
+ (void)expected;
+ (void)deadline;
+
+ assert(wait_state->ops == &test_ops);
+ return;
+}
+
+static void wake32(const struct ck_ec_ops *ops, const uint32_t *addr)
+{
+ (void)addr;
+
+ assert(ops == &test_ops);
+ return;
+}
+
+static void wake64(const struct ck_ec_ops *ops, const uint64_t *addr)
+{
+ (void)addr;
+
+ assert(ops == &test_ops);
+ return;
+}
+
+/*
+ * Check that calling ck_ec{32,64}_wake always clears the waiting bit.
+ */
+
+struct example {
+ uint64_t value;
+};
+
+const struct example examples[] = {
+ { 0 },
+ { 1 },
+ { 1UL << 30 },
+ { 1UL << 31 },
+ { INT32_MAX },
+ { INT64_MAX },
+ { 1ULL << 62 },
+ { 1ULL << 63 },
+};
+
+static inline int test_slow_wakeup(const struct example *example)
+{
+ {
+ struct ck_ec32 ec = { .counter = example->value };
+
+ ck_ec32_wake(&ec, &test_ops);
+ assert(!ck_ec32_has_waiters(&ec));
+ }
+
+#ifdef CK_F_EC64
+ {
+ struct ck_ec64 ec = { .counter = example->value };
+
+ ck_ec64_wake(&ec, &test_ops);
+ assert(!ck_ec64_has_waiters(&ec));
+ }
+#endif /* CK_F_EC64 */
+
+ return 0;
+}
+
+TEST(test_slow_wakeup, examples)
diff --git a/regressions/ck_ec/validate/prop_test_timeutil_add.c b/regressions/ck_ec/validate/prop_test_timeutil_add.c
new file mode 100644
index 0000000..bd44607
--- /dev/null
+++ b/regressions/ck_ec/validate/prop_test_timeutil_add.c
@@ -0,0 +1,101 @@
+#include <assert.h>
+#include <ck_limits.h>
+#include <ck_stdint.h>
+
+#include "../../../src/ck_ec_timeutil.h"
+#include "fuzz_harness.h"
+
+#if ULONG_MAX > 4294967295
+typedef unsigned __int128 dword_t;
+#else
+typedef uint64_t dword_t;
+#endif
+
+struct example {
+ struct timespec ts;
+ struct timespec inc;
+};
+
+static const struct example examples[] = {
+ {
+ {
+ 42,
+ 100
+ },
+ {
+ 1,
+ 2
+ }
+ },
+ {
+ {
+ 42,
+ 100
+ },
+ {
+ 1,
+ NSEC_MAX
+ }
+ },
+ {
+ {
+ 42,
+ NSEC_MAX
+ },
+ {
+ 0,
+ NSEC_MAX
+ }
+ },
+ {
+ {
+ TIME_MAX - 1,
+ 1000
+ },
+ {
+ 2,
+ NSEC_MAX
+ }
+ }
+};
+
+static struct timespec normalize_ts(const struct timespec ts)
+{
+ struct timespec ret = ts;
+
+ if (ret.tv_sec < 0) {
+ ret.tv_sec = ~ret.tv_sec;
+ }
+
+ if (ret.tv_nsec < 0) {
+ ret.tv_nsec = ~ret.tv_nsec;
+ }
+
+ ret.tv_nsec %= NSEC_MAX + 1;
+ return ret;
+}
+
+static dword_t ts_to_nanos(const struct timespec ts)
+{
+ return (dword_t)ts.tv_sec * (NSEC_MAX + 1) + ts.tv_nsec;
+}
+
+static inline int test_timespec_add(const struct example *example)
+{
+ const struct timespec ts = normalize_ts(example->ts);
+ const struct timespec inc = normalize_ts(example->inc);
+ const struct timespec actual = timespec_add(ts, inc);
+ const dword_t nanos = ts_to_nanos(ts) + ts_to_nanos(inc);
+
+ if (nanos / (NSEC_MAX + 1) > TIME_MAX) {
+ assert(actual.tv_sec == TIME_MAX);
+ assert(actual.tv_nsec == NSEC_MAX);
+ } else {
+ assert(actual.tv_sec == (time_t)(nanos / (NSEC_MAX + 1)));
+ assert(actual.tv_nsec == (long)(nanos % (NSEC_MAX + 1)));
+ }
+
+ return 0;
+}
+
+TEST(test_timespec_add, examples)
diff --git a/regressions/ck_ec/validate/prop_test_timeutil_add_ns.c b/regressions/ck_ec/validate/prop_test_timeutil_add_ns.c
new file mode 100644
index 0000000..b62e1c7
--- /dev/null
+++ b/regressions/ck_ec/validate/prop_test_timeutil_add_ns.c
@@ -0,0 +1,88 @@
+#include <assert.h>
+
+#include "../../../src/ck_ec_timeutil.h"
+#include "fuzz_harness.h"
+
+#if ULONG_MAX > 4294967295
+typedef unsigned __int128 dword_t;
+#else
+typedef uint64_t dword_t;
+#endif
+
+struct example {
+ struct timespec ts;
+ uint32_t ns;
+};
+
+static const struct example examples[] = {
+ {
+ {
+ 42,
+ 100
+ },
+ 1
+ },
+ {
+ {
+ 42,
+ 100
+ },
+ 2 * NSEC_MAX
+ },
+ {
+ {
+ 42,
+ NSEC_MAX
+ },
+ NSEC_MAX
+ },
+ {
+ {
+ TIME_MAX - 1,
+ 1000
+ },
+ 2 * NSEC_MAX
+ }
+};
+
+static inline int test_timespec_add_ns(const struct example *example)
+{
+ struct timespec ts = {
+ .tv_sec = example->ts.tv_sec,
+ .tv_nsec = example->ts.tv_nsec
+ };
+ const uint32_t ns = example->ns;
+
+ if (ts.tv_sec < 0) {
+ ts.tv_sec = ~ts.tv_sec;
+ }
+
+ if (ts.tv_nsec < 0) {
+ ts.tv_nsec = ~ts.tv_nsec;
+ }
+
+ ts.tv_nsec %= NSEC_MAX + 1;
+
+ const struct timespec actual = timespec_add_ns(ts, ns);
+
+ dword_t nanos =
+ (dword_t)ts.tv_sec * (NSEC_MAX + 1) + ts.tv_nsec;
+
+ if (ns > NSEC_MAX) {
+ nanos += NSEC_MAX + 1;
+ } else {
+ nanos += ns;
+ }
+
+ if (nanos / (NSEC_MAX + 1) > TIME_MAX) {
+ assert(actual.tv_sec == TIME_MAX);
+ assert(actual.tv_nsec == NSEC_MAX);
+ } else {
+ assert(actual.tv_sec == (time_t)(nanos / (NSEC_MAX + 1)));
+ assert(actual.tv_nsec == (long)(nanos % (NSEC_MAX + 1)));
+ }
+
+ return 0;
+}
+
+TEST(test_timespec_add_ns, examples)
diff --git a/regressions/ck_ec/validate/prop_test_timeutil_cmp.c b/regressions/ck_ec/validate/prop_test_timeutil_cmp.c
new file mode 100644
index 0000000..00e7b2e
--- /dev/null
+++ b/regressions/ck_ec/validate/prop_test_timeutil_cmp.c
@@ -0,0 +1,99 @@
+#include <assert.h>
+
+#include "../../../src/ck_ec_timeutil.h"
+#include "fuzz_harness.h"
+
+#if ULONG_MAX > 4294967295
+typedef __int128 dsword_t;
+#else
+typedef int64_t dsword_t;
+#endif
+
+struct example {
+ struct timespec x;
+ struct timespec y;
+};
+
+static const struct example examples[] = {
+ {
+ {
+ 42,
+ 100
+ },
+ {
+ 1,
+ 2
+ }
+ },
+ {
+ {
+ 42,
+ 100
+ },
+ {
+ 1,
+ NSEC_MAX
+ }
+ },
+ {
+ {
+ 42,
+ NSEC_MAX
+ },
+ {
+ 0,
+ NSEC_MAX
+ }
+ },
+ {
+ {
+ TIME_MAX - 1,
+ 1000
+ },
+ {
+ 2,
+ NSEC_MAX
+ }
+ }
+};
+
+static struct timespec normalize_ts(const struct timespec ts)
+{
+ struct timespec ret = ts;
+
+ if (ret.tv_nsec < 0) {
+ ret.tv_nsec = ~ret.tv_nsec;
+ }
+
+ ret.tv_nsec %= NSEC_MAX + 1;
+ return ret;
+}
+
+static dsword_t ts_to_nanos(const struct timespec ts)
+{
+ return (dsword_t)ts.tv_sec * (NSEC_MAX + 1) + ts.tv_nsec;
+}
+
+static inline int test_timespec_cmp(const struct example *example)
+{
+ const struct timespec x = normalize_ts(example->y);
+ const struct timespec y = normalize_ts(example->x);
+ const dsword_t x_nanos = ts_to_nanos(x);
+ const dsword_t y_nanos = ts_to_nanos(y);
+
+ assert(timespec_cmp(x, x) == 0);
+ assert(timespec_cmp(y, y) == 0);
+ assert(timespec_cmp(x, y) == -timespec_cmp(y, x));
+
+ if (x_nanos == y_nanos) {
+ assert(timespec_cmp(x, y) == 0);
+ } else if (x_nanos < y_nanos) {
+ assert(timespec_cmp(x, y) == -1);
+ } else {
+ assert(timespec_cmp(x, y) == 1);
+ }
+
+ return 0;
+}
+
+TEST(test_timespec_cmp, examples)
diff --git a/regressions/ck_ec/validate/prop_test_timeutil_scale.c b/regressions/ck_ec/validate/prop_test_timeutil_scale.c
new file mode 100644
index 0000000..eb3040f
--- /dev/null
+++ b/regressions/ck_ec/validate/prop_test_timeutil_scale.c
@@ -0,0 +1,41 @@
+#include <assert.h>
+
+#include "../../../src/ck_ec_timeutil.h"
+#include "fuzz_harness.h"
+
+struct example {
+ uint32_t nsec;
+ uint32_t multiplier;
+ unsigned int shift;
+};
+
+static const struct example examples[] = {
+ {
+ UINT32_MAX,
+ UINT32_MAX,
+ 1
+ },
+ {
+ 10,
+ 20,
+ 0
+ }
+};
+
+static inline int test_wait_time_scale(const struct example *example)
+{
+ const uint32_t nsec = example->nsec;
+ const uint32_t multiplier = example->multiplier;
+ const unsigned int shift = example->shift % 32;
+ uint32_t actual = wait_time_scale(nsec, multiplier, shift);
+ uint64_t expected = ((uint64_t)nsec * multiplier) >> shift;
+
+ if (expected > UINT32_MAX) {
+ expected = UINT32_MAX;
+ }
+
+ assert(actual == expected);
+ return 0;
+}
+
+TEST(test_wait_time_scale, examples)
diff --git a/regressions/ck_ec/validate/prop_test_value.c b/regressions/ck_ec/validate/prop_test_value.c
new file mode 100644
index 0000000..8f9eab8
--- /dev/null
+++ b/regressions/ck_ec/validate/prop_test_value.c
@@ -0,0 +1,150 @@
+#include <assert.h>
+#include <ck_ec.h>
+
+#include "fuzz_harness.h"
+
+static int gettime(const struct ck_ec_ops *, struct timespec *out);
+static void wake32(const struct ck_ec_ops *, const uint32_t *);
+static void wait32(const struct ck_ec_wait_state *, const uint32_t *,
+ uint32_t, const struct timespec *);
+static void wake64(const struct ck_ec_ops *, const uint64_t *);
+static void wait64(const struct ck_ec_wait_state *, const uint64_t *,
+ uint64_t, const struct timespec *);
+
+static const struct ck_ec_ops test_ops = {
+ .gettime = gettime,
+ .wait32 = wait32,
+ .wait64 = wait64,
+ .wake32 = wake32,
+ .wake64 = wake64
+};
+
+static const struct ck_ec_mode modes[] = {
+ {
+ .single_producer = true,
+ .ops = &test_ops
+ },
+ {
+ .single_producer = false,
+ .ops = &test_ops
+ },
+};
+
+static int gettime(const struct ck_ec_ops *ops, struct timespec *out)
+{
+ (void)out;
+
+ assert(ops == &test_ops);
+ return -1;
+}
+
+static void wait32(const struct ck_ec_wait_state *wait_state,
+ const uint32_t *addr, uint32_t expected,
+ const struct timespec *deadline)
+{
+ (void)addr;
+ (void)expected;
+ (void)deadline;
+
+ assert(wait_state->ops == &test_ops);
+ return;
+}
+
+static void wait64(const struct ck_ec_wait_state *wait_state,
+ const uint64_t *addr, uint64_t expected,
+ const struct timespec *deadline)
+{
+ (void)addr;
+ (void)expected;
+ (void)deadline;
+
+ assert(wait_state->ops == &test_ops);
+ return;
+}
+
+static void wake32(const struct ck_ec_ops *ops, const uint32_t *addr)
+{
+ (void)addr;
+
+ assert(ops == &test_ops);
+ return;
+}
+
+static void wake64(const struct ck_ec_ops *ops, const uint64_t *addr)
+{
+ (void)addr;
+
+ assert(ops == &test_ops);
+ return;
+}
+
+/*
+ * Check that adding a value correctly updates the counter, and that
+ * incrementing after that also works.
+ */
+struct example {
+ uint64_t value[2];
+};
+
+static const struct example examples[] = {
+ { { 0, 0 } },
+ { { 1, 2 } },
+ { { 0, INT32_MAX - 2 } },
+ { { 0, INT32_MAX - 1 } },
+ { { 0, INT32_MAX } },
+ { { 0, INT64_MAX - 2 } },
+ { { 0, INT64_MAX - 1 } },
+ { { 0, INT64_MAX } },
+};
+
+static inline int test_value(const struct example *example)
+{
+ for (size_t i = 0; i < 2; i++) {
+ const struct ck_ec_mode *mode = &modes[i];
+ const uint32_t value0 = example->value[0] & INT32_MAX;
+ const uint32_t value1 = example->value[1] & INT32_MAX;
+ struct ck_ec32 ec;
+
+ ck_ec32_init(&ec, 0);
+ assert(ck_ec32_value(&ec) == 0);
+
+ ck_ec32_add(&ec, mode, value0);
+ assert(ck_ec32_value(&ec) == value0);
+
+ ck_ec32_add(&ec, mode, value1);
+ assert(ck_ec32_value(&ec) ==
+ ((value0 + value1) & INT32_MAX));
+
+
+ ck_ec32_inc(&ec, mode);
+ assert(ck_ec32_value(&ec) ==
+ ((value0 + value1 + 1) & INT32_MAX));
+ }
+
+#ifdef CK_F_EC64
+ for (size_t i = 0; i < 2; i++) {
+ const struct ck_ec_mode *mode = &modes[i];
+ const uint64_t value0 = example->value[0] & INT64_MAX;
+ const uint64_t value1 = example->value[1] & INT64_MAX;
+ struct ck_ec64 ec;
+
+ ck_ec64_init(&ec, 0);
+ assert(ck_ec64_value(&ec) == 0);
+
+ ck_ec64_add(&ec, mode, value0);
+ assert(ck_ec64_value(&ec) == value0);
+
+ ck_ec64_add(&ec, mode, value1);
+ assert(ck_ec64_value(&ec) ==
+ ((value0 + value1) & INT64_MAX));
+
+ ck_ec64_inc(&ec, mode);
+ assert(ck_ec64_value(&ec) ==
+ ((value0 + value1 + 1) & INT64_MAX));
+ }
+#endif /* CK_F_EC64 */
+
+ return 0;
+}
+
+TEST(test_value, examples)
diff --git a/regressions/ck_ec/validate/prop_test_wakeup.c b/regressions/ck_ec/validate/prop_test_wakeup.c
new file mode 100644
index 0000000..a858e2b
--- /dev/null
+++ b/regressions/ck_ec/validate/prop_test_wakeup.c
@@ -0,0 +1,193 @@
+#include <assert.h>
+#include <ck_ec.h>
+#include <ck_stdbool.h>
+
+#include "fuzz_harness.h"
+
+static int gettime(const struct ck_ec_ops *, struct timespec *out);
+static void wake32(const struct ck_ec_ops *, const uint32_t *);
+static void wait32(const struct ck_ec_wait_state *, const uint32_t *,
+ uint32_t, const struct timespec *);
+static void wake64(const struct ck_ec_ops *, const uint64_t *);
+static void wait64(const struct ck_ec_wait_state *, const uint64_t *,
+ uint64_t, const struct timespec *);
+
+static const struct ck_ec_ops test_ops = {
+ .gettime = gettime,
+ .wait32 = wait32,
+ .wait64 = wait64,
+ .wake32 = wake32,
+ .wake64 = wake64
+};
+
+static const struct ck_ec_mode modes[] = {
+ {
+ .single_producer = true,
+ .ops = &test_ops
+ },
+ {
+ .single_producer = false,
+ .ops = &test_ops
+ },
+};
+
+static bool woken = false;
+
+static int gettime(const struct ck_ec_ops *ops, struct timespec *out)
+{
+ (void)out;
+
+ assert(ops == &test_ops);
+ return -1;
+}
+
+static void wait32(const struct ck_ec_wait_state *state, const uint32_t *addr,
+ uint32_t expected, const struct timespec *deadline)
+{
+ (void)addr;
+ (void)expected;
+ (void)deadline;
+
+ assert(state->ops == &test_ops);
+ return;
+}
+
+static void wait64(const struct ck_ec_wait_state *state, const uint64_t *addr,
+ uint64_t expected, const struct timespec *deadline)
+{
+ (void)addr;
+ (void)expected;
+ (void)deadline;
+
+ assert(state->ops == &test_ops);
+ return;
+}
+
+static void wake32(const struct ck_ec_ops *ops, const uint32_t *addr)
+{
+ (void)addr;
+
+ assert(ops == &test_ops);
+ woken = true;
+ return;
+}
+
+static void wake64(const struct ck_ec_ops *ops, const uint64_t *addr)
+{
+ (void)addr;
+
+ assert(ops == &test_ops);
+ woken = true;
+ return;
+}
+
+/*
+ * Check that adding a value calls the wake function when the sign bit
+ * is set, and does not call it when the sign bit is unset (modulo
+ * wrap-around).
+ */
+struct example {
+ uint64_t initial;
+ uint64_t increment;
+};
+
+const struct example examples[] = {
+ { INT32_MAX, 0 },
+ { INT32_MAX, 1 },
+ { 0 + (0U << 31), 0 },
+ { 1 + (0U << 31), 0 },
+ { 0 + (1U << 31), 0 },
+ { 1 + (1U << 31), 0 },
+
+ { 0 + (0U << 31), 1 },
+ { 1 + (0U << 31), 1 },
+ { 0 + (1U << 31), 1 },
+ { 1 + (1U << 31), 1 },
+
+ { 0 + (0U << 31), INT32_MAX },
+ { 1 + (0U << 31), INT32_MAX },
+ { 0 + (1U << 31), INT32_MAX },
+ { 1 + (1U << 31), INT32_MAX },
+
+ { INT64_MAX, 0 },
+ { INT64_MAX, 1 },
+ { 0 + (0ULL << 63), 0 },
+ { 1 + (0ULL << 63), 0 },
+ { 0 + (1ULL << 63), 0 },
+ { 1 + (1ULL << 63), 0 },
+
+ { 0 + (0ULL << 63), 1 },
+ { 1 + (0ULL << 63), 1 },
+ { 0 + (1ULL << 63), 1 },
+ { 1 + (1ULL << 63), 1 },
+
+ { 0 + (0ULL << 63), INT64_MAX },
+ { 1 + (0ULL << 63), INT64_MAX },
+ { 0 + (1ULL << 63), INT64_MAX },
+ { 1 + (1ULL << 63), INT64_MAX },
+};
+
+static inline int test_wakeup(const struct example *example)
+{
+ for (size_t i = 0; i < 2; i++) {
+ const struct ck_ec_mode *mode = &modes[i];
+ const uint32_t increment = example->increment & INT32_MAX;
+ struct ck_ec32 ec;
+ bool should_wake;
+ bool may_wake;
+
+ ec.counter = example->initial;
+ should_wake = increment != 0 && (ec.counter & (1U << 31));
+ may_wake = should_wake || (ec.counter & (1U << 31));
+
+ woken = false;
+ ck_ec32_add(&ec, mode, increment);
+ assert(!should_wake || woken);
+ assert(may_wake || !woken);
+ assert(!woken || ck_ec32_has_waiters(&ec) == false);
+
+ /* Test inc now. */
+ ec.counter = example->initial + increment;
+ should_wake = ec.counter & (1U << 31);
+ may_wake = should_wake || ((ec.counter + 1) & (1U << 31));
+
+ woken = false;
+ ck_ec32_inc(&ec, mode);
+ assert(!should_wake || woken);
+ assert(may_wake || !woken);
+ assert(!woken || ck_ec32_has_waiters(&ec) == false);
+ }
+
+#ifdef CK_F_EC64
+ for (size_t i = 0; i < 2; i++) {
+ const struct ck_ec_mode *mode = &modes[i];
+ const uint64_t increment = example->increment & INT64_MAX;
+ struct ck_ec64 ec;
+ bool should_wake;
+ bool may_wake;
+
+ ec.counter = example->initial;
+ should_wake = increment != 0 && (ec.counter & 1);
+ may_wake = should_wake || (ec.counter & 1);
+
+ woken = false;
+ ck_ec64_add(&ec, mode, increment);
+ assert(!should_wake || woken);
+ assert(may_wake || !woken);
+ assert(!woken || ck_ec64_has_waiters(&ec) == false);
+
+ /* Test inc now. */
+ ec.counter = example->initial + increment;
+ should_wake = ec.counter & 1;
+
+ woken = false;
+ ck_ec64_inc(&ec, mode);
+ assert(should_wake == woken);
+ assert(!woken || ck_ec64_has_waiters(&ec) == false);
+ }
+#endif /* CK_F_EC64 */
+
+ return 0;
+}
+
+TEST(test_wakeup, examples)