From cb518b00efc8fe29df713e652450e90448340c29 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Thu, 3 Oct 2024 08:32:25 +0200 Subject: Refactor --- examples/gravity.c | 17 +- examples/rf64.c | 252 -------------- examples/test.c | 989 ----------------------------------------------------- examples/ui.c | 321 +---------------- graphics.c | 364 ++++++++++++++++++++ rf64.c | 252 ++++++++++++++ test.c | 989 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1607 insertions(+), 1577 deletions(-) delete mode 100755 examples/rf64.c delete mode 100644 examples/test.c create mode 100644 graphics.c create mode 100755 rf64.c create mode 100644 test.c diff --git a/examples/gravity.c b/examples/gravity.c index 70f279f..8bc45e0 100755 --- a/examples/gravity.c +++ b/examples/gravity.c @@ -25,7 +25,7 @@ gcc \ exit $? # */ #endif -#include "../reduced_system_layer.c" +#include "../graphics.c" typedef struct { f64 x; @@ -47,21 +47,6 @@ i64 time_milliseconds() { return (i64) t.tv_sec * 1000ll + (i64) t.tv_nsec / 1000000ll; } -u32 u32_from_rgb(f32 red, f32 green, f32 blue) { - i32 r = (i32) floor(red * 255.f); - i32 g = (i32) floor(green * 255.f); - i32 b = (i32) floor(blue * 255.f); - - if (r < 0) r = 0; - if (r > 255) r = 255; - if (g < 0) g = 0; - if (g > 255) g = 255; - if (b < 0) b = 0; - if (b > 255) b = 255; - - return (r << 16) | (g << 8) | b; -} - i32 main(i32 argc, c8 **argv) { (void) argc; (void) argv; diff --git a/examples/rf64.c b/examples/rf64.c deleted file mode 100755 index aeba85a..0000000 --- a/examples/rf64.c +++ /dev/null @@ -1,252 +0,0 @@ -#if 0 /* -#/ ================================================================ -#/ -#/ rf64.c -#/ -#/ Probabalistic floating-point numbers -#/ -#/ ---------------------------------------------------------------- -#/ -#/ (C) 2024 Mitya Selivanov , MIT License -#/ -#/ ================================================================ -#/ -#/ Self-compilation shell script -#/ -SRC=${0##*./} -BIN=${SRC%.*} -gcc \ - -Wall -Wextra -Werror -pedantic \ - -Wno-old-style-declaration \ - -Wno-missing-braces \ - -Wno-unused-variable \ - -Wno-unused-but-set-variable \ - -Wno-unused-parameter \ - -Wno-overlength-strings \ - -O3 \ - -fsanitize=undefined,address,leak -mshstk \ - -D RF64_TESTS \ - -o $BIN $SRC && \ - ./$BIN $@ && rm $BIN -exit $? # */ -#endif - -// ================================================================ - -#ifndef TYPES_HEADER_GUARD_ -#define TYPES_HEADER_GUARD_ - -typedef signed char i8; -typedef signed short i16; -typedef signed i32; -typedef signed long long i64; -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned u32; -typedef unsigned long long u64; -typedef char c8; -typedef int c32; -typedef signed char b8; -typedef float f32; -typedef double f64; - -#endif // TYPES_HEADER_GUARD_ - -// ================================================================ - -#ifndef RF64_HEADER_GUARD_ -#define RF64_HEADER_GUARD_ - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include - -#ifndef RF64_RAND -static i32 rf64_rand(i32 min, i32 max) { - if (max <= min) - return min; - return min + (rand() % (max - min + 1)); -} - -#define RF64_RAND(min, max) rf64_rand((min), (max)) -#endif - -#define RF64_EXPONENT_NUM_BITS 30 -#define RF64_MANTISSA_NUM_BITS 32 -#define RF64_EXPONENT_ZERO (1 << (RF64_EXPONENT_NUM_BITS / 2)) -#define RF64_EXPONENT_MAX ((1 << RF64_EXPONENT_NUM_BITS) - 1) -#define RF64_MANTISSA_MAX 0xffffffffu - -typedef struct { - u32 infinity : 1; - u32 sign : 1; - i32 exponent : RF64_EXPONENT_NUM_BITS; - u32 mantissa; -} rf64; - -rf64 rf64_from_i64(i64 x); -i64 i64_from_rf64(rf64 x); -void rf64_inc(rf64 *x); -void rf64_dec(rf64 *x); -void rf64_add(rf64 *x, rf64 y); -void rf64_sub(rf64 *x, rf64 y); -void rf64_mul(rf64 *x, rf64 y); -void rf64_div(rf64 *x, rf64 y); - -#endif // RF64_HEADER_GUARD_ - -#ifndef RF64_HEADER -#ifndef RF64_IMPL_GUARD_ -#define RF64_IMPL_GUARD_ - -rf64 rf64_from_i64(i64 x) { - rf64 z = { - .sign = x < 0, - .exponent = RF64_EXPONENT_ZERO, - }; - - u64 a = z.sign ? -x : x; - - while (a > RF64_MANTISSA_MAX) { - u64 loss_comp = (a & 1) == 1 ? RF64_RAND(0, 1) : 0; - - z.exponent += 1; - - a /= 2; - a += loss_comp; - } - - z.mantissa = (u32) a; - - return z; -} - -i64 i64_from_rf64(rf64 x) { - assert(0); -} - -void rf64_inc(rf64 *x) { - assert(0); -} - -void rf64_dec(rf64 *x) { - assert(x != NULL); - if (x == NULL) return; - - assert(0); -} - -void rf64_add(rf64 *x, rf64 y) { - assert(x != NULL); - if (x == NULL) return; - - assert(0); -} - -void rf64_sub(rf64 *x, rf64 y) { - assert(x != NULL); - if (x == NULL) return; - - assert(0); -} - -void rf64_mul(rf64 *x, rf64 y) { - assert(x != NULL); - if (x == NULL) return; - - assert(0); -} - -void rf64_div(rf64 *x, rf64 y) { - assert(x != NULL); - if (x == NULL) return; - - assert(0); -} - -#ifdef RF64_TESTS - -#include -#include - -void report_test(c8 *name, b8 success) { - i32 l = 20 - (i32) strlen(name); - - printf("%s %.*s %s\n", - name, - l < 1 ? 1 : l, - "....................", - success ? "OK" : "FAIL" - ); -} - -#define TEST_(x) report_test(#x, test_##x()) - -b8 test_from_i64() { - return 1 - && rf64_from_i64( 0 ).infinity == 0 - && rf64_from_i64( 0 ).sign == 0 - && rf64_from_i64( 0 ).exponent == RF64_EXPONENT_ZERO - && rf64_from_i64( 0 ).mantissa == 0 - && rf64_from_i64( 42 ).infinity == 0 - && rf64_from_i64( 42 ).sign == 0 - && rf64_from_i64( 42 ).exponent == RF64_EXPONENT_ZERO - && rf64_from_i64( 42 ).mantissa == 42 - && rf64_from_i64(-42 ).infinity == 0 - && rf64_from_i64(-42 ).sign == 1 - && rf64_from_i64(-42 ).exponent == RF64_EXPONENT_ZERO - && rf64_from_i64(-42 ).mantissa == 42 - && rf64_from_i64( 0xffffffff ).infinity == 0 - && rf64_from_i64( 0xffffffff ).sign == 0 - && rf64_from_i64( 0xffffffff ).exponent == RF64_EXPONENT_ZERO - && rf64_from_i64( 0xffffffff ).mantissa == 0xffffffff - && rf64_from_i64(-0xffffffffll ).infinity == 0 - && rf64_from_i64(-0xffffffffll ).sign == 1 - && rf64_from_i64(-0xffffffffll ).exponent == RF64_EXPONENT_ZERO - && rf64_from_i64(-0xffffffffll ).mantissa == 0xffffffff - && rf64_from_i64( 0x100000000ll).infinity == 0 - && rf64_from_i64( 0x100000000ll).sign == 0 - && rf64_from_i64( 0x100000000ll).exponent == RF64_EXPONENT_ZERO + 1 - && rf64_from_i64( 0x100000000ll).mantissa == 0x80000000u - && rf64_from_i64(-0x100000000ll).infinity == 0 - && rf64_from_i64(-0x100000000ll).sign == 1 - && rf64_from_i64(-0x100000000ll).exponent == RF64_EXPONENT_ZERO + 1 - && rf64_from_i64(-0x100000000ll).mantissa == 0x80000000u - && 1; -} - -b8 test_to_i64() { - return 1 - && i64_from_rf64(rf64_from_i64( 0 )) == 0 - && i64_from_rf64(rf64_from_i64( 1 )) == 1 - && i64_from_rf64(rf64_from_i64(-1 )) == -1 - && i64_from_rf64(rf64_from_i64( 42 )) == 42 - && i64_from_rf64(rf64_from_i64(-42 )) == -42 - && i64_from_rf64(rf64_from_i64( 0xffffffff )) == 0xffffffff - && i64_from_rf64(rf64_from_i64(-0xffffffff )) == -0xffffffff - && i64_from_rf64(rf64_from_i64( 0x100000000ll )) == 0x100000000ll - && i64_from_rf64(rf64_from_i64(-0x100000000ll )) == -0x100000000ll - && i64_from_rf64(rf64_from_i64( 0x42000000000ll)) == 0x42000000000ll - && i64_from_rf64(rf64_from_i64(-0x42000000000ll)) == -0x42000000000ll - && 1; -} - -i32 main(i32 argc, c8 **argv) { - (void) argc; - (void) argv; - - TEST_(from_i64); - TEST_(to_i64); - - return 0; -} - -#undef TEST_ - -#endif // RF64_TESTS - -#endif // RF64_IMPL_GUARD_ -#endif // RF64_HEADER diff --git a/examples/test.c b/examples/test.c deleted file mode 100644 index c5e17bd..0000000 --- a/examples/test.c +++ /dev/null @@ -1,989 +0,0 @@ -// ================================================================ -// -// test.c -// -// Unit-testing and microbenchmarks modular library for C -// -// ---------------------------------------------------------------- -// -// Optional settings -// -// - MAX_NUM_TESTS -// - MAX_NUM_TEST_ASSECTIONS -// - MAX_NUM_BENCHS -// - MAX_NUM_BENCH_REPEATS -// - MAX_NUM_BENCH_CYCLES -// - NUM_BENCH_REPEATS_DEFAULT_1 -// - NUM_BENCH_REPEATS_DEFAULT_2 -// -// ---------------------------------------------------------------- -// -// Usage -// -// - Define a unique TEST_FILE for each file to avoid name -// collisions. -// -// Example -// -// // foo.test.c -// #define TEST_FILE foo // This is required if you want to -// // use multiple test files. -// #define TEST_HEADER // Do not include the implementation. -// #include "test.c" -// TEST("foo") { -// REQUIRE(1); -// REQUIRE_EQ(2 + 2, 4); -// } -// -// // main.c -// #include "test.c" -// int main(int argc, char **argv) { -// return run_tests(argc, argv); -// } -// -// ---------------------------------------------------------------- -// -// (C) 2024 Mitya Selivanov , MIT License -// -// ================================================================ - -#ifndef TYPES_HEADER_GUARD_ -#define TYPES_HEADER_GUARD_ - -typedef signed char i8; -typedef signed short i16; -typedef signed i32; -typedef signed long long i64; -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned u32; -typedef unsigned long long u64; -typedef char c8; -typedef int c32; -typedef signed char b8; -typedef float f32; -typedef double f64; - -#endif // TYPES_HEADER_GUARD_ - -// ================================================================ - -#ifndef TEST_HEADER_GUARD_ -#define TEST_HEADER_GUARD_ - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wunused-value" -# pragma GCC diagnostic ignored "-Wunused-parameter" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#ifndef TEST_FILE -#define TEST_FILE test -#endif - -#ifndef MAX_NUM_TESTS -#define MAX_NUM_TESTS 0x1000 -#endif - -#ifndef MAX_NUM_TEST_ASSECTIONS -#define MAX_NUM_TEST_ASSECTIONS 0x50 -#endif - -#ifndef MAX_NUM_BENCHS -#define MAX_NUM_BENCHS 200 -#endif - -#ifndef MAX_NUM_BENCH_REPEATS -#define MAX_NUM_BENCH_REPEATS 4000 -#endif - -#ifndef MAX_NUM_BENCH_CYCLES -#define MAX_NUM_BENCH_CYCLES 40 -#endif - -#ifndef NUM_BENCH_REPEATS_DEFAULT_1 -#define NUM_BENCH_REPEATS_DEFAULT_1 40 -#endif - -#ifndef NUM_BENCH_REPEATS_DEFAULT_2 -#define NUM_BENCH_REPEATS_DEFAULT_2 400 -#endif - -typedef void (*test_report_fn)(i32 test_index, i32 line, i64 value, i64 expected); -typedef void (*test_run_fn) (i32 test_index_, test_report_fn test_report_fn_); - -typedef struct { - c8 const *test_name; - c8 const *test_file; - test_run_fn test_fn; - i32 assertions; - i32 line [MAX_NUM_TEST_ASSECTIONS]; - i32 status [MAX_NUM_TEST_ASSECTIONS]; - i64 value [MAX_NUM_TEST_ASSECTIONS]; - i64 expected[MAX_NUM_TEST_ASSECTIONS]; - i32 signal; -} Test_Case; - -typedef struct { - i32 size; - Test_Case v[MAX_NUM_TESTS]; -} Tests_List; - -extern Tests_List tests_list; - -#define TEST_CONCAT4_(a, b, c, d) a##b##c##d -#define TEST_CONCAT3_(a, b, c) TEST_CONCAT4_(a, b, _, c) - -#ifdef __cplusplus -# define TEST_ON_START_(f) \ - static void f(void); \ - static i32 TEST_CONCAT3_(_test_init_, __LINE__, f) = (f(), 0); \ - static void f(void) -#else -# ifdef _MSC_VER -# pragma section(".CRT$XCU", read) -# define TEST_ON_START_2_(f, p) \ - static void f(void); \ - __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ - __pragma(comment(linker, "/include:" p #f "_")) static void f(void) -# ifdef _WIN64 -# define TEST_ON_START_(f) TEST_ON_START_2_(f, "") -# else -# define TEST_ON_START_(f) TEST_ON_START_2_(f, "_") -# endif -# else -# define TEST_ON_START_(f) \ - static void f(void) __attribute__((constructor)); \ - static void f(void) -# endif -#endif - -void test_register(c8 const *name, c8 const *file, test_run_fn fn); - -#define TEST(name) \ - static void TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__)(i32, test_report_fn); \ - TEST_ON_START_(TEST_CONCAT3_(test_case_, TEST_FILE, __LINE__)) { \ - test_register(name, __FILE__, TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__)); \ - } \ - static void TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__) \ - (i32 test_index_, test_report_fn test_report_fn_) - -#define REQUIRE(...) test_report_fn_(test_index_, __LINE__, (__VA_ARGS__), 1) -#define REQUIRE_EQ(...) test_report_fn_(test_index_, __LINE__, __VA_ARGS__) - -i32 run_tests(i32 argc, c8 **argv); - -// ================================================================ - -typedef void (*bench_set_repeats_limit_fn)(i32 bench_index, i32 repeats_limit); -typedef i32 (*bench_loop_fn) (i32 bench_index); -typedef void (*bench_begin_fn) (i32 bench_index); -typedef void (*bench_end_fn) (i32 bench_index); - -typedef void (*bench_run_fn)( - i32 bench_index_, - bench_set_repeats_limit_fn bench_set_repeats_limit_, - bench_loop_fn bench_loop_, - bench_begin_fn bench_begin_, - bench_end_fn bench_end_ -); - -typedef struct { - c8 const *bench_name; - c8 const *bench_file; - bench_run_fn bench_fn; - i64 sec [MAX_NUM_BENCH_REPEATS]; - i32 nsec [MAX_NUM_BENCH_REPEATS]; - i64 duration_nsec [MAX_NUM_BENCH_REPEATS]; - i64 duration_sorted_nsec[MAX_NUM_BENCH_REPEATS]; - i32 repeats; - i32 cycles_size; - i32 cycles[MAX_NUM_BENCH_CYCLES]; - i32 cycle; - i32 signal; - i32 ready; -} Benchmark; - -typedef struct { - i32 size; - Benchmark v[MAX_NUM_BENCHS]; -} Benchs_List; - -extern Benchs_List benchs_list; - -void bench_register(c8 const *name, c8 const *file, bench_run_fn fn); - -#define BENCHMARK(name) \ - static void TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__) \ - (i32, bench_set_repeats_limit_fn, bench_loop_fn, bench_begin_fn, bench_end_fn); \ - TEST_ON_START_(TEST_CONCAT3_(benchmark_, TEST_FILE, __LINE__)) { \ - bench_register(name, __FILE__, TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__)); \ - } \ - static void TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__)( \ - i32 bench_index_, \ - bench_set_repeats_limit_fn bench_set_repeats_limit_, \ - bench_loop_fn bench_loop_, \ - bench_begin_fn bench_begin_, \ - bench_end_fn bench_end_ \ - ) - -#define BENCHMARK_REPEAT(repeats_limit_) \ - bench_set_repeats_limit_(bench_index_, repeats_limit_) - -#define BENCHMARK_BEGIN \ - while (bench_loop_(bench_index_)) { \ - bench_begin_(bench_index_); { - -#define BENCHMARK_END \ - } bench_end_(bench_index_); } - -// FIXME -// Does this work reliably? -// -#define DO_NOT_OPTIMIZE(x) \ - do { \ - volatile void *bench_ptr_ = &(x); \ - (void) bench_ptr_; \ - } while (0) - -i32 run_benchmarks(i32 argc, c8 **argv); - -#ifdef __cplusplus -} -#endif - -#endif // TEST_HEADER_GUARD_ - -// ================================================================ -// -// Implementation -// -// ================================================================ - -#ifndef TEST_HEADER -#ifndef TEST_IMPL_GUARD_ -#define TEST_IMPL_GUARD_ - -#include -#include -#include -#include -#include -#include - -enum { - white_, - blue_, - light_, - yellow_, - red_, - green_ -}; - -static c8 const *color_codes_[] = { - "\x1b[38m", "\x1b[34m", - "\x1b[37m", "\x1b[33m", - "\x1b[31m", "\x1b[32m" -}; - -static i32 print_color_(i32 c) { - return printf("%s", color_codes_[c]); -} - -static i32 signums_[] = { SIGINT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGTERM }; - -static c8 const *signames_[64]; - -static void signames_init_(void) { - memset(signames_, 0, sizeof signames_); - signames_[SIGINT] = "Interactive attention signal"; - signames_[SIGILL] = "Illegal instruction"; - signames_[SIGABRT] = "Abnormal termination"; - signames_[SIGFPE] = "Erroneous arithmetic operation"; - signames_[SIGSEGV] = "Invalid access to storage"; - signames_[SIGTERM] = "Termination request"; -} - -Tests_List tests_list = { 0 }; - -static void report_(i32 i, i32 line, i64 value, i64 expected) { - i32 n = tests_list.v[i].assertions++; - - if (n >= MAX_NUM_TEST_ASSECTIONS) - return; - - tests_list.v[i].line[n] = line; - tests_list.v[i].status[n] = value == expected; - tests_list.v[i].value[n] = value; - tests_list.v[i].expected[n] = expected; -} - -static i64 ns_to_ms(i64 ns) { - return (ns + 500000) / 1000000; -} - -static i64 sec_to_ms(int64_t sec) { - return 1000 * sec; -} - -void test_register(c8 const *name, c8 const *file, test_run_fn fn) { - i32 n = tests_list.size++; - if (n < MAX_NUM_TESTS) { - tests_list.v[n].test_fn = fn; - tests_list.v[n].test_name = name; - tests_list.v[n].test_file = file; - tests_list.v[n].assertions = 0; - } -} - -static jmp_buf test_restore_execution; - -static void test_handle_signal(i32 signum) { - longjmp(test_restore_execution, signum); -} - -static void test_setup_signals() { - for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) - signal(signums_[i], test_handle_signal); -} - -static i32 run_test_(volatile i32 i) { - i32 signum = setjmp(test_restore_execution); - - if (signum != 0) { - tests_list.v[i].signal = signum; - return 0; - } - - tests_list.v[i].test_fn(i, report_); - return 1; -} - -i32 run_tests(i32 argc, c8 **argv) { - signames_init_(); - - i32 success_count = 0; - i32 fail_assertion_count = 0; - i32 total_assertion_count = 0; - i32 status = 0; - i32 quiet = 0; - i32 no_color = 0; - i32 line_width = 20; - i32 carriage_return = 1; - - i32 i, j; - - c8 const *specific_test = NULL; - - test_setup_signals(); - - for (i = 0; i < argc; i++) - if (strcmp("--no-term-color", argv[i]) == 0) - no_color = 1; - else if (strcmp("--no-carriage-return", argv[i]) == 0) - carriage_return = 0; - else if (strcmp("--quiet", argv[i]) == 0) - quiet = 1; - else if (strcmp("--match", argv[i]) == 0) - specific_test = argv[++i]; - - quiet && (no_color = 1); - - if (specific_test != NULL) { - no_color || print_color_(light_); - quiet || printf("Run tests matching "); - no_color || print_color_(white_); - quiet || printf("*%s*", specific_test); - no_color || print_color_(light_); - quiet || printf("\n\n"); - } - - c8 const *file = NULL; - i64 file_root = -1; - i32 tests_total = 0; - - for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; - i++) { - if (specific_test != NULL && - strstr(tests_list.v[i].test_name, specific_test) == NULL) - continue; - tests_total++; - i32 l = 2 + (int) strlen(tests_list.v[i].test_name); - if (line_width < l) - line_width = l; - } - - if (tests_total > 0) { - c8 const *s = tests_list.v[0].test_file; - - for (j = 1; j < tests_list.size && j < MAX_NUM_TESTS; - j++) { - if (specific_test != NULL && - strstr(tests_list.v[j].test_name, specific_test) == - NULL) - continue; - if (strcmp(s, tests_list.v[j].test_file) == 0) - continue; - i32 k = 0; - for (; - s[k] != '\0' && tests_list.v[j].test_file[k] != '\0' && - s[k] == tests_list.v[j].test_file[k]; - k++) { } - if (file_root == -1 || file_root > k) - file_root = k; - } - - if (file_root == -1) { - for (i = 0; s[i] != '\0'; i++) - if (s[i] == '/' || s[i] == '\\') - file_root = i + 1; - } - } - - for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { - if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) - continue; - if (file == NULL || strcmp(file, tests_list.v[i].test_file) != 0) { - if (file != NULL) - quiet || printf("\n"); - file = tests_list.v[i].test_file; - no_color || print_color_(blue_); - quiet || printf("* "); - no_color || print_color_(white_); - quiet || printf("%s\n", file + file_root); - } - - !carriage_return || no_color || print_color_(yellow_); - carriage_return || no_color || print_color_(light_); - quiet || printf("` %s ", tests_list.v[i].test_name); - !carriage_return || quiet || printf("\r"); - quiet || fflush(stdout); - - struct timespec begin, end; - timespec_get(&begin, TIME_UTC); - - i32 test_status = run_test_(i); - - timespec_get(&end, TIME_UTC); - i32 duration = (i32) (ns_to_ms(end.tv_nsec - begin.tv_nsec) + sec_to_ms(end.tv_sec - begin.tv_sec)); - - for (j = 0; j < tests_list.v[i].assertions && j < MAX_NUM_TEST_ASSECTIONS; j++) - if (tests_list.v[i].status[j] == 0) { - fail_assertion_count++; - test_status = 0; - } - - if (tests_list.v[i].assertions > MAX_NUM_TEST_ASSECTIONS) - test_status = 0; - - total_assertion_count += tests_list.v[i].assertions; - - !carriage_return || no_color || print_color_(light_); - !carriage_return || quiet || printf("` %s ", tests_list.v[i].test_name); - - i32 l = (i32) strlen(tests_list.v[i].test_name); - quiet || printf("%*c", line_width - l, ' '); - - if (test_status == 0) { - no_color || print_color_(red_); - quiet || printf("FAIL"); - no_color || print_color_(light_); - duration == 0 || quiet || printf(" %d ms", duration); - quiet || printf("\n"); - status = 1; - } else { - no_color || print_color_(green_); - quiet || printf("OK"); - no_color || print_color_(light_); - duration == 0 || quiet || printf(" %d ms", duration); - quiet || printf("\n"); - success_count++; - } - - quiet || fflush(stdout); - } - - no_color || print_color_(white_); - quiet || printf("\n%d of %d tests passed.\n", success_count, tests_total); - quiet || printf("%d of %d assertions passed.\n\n", total_assertion_count - fail_assertion_count, total_assertion_count); - no_color || print_color_(light_); - - if (!quiet && status != 0) { - i32 have_report_s = 0; - - for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { - if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) - continue; - if (tests_list.v[i].signal != 0) { - i32 signum = tests_list.v[i].signal; - if (signum >= 0 && signum < (int) (sizeof signames_ / sizeof *signames_) && signames_[signum] != NULL) { - no_color || print_color_(light_); - printf("Signal \"%s\" (%d) for \"", signames_[signum], signum); - no_color || print_color_(white_); - printf("%s", tests_list.v[i].test_name); - no_color || print_color_(light_); - printf("\" in \""); - no_color || print_color_(white_); - - printf("%s", tests_list.v[i].test_file + file_root); - no_color || print_color_(light_); - printf("\"!\n"); - } else { - no_color || print_color_(light_); - printf("Unknown signal (%d) for \"", signum); - no_color || print_color_(white_); - printf("%s", tests_list.v[i].test_name); - no_color || print_color_(light_); - printf("\" in \""); - no_color || print_color_(white_); - printf("%s", tests_list.v[i].test_file + file_root); - no_color || print_color_(light_); - printf("\"!\n"); - } - have_report_s = 1; - } - if (tests_list.v[i].assertions > - MAX_NUM_TEST_ASSECTIONS) { - no_color || print_color_(light_); - printf("Too many assertions for \""); - no_color || print_color_(white_); - printf("%s", tests_list.v[i].test_name); - no_color || print_color_(light_); - printf("\" in \""); - no_color || print_color_(white_); - printf("%s", tests_list.v[i].test_file + file_root); - no_color || print_color_(light_); - printf("\"!\n"); - have_report_s = 1; - } - } - - have_report_s && printf("\n"); - } - - if (!quiet && status != 0) { - for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { - if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) - continue; - - if (tests_list.v[i].assertions <= MAX_NUM_TEST_ASSECTIONS) - for (j = 0; j < tests_list.v[i].assertions; j++) - if (!tests_list.v[i].status[j]) { - no_color || print_color_(light_); - printf("Assertion on line "); - no_color || print_color_(white_); - printf("%d", tests_list.v[i].line[j]); - no_color || print_color_(light_); - printf(" in \""); - no_color || print_color_(white_); - printf("%s", tests_list.v[i].test_file + file_root); - no_color || print_color_(light_); - printf("\" failed.\n"); - no_color || print_color_(red_); - printf(" -> "); - no_color || print_color_(light_); - printf("Got wrong value "); - no_color || print_color_(white_); - printf("%10lld", (i64) tests_list.v[i].value[j]); - no_color || print_color_(light_); - printf(" ("); - no_color || print_color_(white_); - printf("0x%08llx", (u64) tests_list.v[i].value[j]); - no_color || print_color_(light_); - printf(")\n"); - no_color || print_color_(green_); - printf(" -> "); - no_color || print_color_(light_); - printf("Expected value "); - no_color || print_color_(white_); - printf("%10lld", (i64) tests_list.v[i].expected[j]); - no_color || print_color_(light_); - printf(" ("); - no_color || print_color_(white_); - printf("0x%08llx", (u64) tests_list.v[i].expected[j]); - no_color || print_color_(light_); - printf(")\n\n"); - } - } - } - - if (tests_list.size > MAX_NUM_TESTS) { - no_color || print_color_(light_); - quiet || printf("Too many tests!\n\n"); - status = 1; - } - - if (status == 0) { - no_color || print_color_(green_); - quiet || printf("OK\n"); - } else { - no_color || print_color_(red_); - quiet || printf("FAILED\n"); - } - - no_color || print_color_(light_); - quiet || printf("\n"); - return status; -} - -// ================================================================ - -Benchs_List benchs_list = { 0 }; - -static void bench_set_repeats_limit(i32 i, i32 repeats_limit) { - if (benchs_list.v[i].ready) - return; - if (benchs_list.v[i].cycles_size >= MAX_NUM_BENCH_CYCLES) - return; - benchs_list.v[i].cycles[benchs_list.v[i].cycles_size] = repeats_limit; - benchs_list.v[i].cycles_size++; -} - -static i32 bench_loop(i32 i) { - if (!benchs_list.v[i].ready) - return 0; - return benchs_list.v[i].repeats < benchs_list.v[i].cycles[benchs_list.v[i].cycle]; -} - -static void bench_begin(i32 i) { - i32 n = benchs_list.v[i].repeats++; - - if (n >= MAX_NUM_BENCH_REPEATS) - return; - - struct timespec tv; - timespec_get(&tv, TIME_UTC); - - benchs_list.v[i].sec[n] = (int64_t) tv.tv_sec; - benchs_list.v[i].nsec[n] = (int64_t) tv.tv_nsec; -} - -static void bench_end(i32 i) { - i32 n = benchs_list.v[i].repeats - 1; - - if (n < 0 || n >= MAX_NUM_BENCH_REPEATS) - return; - - struct timespec tv; - timespec_get(&tv, TIME_UTC); - - i64 sec = ((i64) tv.tv_sec) - benchs_list.v[i].sec[n]; - i64 nsec = ((i64) tv.tv_nsec) - benchs_list.v[i].nsec[n]; - - benchs_list.v[i].duration_nsec[n] = sec * 1000000000 + nsec; -} - -void bench_register(c8 const *name, c8 const *file, bench_run_fn fn) { - i32 n = benchs_list.size++; - if (n < MAX_NUM_BENCHS) { - Benchmark *bench = benchs_list.v + n; - - bench->bench_fn = fn; - bench->bench_name = name; - bench->bench_file = file; - bench->cycles_size = 0; - bench->ready = 0; - } -} - -static void bench_setup_signals() { - for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) - signal(signums_[i], test_handle_signal); -} - -static i32 run_bench_(volatile i32 i) { - i32 signum = setjmp(test_restore_execution); - - if (signum != 0) { - benchs_list.v[i].signal = signum; - return 0; - } - - benchs_list.v[i].bench_fn(i, bench_set_repeats_limit, bench_loop, bench_begin, bench_end); - return 1; -} - -static i32 compare_64_(void const *x_, void const *y_) { - i64 const *x = (i64 const *) x_; - i64 const *y = (i64 const *) y_; - return *x - *y; -} - -static i32 compare_32_(void const *x_, void const *y_) { - i32 const *x = (i32 const *) x_; - i32 const *y = (i32 const *) y_; - return *x - *y; -} - -i32 run_benchmarks(i32 argc, c8 **argv) { - i32 success_count = 0; - i32 status = 0; - i32 no_color = 0; - i32 line_width = 20; - i32 carriage_return = 1; - - c8 const *specific_bench = NULL; - - bench_setup_signals(); - - for (i32 i = 0; i < argc; i++) - if (strcmp("--no-term-color", argv[i]) == 0) - no_color = 1; - else if (strcmp("--no-carriage-return", argv[i]) == 0) - carriage_return = 0; - else if (strcmp("--match", argv[i]) == 0) - specific_bench = argv[++i]; - - if (specific_bench != NULL) { - no_color || print_color_(light_); - printf("Run benchmarks matching "); - no_color || print_color_(white_); - printf("*%s*", specific_bench); - no_color || print_color_(light_); - printf("\n\n"); - } - - c8 const *file = NULL; - i64 file_root = -1; - i32 benchs_total = 0; - - for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { - if (specific_bench != NULL && strstr(benchs_list.v[i].bench_name, specific_bench) == NULL) - continue; - benchs_total++; - i32 l = 2 + (int) strlen(benchs_list.v[i].bench_name); - if (line_width < l) - line_width = l; - } - - if (benchs_total > 0) { - c8 const *s = benchs_list.v[0].bench_file; - - for (i32 j = 1; j < benchs_list.size && j < MAX_NUM_BENCHS; j++) { - Benchmark *bench = benchs_list.v + j; - - if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) - continue; - if (strcmp(s, bench->bench_file) == 0) - continue; - i32 k = 0; - for (; s[k] != '\0' && bench->bench_file[k] != '\0' && s[k] == bench->bench_file[k]; k++) { } - if (file_root == -1 || file_root > k) - file_root = k; - } - - if (file_root == -1) { - for (i32 i = 0; s[i] != '\0'; i++) - if (s[i] == '/' || s[i] == '\\') - file_root = i + 1; - } - } - - no_color || print_color_(blue_); - printf("# "); - no_color || print_color_(light_); - printf("BENCHMARK"); - printf("%*c", line_width - 9, ' '); - no_color || print_color_(green_); - printf(" LOW "); - no_color || print_color_(light_); - printf("|"); - no_color || print_color_(blue_); - printf(" MEDIAN "); - no_color || print_color_(light_); - printf("|"); - no_color || print_color_(yellow_); - printf(" HIGH\n"); - no_color || print_color_(light_); - printf(" (in microseconds)\n\n"); - - // Prepare cycles. - // - - for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { - Benchmark *bench = benchs_list.v + i; - - if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) - continue; - - run_bench_(i); - - if (bench->cycles_size == 0) { - bench->cycles_size = 2; - bench->cycles[0] = NUM_BENCH_REPEATS_DEFAULT_1; - bench->cycles[1] = NUM_BENCH_REPEATS_DEFAULT_2; - } - - qsort(bench->cycles, bench->cycles_size, sizeof *bench->cycles, compare_32_); - - benchs_list.v[i].ready = 1; - } - - // Run cycles. - // - - for (i32 cycle = 0; cycle < MAX_NUM_BENCH_CYCLES; cycle++) { - // Prepare cycle. - // - - i32 cycles_done = 1; - - for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { - Benchmark *bench = benchs_list.v + i; - - if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) - continue; - if (cycle >= bench->cycles_size) - continue; - - bench->repeats = 0; - bench->cycle = cycle; - cycles_done = 0; - } - - if (cycles_done) - break; - - // Run benchmarks. - // - - for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { - Benchmark *bench = benchs_list.v + i; - - if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) - continue; - if (cycle >= bench->cycles_size) - continue; - - if (file == NULL || strcmp(file, bench->bench_file) != 0) { - if (file != NULL) - printf("\n"); - file = bench->bench_file; - no_color || print_color_(blue_); - printf("* "); - no_color || print_color_(white_); - printf("%s\n", file + file_root); - } - - !carriage_return || no_color || print_color_(yellow_); - carriage_return || no_color || print_color_(light_); - printf("` %s ", bench->bench_name); - !carriage_return || printf("\r"); - fflush(stdout); - - i32 bench_status = run_bench_(i); - - if (bench->repeats > MAX_NUM_BENCH_REPEATS) - bench_status = 0; - - !carriage_return || no_color || print_color_(light_); - !carriage_return || printf("` %s ", bench->bench_name); - - i32 l = (i32) strlen(bench->bench_name); - printf("%*c", line_width - l, ' '); - - if (bench->repeats <= 0) { - no_color || print_color_(yellow_); - printf(" 0 runs\n"); - success_count++; - } else if (bench_status == 0) { - no_color || print_color_(red_); - printf(" FAIL\n"); - status = 1; - } else { - i32 repeats = bench->repeats; - - memcpy(bench->duration_sorted_nsec, bench->duration_nsec, repeats * sizeof *bench->duration_sorted_nsec); - qsort(bench->duration_sorted_nsec, repeats, sizeof *bench->duration_sorted_nsec, compare_64_); - - i64 average = bench->duration_sorted_nsec[repeats / 2]; - i64 floor = bench->duration_sorted_nsec[repeats / 20]; - i64 roof = bench->duration_sorted_nsec[repeats - repeats / 20 - 1]; - - no_color || print_color_(white_); - printf("%-9g", (f64) floor * 0.001); - no_color || print_color_(light_); - printf("| "); - no_color || print_color_(white_); - printf("%-9g", (f64) average * 0.001); - no_color || print_color_(light_); - printf("| "); - no_color || print_color_(white_); - printf("%-9g", (f64) roof * 0.001); - no_color || print_color_(light_); - printf(" %d runs\n", repeats); - success_count++; - } - } - } - - printf("\n"); - - if (status != 0) { - for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { - Benchmark *bench = benchs_list.v + i; - - if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) - continue; - if (bench->signal != 0) { - i32 signum = bench->signal; - if (signum >= 0 && signum < (i32) (sizeof signames_ / sizeof *signames_) && signames_[signum] != NULL) { - no_color || print_color_(light_); - printf("Signal \"%s\" (%d) for \"", signames_[signum], signum); - no_color || print_color_(white_); - printf("%s", bench->bench_name); - no_color || print_color_(light_); - printf("\" in \""); - no_color || print_color_(white_); - printf("%s", bench->bench_file + file_root); - no_color || print_color_(light_); - printf("\"!.\n"); - } else { - no_color || print_color_(light_); - printf("Unknown signal (%d) for \"", signum); - no_color || print_color_(white_); - printf("%s", bench->bench_name); - no_color || print_color_(light_); - printf("\" in \""); - no_color || print_color_(white_); - printf("%s", bench->bench_file + file_root); - no_color || print_color_(light_); - printf("\"!.\n"); - } - } - } - - printf("\n"); - } - - if (benchs_list.size > MAX_NUM_BENCHS) { - no_color || print_color_(light_); - printf("Too many benchmarks!\n\n"); - status = 1; - } - - if (status == 0) { - no_color || print_color_(green_); - printf("DONE\n"); - } else { - no_color || print_color_(red_); - printf("DONE WITH ERRORS\n"); - } - - no_color || print_color_(light_); - printf("\n"); - return status; -} - -#endif // TEST_IMPL_GUARD_ -#endif // TEST_HEADER diff --git a/examples/ui.c b/examples/ui.c index bd508e8..0847af3 100755 --- a/examples/ui.c +++ b/examples/ui.c @@ -25,326 +25,7 @@ gcc \ exit $? # */ #endif -#include "../reduced_system_layer.c" - -#define CHAR_NUM_BITS_X 6 -#define CHAR_NUM_BITS_Y 7 -#define CHAR_NUM_BITS (CHAR_NUM_BITS_X * CHAR_NUM_BITS_Y) - -enum { - OP_SET, - OP_XOR, -}; - -u64 bitfont[] = { - 0xbc0000000000, 0xc00300000, 0x5fd5040093f24fc9, 0xa00a2c2a1a280105, 0xc000415e6f, 0x400000020be0000, 0x1c38a8400000007d, 0x40002043e1020215, 0x408102000000010, 0x9800000000020002, 0xf913e00000033, 0x53200000207c8800, 0x3654880000099, 0x54b800000f840e00, 0xe953c000001a, 0x953e000000674080, 0x1e54b800000f, 0x490000000000240, 0x88a08000000, 0x20a220050a142850, 0x6520800000, 0x912f801eab260be, 0x800034952bf0001f, 0xc850bf0000921427, 0xf00010a54afc0003, 0xd29427800002142b, 0x840007e1023f0000, 0x7d09100000217e, 0x3f000188a08fc000, 0xc30c0cfc00000810, 0x27803f101013f00f, 0xc244bf0000f214, 0x4bf0002f21427800, 0xc254a480006c24, 0x407c00102fc08100, 0xf208080f0000fa0, 0x531007d81c607c0, 0xc208288c031141, 0x83fc00046954b10, 0x180e03000000, 0x41040000000ff04, 0x8102040810000404, 0x2a54600000000101, 0x309123e0000e, 0xc912180000a22447, 0x8000062a54700007, 0xe52a4300000029f0, 0xa0000602043e0001, 0x1d48000002074, 0x1f000003610f8000, 0x13e04f800000010, 0x470000780813e00f, 0x184893e0000e224, 0x23e0001f12243000, 0x82a54100000008, 0x40780000009f0200, 0xe208080e0001f20, 0xa22007981860780, 0x82082888022282, 0x16c200004ca95320, 0x7f000004, 0x408200000086d04, 0x8204, -}; - -#define BITFONT_LEN ((i64) (sizeof bitfont / sizeof *bitfont)) - -i64 char_column_offset(c32 c, i64 column_index) { - if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) - return -1; - return (c - 32) * CHAR_NUM_BITS + column_index * CHAR_NUM_BITS_Y; -} - -b8 char_bit(i64 column_offset, i64 row_index) { - if (column_offset < 0 || column_offset / 64 >= BITFONT_LEN || row_index < 0 || row_index >= CHAR_NUM_BITS_Y) - return 0; - - i64 bit_index = column_offset + row_index; - i64 qword_index = bit_index / 64; - if (qword_index < 0 || qword_index >= BITFONT_LEN) - return 0; - u64 mask = 1ull << (bit_index % 64); - return !!(bitfont[qword_index] & mask); -} - -u64 char_column_convolved(c32 c, i64 column_index) { - if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) - return 0; - - u64 column = 0; - i64 offset = char_column_offset(c, column_index); - - for (i64 y = 0; y < CHAR_NUM_BITS_Y; ++y) - if (char_bit(offset, y)) - column |= 3ull << y; - - return column; -} - -b8 char_column_empty(c32 c, i64 column_index) { - if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) - return 1; - - i64 offset = char_column_offset(c, column_index); - - for (i64 y = 0; y < CHAR_NUM_BITS_Y; ++y) - if (char_bit(offset, y)) - return 0; - - return 1; -} - -i64 char_width(c32 c) { - if (c < 32) - return 0; - if (c == ' ' || c > 127) - return 4; - - i64 width = 0; - - for (; width < CHAR_NUM_BITS_X; ++width) - if (char_column_empty(c, width) && char_column_empty(c, width + 1)) - break; - - return width; -} - -i64 char_spacing(i64 num_chars, c32 *text, i64 index) { - assert(text != NULL); - - if (index < 0 || index + 1 >= num_chars) - return 0; - - u64 a = char_column_convolved(text[index], char_width(text[index]) - 1); - u64 b = char_column_convolved(text[index + 1], 0); - - if (!!(a & b)) - return 1; - - return 0; -} - -i64 text_cursor(i64 num_chars, c32 *text) { - assert(text != NULL); - - i64 cursor = 0; - - for (i64 i = 0; i < num_chars; ++i) { - if (text[i] <= ' ') { - if (text[i] == '\n') - cursor = 0; - else if (text[i] == '\b' && i > 0) - cursor -= char_width(text[i - 1]) + char_spacing(num_chars, text, i - 1); - else if (text[i] == '\r') - cursor = 0; - else - cursor += char_width(' ') + char_spacing(num_chars, text, i); - continue; - } - cursor += char_width(text[i]) + char_spacing(num_chars, text, i); - } - - return cursor; -} - -i64 enum_text_columns(i64 num_chars, c32 *text) { - assert(text != NULL); - - i64 cols = 0; - i64 n = 0; - - for (i64 i = 0; i < num_chars; ++i) { - if (text[i] <= ' ') { - if (text[i] == '\n') { - if (cols < n) - cols = n; - n = 0; - } else if (text[i] == '\b' && i > 0) { - if (cols < n) - cols = n; - n -= char_width(text[i - 1]) + char_spacing(num_chars, text, i - 1); - } else if (text[i] == '\r') { - if (cols < n) - cols = n; - n = 0; - } else - n += char_width(' ') + char_spacing(num_chars, text, i); - continue; - } - n += char_width(text[i]) + char_spacing(num_chars, text, i); - } - - if (cols < n) - cols = n; - - return cols; -} - -i64 enum_text_rows(i64 num_chars, c32 *text) { - assert(text != NULL); - - i64 rows = 0; - - for (i64 i = 0; i <= num_chars; ++i) - if (i == num_chars || text[i] == '\n') { - if (rows > 0) - ++rows; - rows += CHAR_NUM_BITS_Y; - } - - return rows; -} - -void print_text(u32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_chars, c32 *text) { - assert(text != NULL); - - f64 x = x0; - f64 y = y0; - - f64 kx = scale_x; - f64 h = scale_y * CHAR_NUM_BITS_Y; - - for (i64 n = 0; n < num_chars; ++n) { - if (text[n] <= ' ') { - if (text[n] == '\n') { - x = x0; - y += scale_y * (CHAR_NUM_BITS_Y + 1); - } - else if (text[n] == '\b' && n > 0) - x -= kx * (char_width(text[n - 1]) + char_spacing(num_chars, text, n - 1)); - else if (text[n] == '\r') - x = x0; - else - x += kx * (char_width(' ') + char_spacing(num_chars, text, n)); - continue; - } - - i64 num_cols = char_width(text[n]); - f64 w = num_cols * kx; - - i64 i0 = (i64) floor(x + .5); - i64 i1 = (i64) floor(x + w + .5); - i64 j0 = (i64) floor(y + .5); - i64 j1 = (i64) floor(y + h + .5); - - for (i64 i = i0; i < i1; ++i) { - if (i < 0) continue; - if (i >= platform.frame_width) break; - - i64 column = ((i - i0) * num_cols) / (i1 - i0); - i64 offset = char_column_offset(text[n], column); - - for (i64 j = j0; j < j1; ++j) { - if (j < 0) continue; - if (j >= platform.frame_height) break; - - i64 row = ((j - j0) * CHAR_NUM_BITS_Y) / (j1 - j0); - - if (char_bit(offset, row)) - platform.pixels[j * platform.frame_width + i] = color; - } - } - - x += kx * (num_cols + char_spacing(num_chars, text, n)); - } -} - -void draw_panel(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height) { - i64 i0 = (i64) floor(x0 + .5); - i64 j0 = (i64) floor(y0 + .5); - i64 i1 = (i64) floor(x0 + width + .5); - i64 j1 = (i64) floor(y0 + height + .5); - - if (i0 < 0) i0 = 0; - if (j0 < 0) j0 = 0; - if (i1 >= platform.frame_width) i1 = platform.frame_width - 1; - if (j1 >= platform.frame_height) j1 = platform.frame_height - 1; - - for (i64 j = j0; j < j1; ++j) - for (i64 i = i0; i < i1; ++i) - if (op == OP_XOR) - platform.pixels[j * platform.frame_width + i] ^= color; - else - platform.pixels[j * platform.frame_width + i] = color; -} - -void draw_text_area(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 num_chars, c32 *text) { - assert(max_scale_x > 1e-6); - assert(max_scale_y > 1e-6); - - i64 num_columns = enum_text_columns(num_chars, text); - i64 num_rows = enum_text_rows(num_chars, text); - - f64 scale_x = width / num_columns; - f64 scale_y = height / num_rows; - - f64 kx = scale_x / max_scale_x; - f64 ky = scale_y / max_scale_y; - - f64 k = kx < ky ? kx : ky; - - kx = k * max_scale_x; - ky = k * max_scale_y; - - print_text(color, x0, y0, kx, ky, num_chars, text); -} - -void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c32 *text) { - assert(max_scale_x > 1e-6); - assert(max_scale_y > 1e-6); - - i64 num_columns = enum_text_columns(num_chars, text); - i64 num_rows = enum_text_rows(num_chars, text); - i64 cursor_x = text_cursor(cursor, text); - i64 cursor_y = enum_text_rows(cursor, text); - f64 scale_x = width / num_columns; - f64 scale_y = height / num_rows; - - f64 kx = scale_x / max_scale_x; - f64 ky = scale_y / max_scale_y; - - f64 k = kx < ky ? kx : ky; - - kx = k * max_scale_x; - ky = k * max_scale_y; - - if (selection != 0) { - i64 selection_x, selection_y; - - if (selection > 0) { - selection_x = text_cursor(cursor + selection, text); - selection_y = enum_text_rows(cursor + selection, text); - } else { - selection_x = cursor_x; - selection_y = cursor_y; - cursor_x = text_cursor(cursor + selection, text); - cursor_y = enum_text_rows(cursor + selection, text); - } - - if (cursor_y == selection_y) - draw_panel(OP_XOR, color, - x0 + kx * cursor_x, - y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), - kx * (selection_x - cursor_x), - ky * (CHAR_NUM_BITS_Y + 1) - ); - else { - draw_panel(OP_XOR, color, - x0 + kx * cursor_x, - y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), - kx * (num_columns - cursor_x), - ky * (CHAR_NUM_BITS_Y + 1) - ); - for (i64 j = cursor_y + CHAR_NUM_BITS_Y + 1; j < selection_y; j += CHAR_NUM_BITS_Y + 1) - draw_panel(OP_XOR, color, - x0, - y0 + ky * j - ky * (CHAR_NUM_BITS_Y + 1), - kx * num_columns, - ky * (CHAR_NUM_BITS_Y + 1) - ); - draw_panel(OP_XOR, color, - x0, - y0 + ky * selection_y - ky * (CHAR_NUM_BITS_Y + 1), - kx * selection_x, - ky * (CHAR_NUM_BITS_Y + 1) - ); - } - } else - draw_panel(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * CHAR_NUM_BITS_Y, kx * .5, ky * (CHAR_NUM_BITS_Y - 1)); -} +#include "../graphics.c" i32 main(i32 argc, c8 **argv) { (void) argc; diff --git a/graphics.c b/graphics.c new file mode 100644 index 0000000..b054db7 --- /dev/null +++ b/graphics.c @@ -0,0 +1,364 @@ +// ================================================================ +// +// graphics.c +// +// ================================================================ + +#ifndef GRAPHICS_HEADER_GUARD_ +#define GRAPHICS_HEADER_GUARD_ + +#ifdef GRAPHICS_HEADER +#define REDUCED_SYSTEM_LAYER_HEADER +#endif + +#include "reduced_system_layer.c" + +enum { + OP_SET, + OP_XOR, +}; + +u32 u32_from_rgb(f32 red, f32 green, f32 blue); +void draw_panel(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height); +void draw_text_area(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 num_chars, c32 *text); +void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c32 *text); + +#endif // GRAPHICS_HEADER_GUARD_ + +#ifndef GRAPHICS_HEADER +#ifndef GRAPHICS_IMPL_GUARD_ +#define GRAPHICS_IMPL_GUARD_ + +#include + +u32 u32_from_rgb(f32 red, f32 green, f32 blue) { + i32 r = (i32) floor(red * 255.f); + i32 g = (i32) floor(green * 255.f); + i32 b = (i32) floor(blue * 255.f); + + if (r < 0) r = 0; + if (r > 255) r = 255; + if (g < 0) g = 0; + if (g > 255) g = 255; + if (b < 0) b = 0; + if (b > 255) b = 255; + + return (r << 16) | (g << 8) | b; +} + +u64 bitfont[] = { + 0xbc0000000000, 0xc00300000, 0x5fd5040093f24fc9, 0xa00a2c2a1a280105, 0xc000415e6f, 0x400000020be0000, 0x1c38a8400000007d, 0x40002043e1020215, 0x408102000000010, 0x9800000000020002, 0xf913e00000033, 0x53200000207c8800, 0x3654880000099, 0x54b800000f840e00, 0xe953c000001a, 0x953e000000674080, 0x1e54b800000f, 0x490000000000240, 0x88a08000000, 0x20a220050a142850, 0x6520800000, 0x912f801eab260be, 0x800034952bf0001f, 0xc850bf0000921427, 0xf00010a54afc0003, 0xd29427800002142b, 0x840007e1023f0000, 0x7d09100000217e, 0x3f000188a08fc000, 0xc30c0cfc00000810, 0x27803f101013f00f, 0xc244bf0000f214, 0x4bf0002f21427800, 0xc254a480006c24, 0x407c00102fc08100, 0xf208080f0000fa0, 0x531007d81c607c0, 0xc208288c031141, 0x83fc00046954b10, 0x180e03000000, 0x41040000000ff04, 0x8102040810000404, 0x2a54600000000101, 0x309123e0000e, 0xc912180000a22447, 0x8000062a54700007, 0xe52a4300000029f0, 0xa0000602043e0001, 0x1d48000002074, 0x1f000003610f8000, 0x13e04f800000010, 0x470000780813e00f, 0x184893e0000e224, 0x23e0001f12243000, 0x82a54100000008, 0x40780000009f0200, 0xe208080e0001f20, 0xa22007981860780, 0x82082888022282, 0x16c200004ca95320, 0x7f000004, 0x408200000086d04, 0x8204, +}; + +#define CHAR_NUM_BITS_X 6 +#define CHAR_NUM_BITS_Y 7 +#define CHAR_NUM_BITS (CHAR_NUM_BITS_X * CHAR_NUM_BITS_Y) + +#define BITFONT_LEN ((i64) (sizeof bitfont / sizeof *bitfont)) + +i64 char_column_offset(c32 c, i64 column_index) { + if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) + return -1; + return (c - 32) * CHAR_NUM_BITS + column_index * CHAR_NUM_BITS_Y; +} + +b8 char_bit(i64 column_offset, i64 row_index) { + if (column_offset < 0 || column_offset / 64 >= BITFONT_LEN || row_index < 0 || row_index >= CHAR_NUM_BITS_Y) + return 0; + + i64 bit_index = column_offset + row_index; + i64 qword_index = bit_index / 64; + if (qword_index < 0 || qword_index >= BITFONT_LEN) + return 0; + u64 mask = 1ull << (bit_index % 64); + return !!(bitfont[qword_index] & mask); +} + +u64 char_column_convolved(c32 c, i64 column_index) { + if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) + return 0; + + u64 column = 0; + i64 offset = char_column_offset(c, column_index); + + for (i64 y = 0; y < CHAR_NUM_BITS_Y; ++y) + if (char_bit(offset, y)) + column |= 3ull << y; + + return column; +} + +b8 char_column_empty(c32 c, i64 column_index) { + if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) + return 1; + + i64 offset = char_column_offset(c, column_index); + + for (i64 y = 0; y < CHAR_NUM_BITS_Y; ++y) + if (char_bit(offset, y)) + return 0; + + return 1; +} + +i64 char_width(c32 c) { + if (c < 32) + return 0; + if (c == ' ' || c > 127) + return 4; + + i64 width = 0; + + for (; width < CHAR_NUM_BITS_X; ++width) + if (char_column_empty(c, width) && char_column_empty(c, width + 1)) + break; + + return width; +} + +i64 char_spacing(i64 num_chars, c32 *text, i64 index) { + assert(text != NULL); + + if (index < 0 || index + 1 >= num_chars) + return 0; + + u64 a = char_column_convolved(text[index], char_width(text[index]) - 1); + u64 b = char_column_convolved(text[index + 1], 0); + + if (!!(a & b)) + return 1; + + return 0; +} + +i64 text_cursor(i64 num_chars, c32 *text) { + assert(text != NULL); + + i64 cursor = 0; + + for (i64 i = 0; i < num_chars; ++i) { + if (text[i] <= ' ') { + if (text[i] == '\n') + cursor = 0; + else if (text[i] == '\b' && i > 0) + cursor -= char_width(text[i - 1]) + char_spacing(num_chars, text, i - 1); + else if (text[i] == '\r') + cursor = 0; + else + cursor += char_width(' ') + char_spacing(num_chars, text, i); + continue; + } + cursor += char_width(text[i]) + char_spacing(num_chars, text, i); + } + + return cursor; +} + +i64 enum_text_columns(i64 num_chars, c32 *text) { + assert(text != NULL); + + i64 cols = 0; + i64 n = 0; + + for (i64 i = 0; i < num_chars; ++i) { + if (text[i] <= ' ') { + if (text[i] == '\n') { + if (cols < n) + cols = n; + n = 0; + } else if (text[i] == '\b' && i > 0) { + if (cols < n) + cols = n; + n -= char_width(text[i - 1]) + char_spacing(num_chars, text, i - 1); + } else if (text[i] == '\r') { + if (cols < n) + cols = n; + n = 0; + } else + n += char_width(' ') + char_spacing(num_chars, text, i); + continue; + } + n += char_width(text[i]) + char_spacing(num_chars, text, i); + } + + if (cols < n) + cols = n; + + return cols; +} + +i64 enum_text_rows(i64 num_chars, c32 *text) { + assert(text != NULL); + + i64 rows = 0; + + for (i64 i = 0; i <= num_chars; ++i) + if (i == num_chars || text[i] == '\n') { + if (rows > 0) + ++rows; + rows += CHAR_NUM_BITS_Y; + } + + return rows; +} + +void print_text(u32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_chars, c32 *text) { + assert(text != NULL); + + f64 x = x0; + f64 y = y0; + + f64 kx = scale_x; + f64 h = scale_y * CHAR_NUM_BITS_Y; + + for (i64 n = 0; n < num_chars; ++n) { + if (text[n] <= ' ') { + if (text[n] == '\n') { + x = x0; + y += scale_y * (CHAR_NUM_BITS_Y + 1); + } + else if (text[n] == '\b' && n > 0) + x -= kx * (char_width(text[n - 1]) + char_spacing(num_chars, text, n - 1)); + else if (text[n] == '\r') + x = x0; + else + x += kx * (char_width(' ') + char_spacing(num_chars, text, n)); + continue; + } + + i64 num_cols = char_width(text[n]); + f64 w = num_cols * kx; + + i64 i0 = (i64) floor(x + .5); + i64 i1 = (i64) floor(x + w + .5); + i64 j0 = (i64) floor(y + .5); + i64 j1 = (i64) floor(y + h + .5); + + for (i64 i = i0; i < i1; ++i) { + if (i < 0) continue; + if (i >= platform.frame_width) break; + + i64 column = ((i - i0) * num_cols) / (i1 - i0); + i64 offset = char_column_offset(text[n], column); + + for (i64 j = j0; j < j1; ++j) { + if (j < 0) continue; + if (j >= platform.frame_height) break; + + i64 row = ((j - j0) * CHAR_NUM_BITS_Y) / (j1 - j0); + + if (char_bit(offset, row)) + platform.pixels[j * platform.frame_width + i] = color; + } + } + + x += kx * (num_cols + char_spacing(num_chars, text, n)); + } +} + +void draw_panel(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height) { + i64 i0 = (i64) floor(x0 + .5); + i64 j0 = (i64) floor(y0 + .5); + i64 i1 = (i64) floor(x0 + width + .5); + i64 j1 = (i64) floor(y0 + height + .5); + + if (i0 < 0) i0 = 0; + if (j0 < 0) j0 = 0; + if (i1 >= platform.frame_width) i1 = platform.frame_width - 1; + if (j1 >= platform.frame_height) j1 = platform.frame_height - 1; + + for (i64 j = j0; j < j1; ++j) + for (i64 i = i0; i < i1; ++i) + if (op == OP_XOR) + platform.pixels[j * platform.frame_width + i] ^= color; + else + platform.pixels[j * platform.frame_width + i] = color; +} + +void draw_text_area(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 num_chars, c32 *text) { + assert(max_scale_x > 1e-6); + assert(max_scale_y > 1e-6); + + i64 num_columns = enum_text_columns(num_chars, text); + i64 num_rows = enum_text_rows(num_chars, text); + + f64 scale_x = width / num_columns; + f64 scale_y = height / num_rows; + + f64 kx = scale_x / max_scale_x; + f64 ky = scale_y / max_scale_y; + + f64 k = kx < ky ? kx : ky; + + kx = k * max_scale_x; + ky = k * max_scale_y; + + print_text(color, x0, y0, kx, ky, num_chars, text); +} + +void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c32 *text) { + assert(max_scale_x > 1e-6); + assert(max_scale_y > 1e-6); + + i64 num_columns = enum_text_columns(num_chars, text); + i64 num_rows = enum_text_rows(num_chars, text); + i64 cursor_x = text_cursor(cursor, text); + i64 cursor_y = enum_text_rows(cursor, text); + f64 scale_x = width / num_columns; + f64 scale_y = height / num_rows; + + f64 kx = scale_x / max_scale_x; + f64 ky = scale_y / max_scale_y; + + f64 k = kx < ky ? kx : ky; + + kx = k * max_scale_x; + ky = k * max_scale_y; + + if (selection != 0) { + i64 selection_x, selection_y; + + if (selection > 0) { + selection_x = text_cursor(cursor + selection, text); + selection_y = enum_text_rows(cursor + selection, text); + } else { + selection_x = cursor_x; + selection_y = cursor_y; + cursor_x = text_cursor(cursor + selection, text); + cursor_y = enum_text_rows(cursor + selection, text); + } + + if (cursor_y == selection_y) + draw_panel(OP_XOR, color, + x0 + kx * cursor_x, + y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), + kx * (selection_x - cursor_x), + ky * (CHAR_NUM_BITS_Y + 1) + ); + else { + draw_panel(OP_XOR, color, + x0 + kx * cursor_x, + y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), + kx * (num_columns - cursor_x), + ky * (CHAR_NUM_BITS_Y + 1) + ); + for (i64 j = cursor_y + CHAR_NUM_BITS_Y + 1; j < selection_y; j += CHAR_NUM_BITS_Y + 1) + draw_panel(OP_XOR, color, + x0, + y0 + ky * j - ky * (CHAR_NUM_BITS_Y + 1), + kx * num_columns, + ky * (CHAR_NUM_BITS_Y + 1) + ); + draw_panel(OP_XOR, color, + x0, + y0 + ky * selection_y - ky * (CHAR_NUM_BITS_Y + 1), + kx * selection_x, + ky * (CHAR_NUM_BITS_Y + 1) + ); + } + } else + draw_panel(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * CHAR_NUM_BITS_Y, kx * .5, ky * (CHAR_NUM_BITS_Y - 1)); +} + +#endif // GRAPHICS_IMPL_GUARD_ +#endif // GRAPHICS_HEADER diff --git a/rf64.c b/rf64.c new file mode 100755 index 0000000..aeba85a --- /dev/null +++ b/rf64.c @@ -0,0 +1,252 @@ +#if 0 /* +#/ ================================================================ +#/ +#/ rf64.c +#/ +#/ Probabalistic floating-point numbers +#/ +#/ ---------------------------------------------------------------- +#/ +#/ (C) 2024 Mitya Selivanov , MIT License +#/ +#/ ================================================================ +#/ +#/ Self-compilation shell script +#/ +SRC=${0##*./} +BIN=${SRC%.*} +gcc \ + -Wall -Wextra -Werror -pedantic \ + -Wno-old-style-declaration \ + -Wno-missing-braces \ + -Wno-unused-variable \ + -Wno-unused-but-set-variable \ + -Wno-unused-parameter \ + -Wno-overlength-strings \ + -O3 \ + -fsanitize=undefined,address,leak -mshstk \ + -D RF64_TESTS \ + -o $BIN $SRC && \ + ./$BIN $@ && rm $BIN +exit $? # */ +#endif + +// ================================================================ + +#ifndef TYPES_HEADER_GUARD_ +#define TYPES_HEADER_GUARD_ + +typedef signed char i8; +typedef signed short i16; +typedef signed i32; +typedef signed long long i64; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned u32; +typedef unsigned long long u64; +typedef char c8; +typedef int c32; +typedef signed char b8; +typedef float f32; +typedef double f64; + +#endif // TYPES_HEADER_GUARD_ + +// ================================================================ + +#ifndef RF64_HEADER_GUARD_ +#define RF64_HEADER_GUARD_ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include + +#ifndef RF64_RAND +static i32 rf64_rand(i32 min, i32 max) { + if (max <= min) + return min; + return min + (rand() % (max - min + 1)); +} + +#define RF64_RAND(min, max) rf64_rand((min), (max)) +#endif + +#define RF64_EXPONENT_NUM_BITS 30 +#define RF64_MANTISSA_NUM_BITS 32 +#define RF64_EXPONENT_ZERO (1 << (RF64_EXPONENT_NUM_BITS / 2)) +#define RF64_EXPONENT_MAX ((1 << RF64_EXPONENT_NUM_BITS) - 1) +#define RF64_MANTISSA_MAX 0xffffffffu + +typedef struct { + u32 infinity : 1; + u32 sign : 1; + i32 exponent : RF64_EXPONENT_NUM_BITS; + u32 mantissa; +} rf64; + +rf64 rf64_from_i64(i64 x); +i64 i64_from_rf64(rf64 x); +void rf64_inc(rf64 *x); +void rf64_dec(rf64 *x); +void rf64_add(rf64 *x, rf64 y); +void rf64_sub(rf64 *x, rf64 y); +void rf64_mul(rf64 *x, rf64 y); +void rf64_div(rf64 *x, rf64 y); + +#endif // RF64_HEADER_GUARD_ + +#ifndef RF64_HEADER +#ifndef RF64_IMPL_GUARD_ +#define RF64_IMPL_GUARD_ + +rf64 rf64_from_i64(i64 x) { + rf64 z = { + .sign = x < 0, + .exponent = RF64_EXPONENT_ZERO, + }; + + u64 a = z.sign ? -x : x; + + while (a > RF64_MANTISSA_MAX) { + u64 loss_comp = (a & 1) == 1 ? RF64_RAND(0, 1) : 0; + + z.exponent += 1; + + a /= 2; + a += loss_comp; + } + + z.mantissa = (u32) a; + + return z; +} + +i64 i64_from_rf64(rf64 x) { + assert(0); +} + +void rf64_inc(rf64 *x) { + assert(0); +} + +void rf64_dec(rf64 *x) { + assert(x != NULL); + if (x == NULL) return; + + assert(0); +} + +void rf64_add(rf64 *x, rf64 y) { + assert(x != NULL); + if (x == NULL) return; + + assert(0); +} + +void rf64_sub(rf64 *x, rf64 y) { + assert(x != NULL); + if (x == NULL) return; + + assert(0); +} + +void rf64_mul(rf64 *x, rf64 y) { + assert(x != NULL); + if (x == NULL) return; + + assert(0); +} + +void rf64_div(rf64 *x, rf64 y) { + assert(x != NULL); + if (x == NULL) return; + + assert(0); +} + +#ifdef RF64_TESTS + +#include +#include + +void report_test(c8 *name, b8 success) { + i32 l = 20 - (i32) strlen(name); + + printf("%s %.*s %s\n", + name, + l < 1 ? 1 : l, + "....................", + success ? "OK" : "FAIL" + ); +} + +#define TEST_(x) report_test(#x, test_##x()) + +b8 test_from_i64() { + return 1 + && rf64_from_i64( 0 ).infinity == 0 + && rf64_from_i64( 0 ).sign == 0 + && rf64_from_i64( 0 ).exponent == RF64_EXPONENT_ZERO + && rf64_from_i64( 0 ).mantissa == 0 + && rf64_from_i64( 42 ).infinity == 0 + && rf64_from_i64( 42 ).sign == 0 + && rf64_from_i64( 42 ).exponent == RF64_EXPONENT_ZERO + && rf64_from_i64( 42 ).mantissa == 42 + && rf64_from_i64(-42 ).infinity == 0 + && rf64_from_i64(-42 ).sign == 1 + && rf64_from_i64(-42 ).exponent == RF64_EXPONENT_ZERO + && rf64_from_i64(-42 ).mantissa == 42 + && rf64_from_i64( 0xffffffff ).infinity == 0 + && rf64_from_i64( 0xffffffff ).sign == 0 + && rf64_from_i64( 0xffffffff ).exponent == RF64_EXPONENT_ZERO + && rf64_from_i64( 0xffffffff ).mantissa == 0xffffffff + && rf64_from_i64(-0xffffffffll ).infinity == 0 + && rf64_from_i64(-0xffffffffll ).sign == 1 + && rf64_from_i64(-0xffffffffll ).exponent == RF64_EXPONENT_ZERO + && rf64_from_i64(-0xffffffffll ).mantissa == 0xffffffff + && rf64_from_i64( 0x100000000ll).infinity == 0 + && rf64_from_i64( 0x100000000ll).sign == 0 + && rf64_from_i64( 0x100000000ll).exponent == RF64_EXPONENT_ZERO + 1 + && rf64_from_i64( 0x100000000ll).mantissa == 0x80000000u + && rf64_from_i64(-0x100000000ll).infinity == 0 + && rf64_from_i64(-0x100000000ll).sign == 1 + && rf64_from_i64(-0x100000000ll).exponent == RF64_EXPONENT_ZERO + 1 + && rf64_from_i64(-0x100000000ll).mantissa == 0x80000000u + && 1; +} + +b8 test_to_i64() { + return 1 + && i64_from_rf64(rf64_from_i64( 0 )) == 0 + && i64_from_rf64(rf64_from_i64( 1 )) == 1 + && i64_from_rf64(rf64_from_i64(-1 )) == -1 + && i64_from_rf64(rf64_from_i64( 42 )) == 42 + && i64_from_rf64(rf64_from_i64(-42 )) == -42 + && i64_from_rf64(rf64_from_i64( 0xffffffff )) == 0xffffffff + && i64_from_rf64(rf64_from_i64(-0xffffffff )) == -0xffffffff + && i64_from_rf64(rf64_from_i64( 0x100000000ll )) == 0x100000000ll + && i64_from_rf64(rf64_from_i64(-0x100000000ll )) == -0x100000000ll + && i64_from_rf64(rf64_from_i64( 0x42000000000ll)) == 0x42000000000ll + && i64_from_rf64(rf64_from_i64(-0x42000000000ll)) == -0x42000000000ll + && 1; +} + +i32 main(i32 argc, c8 **argv) { + (void) argc; + (void) argv; + + TEST_(from_i64); + TEST_(to_i64); + + return 0; +} + +#undef TEST_ + +#endif // RF64_TESTS + +#endif // RF64_IMPL_GUARD_ +#endif // RF64_HEADER diff --git a/test.c b/test.c new file mode 100644 index 0000000..c5e17bd --- /dev/null +++ b/test.c @@ -0,0 +1,989 @@ +// ================================================================ +// +// test.c +// +// Unit-testing and microbenchmarks modular library for C +// +// ---------------------------------------------------------------- +// +// Optional settings +// +// - MAX_NUM_TESTS +// - MAX_NUM_TEST_ASSECTIONS +// - MAX_NUM_BENCHS +// - MAX_NUM_BENCH_REPEATS +// - MAX_NUM_BENCH_CYCLES +// - NUM_BENCH_REPEATS_DEFAULT_1 +// - NUM_BENCH_REPEATS_DEFAULT_2 +// +// ---------------------------------------------------------------- +// +// Usage +// +// - Define a unique TEST_FILE for each file to avoid name +// collisions. +// +// Example +// +// // foo.test.c +// #define TEST_FILE foo // This is required if you want to +// // use multiple test files. +// #define TEST_HEADER // Do not include the implementation. +// #include "test.c" +// TEST("foo") { +// REQUIRE(1); +// REQUIRE_EQ(2 + 2, 4); +// } +// +// // main.c +// #include "test.c" +// int main(int argc, char **argv) { +// return run_tests(argc, argv); +// } +// +// ---------------------------------------------------------------- +// +// (C) 2024 Mitya Selivanov , MIT License +// +// ================================================================ + +#ifndef TYPES_HEADER_GUARD_ +#define TYPES_HEADER_GUARD_ + +typedef signed char i8; +typedef signed short i16; +typedef signed i32; +typedef signed long long i64; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned u32; +typedef unsigned long long u64; +typedef char c8; +typedef int c32; +typedef signed char b8; +typedef float f32; +typedef double f64; + +#endif // TYPES_HEADER_GUARD_ + +// ================================================================ + +#ifndef TEST_HEADER_GUARD_ +#define TEST_HEADER_GUARD_ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wunused-value" +# pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifndef TEST_FILE +#define TEST_FILE test +#endif + +#ifndef MAX_NUM_TESTS +#define MAX_NUM_TESTS 0x1000 +#endif + +#ifndef MAX_NUM_TEST_ASSECTIONS +#define MAX_NUM_TEST_ASSECTIONS 0x50 +#endif + +#ifndef MAX_NUM_BENCHS +#define MAX_NUM_BENCHS 200 +#endif + +#ifndef MAX_NUM_BENCH_REPEATS +#define MAX_NUM_BENCH_REPEATS 4000 +#endif + +#ifndef MAX_NUM_BENCH_CYCLES +#define MAX_NUM_BENCH_CYCLES 40 +#endif + +#ifndef NUM_BENCH_REPEATS_DEFAULT_1 +#define NUM_BENCH_REPEATS_DEFAULT_1 40 +#endif + +#ifndef NUM_BENCH_REPEATS_DEFAULT_2 +#define NUM_BENCH_REPEATS_DEFAULT_2 400 +#endif + +typedef void (*test_report_fn)(i32 test_index, i32 line, i64 value, i64 expected); +typedef void (*test_run_fn) (i32 test_index_, test_report_fn test_report_fn_); + +typedef struct { + c8 const *test_name; + c8 const *test_file; + test_run_fn test_fn; + i32 assertions; + i32 line [MAX_NUM_TEST_ASSECTIONS]; + i32 status [MAX_NUM_TEST_ASSECTIONS]; + i64 value [MAX_NUM_TEST_ASSECTIONS]; + i64 expected[MAX_NUM_TEST_ASSECTIONS]; + i32 signal; +} Test_Case; + +typedef struct { + i32 size; + Test_Case v[MAX_NUM_TESTS]; +} Tests_List; + +extern Tests_List tests_list; + +#define TEST_CONCAT4_(a, b, c, d) a##b##c##d +#define TEST_CONCAT3_(a, b, c) TEST_CONCAT4_(a, b, _, c) + +#ifdef __cplusplus +# define TEST_ON_START_(f) \ + static void f(void); \ + static i32 TEST_CONCAT3_(_test_init_, __LINE__, f) = (f(), 0); \ + static void f(void) +#else +# ifdef _MSC_VER +# pragma section(".CRT$XCU", read) +# define TEST_ON_START_2_(f, p) \ + static void f(void); \ + __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ + __pragma(comment(linker, "/include:" p #f "_")) static void f(void) +# ifdef _WIN64 +# define TEST_ON_START_(f) TEST_ON_START_2_(f, "") +# else +# define TEST_ON_START_(f) TEST_ON_START_2_(f, "_") +# endif +# else +# define TEST_ON_START_(f) \ + static void f(void) __attribute__((constructor)); \ + static void f(void) +# endif +#endif + +void test_register(c8 const *name, c8 const *file, test_run_fn fn); + +#define TEST(name) \ + static void TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__)(i32, test_report_fn); \ + TEST_ON_START_(TEST_CONCAT3_(test_case_, TEST_FILE, __LINE__)) { \ + test_register(name, __FILE__, TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__)); \ + } \ + static void TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__) \ + (i32 test_index_, test_report_fn test_report_fn_) + +#define REQUIRE(...) test_report_fn_(test_index_, __LINE__, (__VA_ARGS__), 1) +#define REQUIRE_EQ(...) test_report_fn_(test_index_, __LINE__, __VA_ARGS__) + +i32 run_tests(i32 argc, c8 **argv); + +// ================================================================ + +typedef void (*bench_set_repeats_limit_fn)(i32 bench_index, i32 repeats_limit); +typedef i32 (*bench_loop_fn) (i32 bench_index); +typedef void (*bench_begin_fn) (i32 bench_index); +typedef void (*bench_end_fn) (i32 bench_index); + +typedef void (*bench_run_fn)( + i32 bench_index_, + bench_set_repeats_limit_fn bench_set_repeats_limit_, + bench_loop_fn bench_loop_, + bench_begin_fn bench_begin_, + bench_end_fn bench_end_ +); + +typedef struct { + c8 const *bench_name; + c8 const *bench_file; + bench_run_fn bench_fn; + i64 sec [MAX_NUM_BENCH_REPEATS]; + i32 nsec [MAX_NUM_BENCH_REPEATS]; + i64 duration_nsec [MAX_NUM_BENCH_REPEATS]; + i64 duration_sorted_nsec[MAX_NUM_BENCH_REPEATS]; + i32 repeats; + i32 cycles_size; + i32 cycles[MAX_NUM_BENCH_CYCLES]; + i32 cycle; + i32 signal; + i32 ready; +} Benchmark; + +typedef struct { + i32 size; + Benchmark v[MAX_NUM_BENCHS]; +} Benchs_List; + +extern Benchs_List benchs_list; + +void bench_register(c8 const *name, c8 const *file, bench_run_fn fn); + +#define BENCHMARK(name) \ + static void TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__) \ + (i32, bench_set_repeats_limit_fn, bench_loop_fn, bench_begin_fn, bench_end_fn); \ + TEST_ON_START_(TEST_CONCAT3_(benchmark_, TEST_FILE, __LINE__)) { \ + bench_register(name, __FILE__, TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__)); \ + } \ + static void TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__)( \ + i32 bench_index_, \ + bench_set_repeats_limit_fn bench_set_repeats_limit_, \ + bench_loop_fn bench_loop_, \ + bench_begin_fn bench_begin_, \ + bench_end_fn bench_end_ \ + ) + +#define BENCHMARK_REPEAT(repeats_limit_) \ + bench_set_repeats_limit_(bench_index_, repeats_limit_) + +#define BENCHMARK_BEGIN \ + while (bench_loop_(bench_index_)) { \ + bench_begin_(bench_index_); { + +#define BENCHMARK_END \ + } bench_end_(bench_index_); } + +// FIXME +// Does this work reliably? +// +#define DO_NOT_OPTIMIZE(x) \ + do { \ + volatile void *bench_ptr_ = &(x); \ + (void) bench_ptr_; \ + } while (0) + +i32 run_benchmarks(i32 argc, c8 **argv); + +#ifdef __cplusplus +} +#endif + +#endif // TEST_HEADER_GUARD_ + +// ================================================================ +// +// Implementation +// +// ================================================================ + +#ifndef TEST_HEADER +#ifndef TEST_IMPL_GUARD_ +#define TEST_IMPL_GUARD_ + +#include +#include +#include +#include +#include +#include + +enum { + white_, + blue_, + light_, + yellow_, + red_, + green_ +}; + +static c8 const *color_codes_[] = { + "\x1b[38m", "\x1b[34m", + "\x1b[37m", "\x1b[33m", + "\x1b[31m", "\x1b[32m" +}; + +static i32 print_color_(i32 c) { + return printf("%s", color_codes_[c]); +} + +static i32 signums_[] = { SIGINT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGTERM }; + +static c8 const *signames_[64]; + +static void signames_init_(void) { + memset(signames_, 0, sizeof signames_); + signames_[SIGINT] = "Interactive attention signal"; + signames_[SIGILL] = "Illegal instruction"; + signames_[SIGABRT] = "Abnormal termination"; + signames_[SIGFPE] = "Erroneous arithmetic operation"; + signames_[SIGSEGV] = "Invalid access to storage"; + signames_[SIGTERM] = "Termination request"; +} + +Tests_List tests_list = { 0 }; + +static void report_(i32 i, i32 line, i64 value, i64 expected) { + i32 n = tests_list.v[i].assertions++; + + if (n >= MAX_NUM_TEST_ASSECTIONS) + return; + + tests_list.v[i].line[n] = line; + tests_list.v[i].status[n] = value == expected; + tests_list.v[i].value[n] = value; + tests_list.v[i].expected[n] = expected; +} + +static i64 ns_to_ms(i64 ns) { + return (ns + 500000) / 1000000; +} + +static i64 sec_to_ms(int64_t sec) { + return 1000 * sec; +} + +void test_register(c8 const *name, c8 const *file, test_run_fn fn) { + i32 n = tests_list.size++; + if (n < MAX_NUM_TESTS) { + tests_list.v[n].test_fn = fn; + tests_list.v[n].test_name = name; + tests_list.v[n].test_file = file; + tests_list.v[n].assertions = 0; + } +} + +static jmp_buf test_restore_execution; + +static void test_handle_signal(i32 signum) { + longjmp(test_restore_execution, signum); +} + +static void test_setup_signals() { + for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) + signal(signums_[i], test_handle_signal); +} + +static i32 run_test_(volatile i32 i) { + i32 signum = setjmp(test_restore_execution); + + if (signum != 0) { + tests_list.v[i].signal = signum; + return 0; + } + + tests_list.v[i].test_fn(i, report_); + return 1; +} + +i32 run_tests(i32 argc, c8 **argv) { + signames_init_(); + + i32 success_count = 0; + i32 fail_assertion_count = 0; + i32 total_assertion_count = 0; + i32 status = 0; + i32 quiet = 0; + i32 no_color = 0; + i32 line_width = 20; + i32 carriage_return = 1; + + i32 i, j; + + c8 const *specific_test = NULL; + + test_setup_signals(); + + for (i = 0; i < argc; i++) + if (strcmp("--no-term-color", argv[i]) == 0) + no_color = 1; + else if (strcmp("--no-carriage-return", argv[i]) == 0) + carriage_return = 0; + else if (strcmp("--quiet", argv[i]) == 0) + quiet = 1; + else if (strcmp("--match", argv[i]) == 0) + specific_test = argv[++i]; + + quiet && (no_color = 1); + + if (specific_test != NULL) { + no_color || print_color_(light_); + quiet || printf("Run tests matching "); + no_color || print_color_(white_); + quiet || printf("*%s*", specific_test); + no_color || print_color_(light_); + quiet || printf("\n\n"); + } + + c8 const *file = NULL; + i64 file_root = -1; + i32 tests_total = 0; + + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; + i++) { + if (specific_test != NULL && + strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + tests_total++; + i32 l = 2 + (int) strlen(tests_list.v[i].test_name); + if (line_width < l) + line_width = l; + } + + if (tests_total > 0) { + c8 const *s = tests_list.v[0].test_file; + + for (j = 1; j < tests_list.size && j < MAX_NUM_TESTS; + j++) { + if (specific_test != NULL && + strstr(tests_list.v[j].test_name, specific_test) == + NULL) + continue; + if (strcmp(s, tests_list.v[j].test_file) == 0) + continue; + i32 k = 0; + for (; + s[k] != '\0' && tests_list.v[j].test_file[k] != '\0' && + s[k] == tests_list.v[j].test_file[k]; + k++) { } + if (file_root == -1 || file_root > k) + file_root = k; + } + + if (file_root == -1) { + for (i = 0; s[i] != '\0'; i++) + if (s[i] == '/' || s[i] == '\\') + file_root = i + 1; + } + } + + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { + if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + if (file == NULL || strcmp(file, tests_list.v[i].test_file) != 0) { + if (file != NULL) + quiet || printf("\n"); + file = tests_list.v[i].test_file; + no_color || print_color_(blue_); + quiet || printf("* "); + no_color || print_color_(white_); + quiet || printf("%s\n", file + file_root); + } + + !carriage_return || no_color || print_color_(yellow_); + carriage_return || no_color || print_color_(light_); + quiet || printf("` %s ", tests_list.v[i].test_name); + !carriage_return || quiet || printf("\r"); + quiet || fflush(stdout); + + struct timespec begin, end; + timespec_get(&begin, TIME_UTC); + + i32 test_status = run_test_(i); + + timespec_get(&end, TIME_UTC); + i32 duration = (i32) (ns_to_ms(end.tv_nsec - begin.tv_nsec) + sec_to_ms(end.tv_sec - begin.tv_sec)); + + for (j = 0; j < tests_list.v[i].assertions && j < MAX_NUM_TEST_ASSECTIONS; j++) + if (tests_list.v[i].status[j] == 0) { + fail_assertion_count++; + test_status = 0; + } + + if (tests_list.v[i].assertions > MAX_NUM_TEST_ASSECTIONS) + test_status = 0; + + total_assertion_count += tests_list.v[i].assertions; + + !carriage_return || no_color || print_color_(light_); + !carriage_return || quiet || printf("` %s ", tests_list.v[i].test_name); + + i32 l = (i32) strlen(tests_list.v[i].test_name); + quiet || printf("%*c", line_width - l, ' '); + + if (test_status == 0) { + no_color || print_color_(red_); + quiet || printf("FAIL"); + no_color || print_color_(light_); + duration == 0 || quiet || printf(" %d ms", duration); + quiet || printf("\n"); + status = 1; + } else { + no_color || print_color_(green_); + quiet || printf("OK"); + no_color || print_color_(light_); + duration == 0 || quiet || printf(" %d ms", duration); + quiet || printf("\n"); + success_count++; + } + + quiet || fflush(stdout); + } + + no_color || print_color_(white_); + quiet || printf("\n%d of %d tests passed.\n", success_count, tests_total); + quiet || printf("%d of %d assertions passed.\n\n", total_assertion_count - fail_assertion_count, total_assertion_count); + no_color || print_color_(light_); + + if (!quiet && status != 0) { + i32 have_report_s = 0; + + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { + if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + if (tests_list.v[i].signal != 0) { + i32 signum = tests_list.v[i].signal; + if (signum >= 0 && signum < (int) (sizeof signames_ / sizeof *signames_) && signames_[signum] != NULL) { + no_color || print_color_(light_); + printf("Signal \"%s\" (%d) for \"", signames_[signum], signum); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\"!\n"); + } else { + no_color || print_color_(light_); + printf("Unknown signal (%d) for \"", signum); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\"!\n"); + } + have_report_s = 1; + } + if (tests_list.v[i].assertions > + MAX_NUM_TEST_ASSECTIONS) { + no_color || print_color_(light_); + printf("Too many assertions for \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\"!\n"); + have_report_s = 1; + } + } + + have_report_s && printf("\n"); + } + + if (!quiet && status != 0) { + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { + if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + + if (tests_list.v[i].assertions <= MAX_NUM_TEST_ASSECTIONS) + for (j = 0; j < tests_list.v[i].assertions; j++) + if (!tests_list.v[i].status[j]) { + no_color || print_color_(light_); + printf("Assertion on line "); + no_color || print_color_(white_); + printf("%d", tests_list.v[i].line[j]); + no_color || print_color_(light_); + printf(" in \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\" failed.\n"); + no_color || print_color_(red_); + printf(" -> "); + no_color || print_color_(light_); + printf("Got wrong value "); + no_color || print_color_(white_); + printf("%10lld", (i64) tests_list.v[i].value[j]); + no_color || print_color_(light_); + printf(" ("); + no_color || print_color_(white_); + printf("0x%08llx", (u64) tests_list.v[i].value[j]); + no_color || print_color_(light_); + printf(")\n"); + no_color || print_color_(green_); + printf(" -> "); + no_color || print_color_(light_); + printf("Expected value "); + no_color || print_color_(white_); + printf("%10lld", (i64) tests_list.v[i].expected[j]); + no_color || print_color_(light_); + printf(" ("); + no_color || print_color_(white_); + printf("0x%08llx", (u64) tests_list.v[i].expected[j]); + no_color || print_color_(light_); + printf(")\n\n"); + } + } + } + + if (tests_list.size > MAX_NUM_TESTS) { + no_color || print_color_(light_); + quiet || printf("Too many tests!\n\n"); + status = 1; + } + + if (status == 0) { + no_color || print_color_(green_); + quiet || printf("OK\n"); + } else { + no_color || print_color_(red_); + quiet || printf("FAILED\n"); + } + + no_color || print_color_(light_); + quiet || printf("\n"); + return status; +} + +// ================================================================ + +Benchs_List benchs_list = { 0 }; + +static void bench_set_repeats_limit(i32 i, i32 repeats_limit) { + if (benchs_list.v[i].ready) + return; + if (benchs_list.v[i].cycles_size >= MAX_NUM_BENCH_CYCLES) + return; + benchs_list.v[i].cycles[benchs_list.v[i].cycles_size] = repeats_limit; + benchs_list.v[i].cycles_size++; +} + +static i32 bench_loop(i32 i) { + if (!benchs_list.v[i].ready) + return 0; + return benchs_list.v[i].repeats < benchs_list.v[i].cycles[benchs_list.v[i].cycle]; +} + +static void bench_begin(i32 i) { + i32 n = benchs_list.v[i].repeats++; + + if (n >= MAX_NUM_BENCH_REPEATS) + return; + + struct timespec tv; + timespec_get(&tv, TIME_UTC); + + benchs_list.v[i].sec[n] = (int64_t) tv.tv_sec; + benchs_list.v[i].nsec[n] = (int64_t) tv.tv_nsec; +} + +static void bench_end(i32 i) { + i32 n = benchs_list.v[i].repeats - 1; + + if (n < 0 || n >= MAX_NUM_BENCH_REPEATS) + return; + + struct timespec tv; + timespec_get(&tv, TIME_UTC); + + i64 sec = ((i64) tv.tv_sec) - benchs_list.v[i].sec[n]; + i64 nsec = ((i64) tv.tv_nsec) - benchs_list.v[i].nsec[n]; + + benchs_list.v[i].duration_nsec[n] = sec * 1000000000 + nsec; +} + +void bench_register(c8 const *name, c8 const *file, bench_run_fn fn) { + i32 n = benchs_list.size++; + if (n < MAX_NUM_BENCHS) { + Benchmark *bench = benchs_list.v + n; + + bench->bench_fn = fn; + bench->bench_name = name; + bench->bench_file = file; + bench->cycles_size = 0; + bench->ready = 0; + } +} + +static void bench_setup_signals() { + for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) + signal(signums_[i], test_handle_signal); +} + +static i32 run_bench_(volatile i32 i) { + i32 signum = setjmp(test_restore_execution); + + if (signum != 0) { + benchs_list.v[i].signal = signum; + return 0; + } + + benchs_list.v[i].bench_fn(i, bench_set_repeats_limit, bench_loop, bench_begin, bench_end); + return 1; +} + +static i32 compare_64_(void const *x_, void const *y_) { + i64 const *x = (i64 const *) x_; + i64 const *y = (i64 const *) y_; + return *x - *y; +} + +static i32 compare_32_(void const *x_, void const *y_) { + i32 const *x = (i32 const *) x_; + i32 const *y = (i32 const *) y_; + return *x - *y; +} + +i32 run_benchmarks(i32 argc, c8 **argv) { + i32 success_count = 0; + i32 status = 0; + i32 no_color = 0; + i32 line_width = 20; + i32 carriage_return = 1; + + c8 const *specific_bench = NULL; + + bench_setup_signals(); + + for (i32 i = 0; i < argc; i++) + if (strcmp("--no-term-color", argv[i]) == 0) + no_color = 1; + else if (strcmp("--no-carriage-return", argv[i]) == 0) + carriage_return = 0; + else if (strcmp("--match", argv[i]) == 0) + specific_bench = argv[++i]; + + if (specific_bench != NULL) { + no_color || print_color_(light_); + printf("Run benchmarks matching "); + no_color || print_color_(white_); + printf("*%s*", specific_bench); + no_color || print_color_(light_); + printf("\n\n"); + } + + c8 const *file = NULL; + i64 file_root = -1; + i32 benchs_total = 0; + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + if (specific_bench != NULL && strstr(benchs_list.v[i].bench_name, specific_bench) == NULL) + continue; + benchs_total++; + i32 l = 2 + (int) strlen(benchs_list.v[i].bench_name); + if (line_width < l) + line_width = l; + } + + if (benchs_total > 0) { + c8 const *s = benchs_list.v[0].bench_file; + + for (i32 j = 1; j < benchs_list.size && j < MAX_NUM_BENCHS; j++) { + Benchmark *bench = benchs_list.v + j; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (strcmp(s, bench->bench_file) == 0) + continue; + i32 k = 0; + for (; s[k] != '\0' && bench->bench_file[k] != '\0' && s[k] == bench->bench_file[k]; k++) { } + if (file_root == -1 || file_root > k) + file_root = k; + } + + if (file_root == -1) { + for (i32 i = 0; s[i] != '\0'; i++) + if (s[i] == '/' || s[i] == '\\') + file_root = i + 1; + } + } + + no_color || print_color_(blue_); + printf("# "); + no_color || print_color_(light_); + printf("BENCHMARK"); + printf("%*c", line_width - 9, ' '); + no_color || print_color_(green_); + printf(" LOW "); + no_color || print_color_(light_); + printf("|"); + no_color || print_color_(blue_); + printf(" MEDIAN "); + no_color || print_color_(light_); + printf("|"); + no_color || print_color_(yellow_); + printf(" HIGH\n"); + no_color || print_color_(light_); + printf(" (in microseconds)\n\n"); + + // Prepare cycles. + // + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + + run_bench_(i); + + if (bench->cycles_size == 0) { + bench->cycles_size = 2; + bench->cycles[0] = NUM_BENCH_REPEATS_DEFAULT_1; + bench->cycles[1] = NUM_BENCH_REPEATS_DEFAULT_2; + } + + qsort(bench->cycles, bench->cycles_size, sizeof *bench->cycles, compare_32_); + + benchs_list.v[i].ready = 1; + } + + // Run cycles. + // + + for (i32 cycle = 0; cycle < MAX_NUM_BENCH_CYCLES; cycle++) { + // Prepare cycle. + // + + i32 cycles_done = 1; + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (cycle >= bench->cycles_size) + continue; + + bench->repeats = 0; + bench->cycle = cycle; + cycles_done = 0; + } + + if (cycles_done) + break; + + // Run benchmarks. + // + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (cycle >= bench->cycles_size) + continue; + + if (file == NULL || strcmp(file, bench->bench_file) != 0) { + if (file != NULL) + printf("\n"); + file = bench->bench_file; + no_color || print_color_(blue_); + printf("* "); + no_color || print_color_(white_); + printf("%s\n", file + file_root); + } + + !carriage_return || no_color || print_color_(yellow_); + carriage_return || no_color || print_color_(light_); + printf("` %s ", bench->bench_name); + !carriage_return || printf("\r"); + fflush(stdout); + + i32 bench_status = run_bench_(i); + + if (bench->repeats > MAX_NUM_BENCH_REPEATS) + bench_status = 0; + + !carriage_return || no_color || print_color_(light_); + !carriage_return || printf("` %s ", bench->bench_name); + + i32 l = (i32) strlen(bench->bench_name); + printf("%*c", line_width - l, ' '); + + if (bench->repeats <= 0) { + no_color || print_color_(yellow_); + printf(" 0 runs\n"); + success_count++; + } else if (bench_status == 0) { + no_color || print_color_(red_); + printf(" FAIL\n"); + status = 1; + } else { + i32 repeats = bench->repeats; + + memcpy(bench->duration_sorted_nsec, bench->duration_nsec, repeats * sizeof *bench->duration_sorted_nsec); + qsort(bench->duration_sorted_nsec, repeats, sizeof *bench->duration_sorted_nsec, compare_64_); + + i64 average = bench->duration_sorted_nsec[repeats / 2]; + i64 floor = bench->duration_sorted_nsec[repeats / 20]; + i64 roof = bench->duration_sorted_nsec[repeats - repeats / 20 - 1]; + + no_color || print_color_(white_); + printf("%-9g", (f64) floor * 0.001); + no_color || print_color_(light_); + printf("| "); + no_color || print_color_(white_); + printf("%-9g", (f64) average * 0.001); + no_color || print_color_(light_); + printf("| "); + no_color || print_color_(white_); + printf("%-9g", (f64) roof * 0.001); + no_color || print_color_(light_); + printf(" %d runs\n", repeats); + success_count++; + } + } + } + + printf("\n"); + + if (status != 0) { + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (bench->signal != 0) { + i32 signum = bench->signal; + if (signum >= 0 && signum < (i32) (sizeof signames_ / sizeof *signames_) && signames_[signum] != NULL) { + no_color || print_color_(light_); + printf("Signal \"%s\" (%d) for \"", signames_[signum], signum); + no_color || print_color_(white_); + printf("%s", bench->bench_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", bench->bench_file + file_root); + no_color || print_color_(light_); + printf("\"!.\n"); + } else { + no_color || print_color_(light_); + printf("Unknown signal (%d) for \"", signum); + no_color || print_color_(white_); + printf("%s", bench->bench_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", bench->bench_file + file_root); + no_color || print_color_(light_); + printf("\"!.\n"); + } + } + } + + printf("\n"); + } + + if (benchs_list.size > MAX_NUM_BENCHS) { + no_color || print_color_(light_); + printf("Too many benchmarks!\n\n"); + status = 1; + } + + if (status == 0) { + no_color || print_color_(green_); + printf("DONE\n"); + } else { + no_color || print_color_(red_); + printf("DONE WITH ERRORS\n"); + } + + no_color || print_color_(light_); + printf("\n"); + return status; +} + +#endif // TEST_IMPL_GUARD_ +#endif // TEST_HEADER -- cgit v1.2.3