#include "libc/math.h"
#include "third_party/chibicc/test/test.h"

#define FPNAN       0
#define FPINFINITE  1
#define FPZERO      2
#define FPSUBNORMAL 3
#define FPNORMAL    4
#define FPCLASSIFY(x) \
  __builtin_fpclassify(FPNAN, FPINFINITE, FPNORMAL, FPSUBNORMAL, FPZERO, x)

#define conceal(x)         \
  ({                       \
    typeof(x) cloak = x;   \
    asm("" : "+r"(cloak)); \
    cloak;                 \
  })

struct frame {
  struct frame *next;
  intptr_t addr;
};

struct frame *frame(void) {
  struct frame *myframe = __builtin_frame_address(0);
  struct frame *parentframe = myframe->next;
  return parentframe;
}

void test_frame_address(void) {
  ASSERT(1, __builtin_frame_address(0) == frame());
}

void test_constant(void) {
  ASSERT(1, __builtin_constant_p(1));
  ASSERT(0, __builtin_constant_p(conceal(1)));
  ASSERT(0, __builtin_constant_p(stdin));
  ASSERT(1, __builtin_constant_p(__builtin_popcount(0b10111011)));
  ASSERT(1, __builtin_constant_p(__builtin_popcountll((size_t)0b10111011)));
}

void test_ignored(void) {
  ASSERT(123, __builtin_assume_aligned(123, 8));
  ASSERT(123, __builtin_assume_aligned(123, 32, 8));
  ASSERT(123, __builtin_expect(123, 0));
}

void __attribute__((__aligned__(16))) test_clz(void) {
  ASSERT(31, __builtin_clz(1));
  ASSERT(63, __builtin_clzl(1));
  ASSERT(63, __builtin_clzll(1));
  ASSERT(25, __builtin_clz(77));
  ASSERT(4, __builtin_clz(0x08000000));
  ASSERT(4, __builtin_clz(0xfff08000000));
  ASSERT(25, __builtin_clz(conceal(77)));
  __builtin_clz(conceal(77));
}

__attribute__((__weak__)) void test_ctz(void) {
  ASSERT(0, __builtin_ctz(1));
  ASSERT(0, __builtin_ctz(77));
  ASSERT(27, __builtin_ctz(0x08000000));
  ASSERT(27, __builtin_ctz(0xffff08000000));
  ASSERT(63, __builtin_ctzl(0x8000000000000000));
  ASSERT(63, __builtin_ctzll(0x8000000000000000));
  ASSERT(0, __builtin_ctz(conceal(77)));
}

void test_ffs(void) {
  ASSERT(0, __builtin_ffs(0));
  ASSERT(1, __builtin_ffs(1));
  ASSERT(1, __builtin_ffs(77));
  ASSERT(28, __builtin_ffs(0x08000000));
  ASSERT(28, __builtin_ffs(0xffff08000000));
  ASSERT(1, __builtin_ffs(conceal(77)));
}

void test_popcnt(void) {
  ASSERT(0, __builtin_popcount(0));
  ASSERT(1, __builtin_popcount(1));
  ASSERT(6, __builtin_popcount(0b10111011));
  ASSERT(6, __builtin_popcountl(0b10111011));
  ASSERT(6, __builtin_popcountll(0xbb00000000000000));
  ASSERT(6, __builtin_popcountl(conceal(0b10111011)));
}

void test_bswap(void) {
  ASSERT(0x3412, __builtin_bswap16(0x1234));
  ASSERT(0x78563412, __builtin_bswap32(0x12345678));
  ASSERT(0xefcdab8967452301, __builtin_bswap64(0x0123456789abcdef));
  ASSERT(0xefcdab89, __builtin_bswap32(0x0123456789abcdef));
  ASSERT(0x78563412, __builtin_bswap32(conceal(0x12345678)));
}

void test_fpclassify(void) {
  float minf = __FLT_MIN__;
  double min = __DBL_MIN__;
  long double minl = __LDBL_MIN__;
  ASSERT(FPZERO, FPCLASSIFY(.0f));
  ASSERT(FPZERO, FPCLASSIFY(.0));
  ASSERT(FPZERO, FPCLASSIFY(.0L));
  ASSERT(FPNORMAL, FPCLASSIFY(1.f));
  ASSERT(FPNORMAL, FPCLASSIFY(1.));
  ASSERT(FPNORMAL, FPCLASSIFY(1.L));
  ASSERT(FPNORMAL, FPCLASSIFY(1.f));
  ASSERT(FPNORMAL, FPCLASSIFY(1.));
  ASSERT(FPNORMAL, FPCLASSIFY(1.L));
  ASSERT(FPNORMAL, FPCLASSIFY(minf));
  ASSERT(FPNORMAL, FPCLASSIFY(min));
  ASSERT(FPNORMAL, FPCLASSIFY(minl));
  ASSERT(FPSUBNORMAL, FPCLASSIFY(minf / 2));
  ASSERT(FPSUBNORMAL, FPCLASSIFY(min / 2));
  ASSERT(FPSUBNORMAL, FPCLASSIFY(minl / 2));
  ASSERT(FPNAN, FPCLASSIFY(__builtin_nanf("")));
  ASSERT(FPNAN, FPCLASSIFY(__builtin_nan("")));
  ASSERT(FPNAN, FPCLASSIFY(__builtin_nanl("")));
}

void test_logb(void) {
  ASSERT(6, __builtin_logbl(123.456));
  ASSERT(logbl(123.456L), __builtin_logbl(123.456L));
  ASSERT(logbl(__LDBL_MIN__), __builtin_logbl(__LDBL_MIN__));
  ASSERT(logbl(__LDBL_MAX__), __builtin_logbl(__LDBL_MAX__));
}

void test_fmax(void) {
  ASSERT(fmaxl(1, 2), __builtin_fmaxl(1, 2));
  ASSERT(2, __builtin_fmaxl(__builtin_nanl(""), 2));
  ASSERT(1, __builtin_fmaxl(1, __builtin_nanl("")));
  ASSERT(2, fmaxl(nanl(""), 2));
  ASSERT(1, fmaxl(1, nanl("")));
  ASSERT(fmaxf(1, 2), __builtin_fmaxf(1, 2));
  ASSERT(2, __builtin_fmaxf(__builtin_nanl(""), 2));
  ASSERT(1, __builtin_fmaxf(1, __builtin_nanl("")));
  ASSERT(2, fmaxf(nanl(""), 2));
  ASSERT(1, fmaxf(1, nanl("")));
  ASSERT(fmax(1, 2), __builtin_fmax(1, 2));
  ASSERT(2, __builtin_fmax(__builtin_nanl(""), 2));
  ASSERT(1, __builtin_fmax(1, __builtin_nanl("")));
  ASSERT(2, fmax(nanl(""), 2));
  ASSERT(1, fmax(1, nanl("")));
}

void test_strlen(void) {
  ASSERT(5, strlen("hello"));
  ASSERT(5, __builtin_strlen("hello"));
}

void test_strchr(void) {
  ASSERT(1, __builtin_strchr("hello", 'z') == NULL);
  ASSERT(0, (strcmp)(__builtin_strchr("hello", 'e'), "ello"));
}

void test_strpbrk(void) {
  ASSERT(1, __builtin_strpbrk("hello", "z") == NULL);
  ASSERT(0, (strcmp)(__builtin_strpbrk("hello", "ze"), "ello"));
}

void test_strstr(void) {
  ASSERT(1, __builtin_strstr("hello", "sup") == NULL);
  ASSERT(0, (strcmp)(__builtin_strstr("hello", "ell"), "ello"));
}

void test_memcpy(void) {
  {
    char x[5] = {4, 3, 2, 1, 0};
    char y[5] = {0, 1, 2, 3, 4};
    char z[5] = {2, 3, 4, 1, 0};
    ASSERT(1, x == (memcpy)(x, y + 2, 3));
    ASSERT(0, memcmp(x, z, 5));
  }
  {
    char x[5] = {4, 3, 2, 1, 0};
    char y[5] = {0, 1, 2, 3, 4};
    char z[5] = {2, 3, 4, 1, 0};
    ASSERT(1, x == __builtin_memcpy(x, y + 2, 3));
    ASSERT(0, memcmp(x, z, 5));
  }
  {
    int n = 3;
    char x[5] = {4, 3, 2, 1, 0};
    char y[5] = {0, 1, 2, 3, 4};
    char z[5] = {2, 3, 4, 1, 0};
    ASSERT(1, x == __builtin_memcpy(x, y + 2, n));
    ASSERT(0, memcmp(x, z, 5));
  }
  {
    char x[5] = {4, 3, 2, 1, 0};
    ASSERT(1, x == __builtin_memcpy(x, x + 1, 4));
    ASSERT(0, memcmp(x, (char[5]){3, 2, 1, 0, 0}, 5));
  }
}

void test_add_overflow(void) {
  {
    int z;
    ASSERT(0, __builtin_add_overflow(2, 3, &z));
    ASSERT(5, z);
  }
  {
    int x, y, z;
    x = 2;
    y = 3;
    ASSERT(0, __builtin_add_overflow(x, y, &z));
    ASSERT(5, z);
  }
  {
    int x, y, z;
    x = 0x7fffffff;
    y = 1;
    ASSERT(1, __builtin_add_overflow(x, y, &z));
    ASSERT(-2147483648, z);
  }
  {
    long x, y, z;
    x = 0x7fffffff;
    y = 1;
    ASSERT(0, __builtin_add_overflow(x, y, &z));
    ASSERT(2147483648, z);
  }
}

void test_sub_overflow(void) {
  {
    int x, y, z;
    x = 2;
    y = 3;
    ASSERT(0, __builtin_sub_overflow(x, y, &z));
    ASSERT(-1, z);
  }
  {
    int x, y, z;
    x = -2147483648;
    y = 1;
    ASSERT(1, __builtin_sub_overflow(x, y, &z));
    ASSERT(2147483647, z);
  }
  {
    long x, y, z;
    x = -2147483648;
    y = 1;
    ASSERT(0, __builtin_sub_overflow(x, y, &z));
    ASSERT(-2147483649, z);
  }
}

void test_mul_overflow(void) {
  {
    int x, y, z;
    x = 2;
    y = 3;
    ASSERT(0, __builtin_mul_overflow(x, y, &z));
    ASSERT(6, z);
  }
  {
    int x, y, z;
    x = 2147483647;
    y = 2;
    ASSERT(1, __builtin_mul_overflow(x, y, &z));
    ASSERT(-2, z);
  }
  {
    long x, y, z;
    x = 2147483647;
    y = 2;
    ASSERT(0, __builtin_mul_overflow(x, y, &z));
    ASSERT(4294967294, z);
  }
}

void test_neg_overflow(void) {
  {
    int x, z;
    x = 2;
    ASSERT(0, __builtin_neg_overflow(x, &z));
    ASSERT(-2, z);
  }
  {
    int x, z;
    x = -2147483648;
    ASSERT(1, __builtin_neg_overflow(x, &z));
    ASSERT(-2147483648, z);
  }
  {
    long x, z;
    x = -2147483648;
    ASSERT(0, __builtin_neg_overflow(x, &z));
    ASSERT(2147483648, z);
  }
}

void test_inf(void) {
  ASSERT(0, __builtin_isinf(0));
  ASSERT(0, __builtin_isinf(1));
  ASSERT(1, __builtin_isinf(__builtin_inff()));
  ASSERT(1, __builtin_isinf(-__builtin_inff()));
  ASSERT(1, __builtin_isinf(__builtin_inf()));
  ASSERT(1, __builtin_isinf(-__builtin_inf()));
  ASSERT(1, __builtin_isinf(__builtin_infl()));
  ASSERT(1, __builtin_isinf(-__builtin_infl()));
  ASSERT(1, __builtin_isfinite(0));
  ASSERT(1, __builtin_isfinite(1));
  ASSERT(0, __builtin_isfinite(__builtin_inff()));
  ASSERT(0, __builtin_isfinite(-__builtin_inff()));
  ASSERT(0, __builtin_isfinite(__builtin_inf()));
  ASSERT(0, __builtin_isfinite(-__builtin_inf()));
  ASSERT(0, __builtin_isfinite(__builtin_infl()));
  ASSERT(0, __builtin_isfinite(-__builtin_infl()));
}

void test_signbit(void) {
  ASSERT(0, !!__builtin_signbitf(0));
  ASSERT(0, !!__builtin_signbitf(0.f));
  ASSERT(0, !!__builtin_signbit(0.));
  ASSERT(0, !!__builtin_signbitl(0.L));
  ASSERT(1, !!__builtin_signbitf(-0.f));
  ASSERT(1, !!__builtin_signbit(-0.));
  ASSERT(1, !!__builtin_signbitl(-0.L));
  ASSERT(0, !!__builtin_signbitf(__builtin_nanf("")));
  ASSERT(1, !!__builtin_signbitf(-__builtin_nanf("")));
  ASSERT(0, !!__builtin_signbit(__builtin_nan("")));
  ASSERT(1, !!__builtin_signbit(-__builtin_nan("")));
  ASSERT(0, !!__builtin_signbitl(__builtin_nanl("")));
  ASSERT(1, !!__builtin_signbitl(-__builtin_nanl("")));
  ASSERT(0, !!__builtin_signbitf(__builtin_inff()));
  ASSERT(1, !!__builtin_signbitf(-__builtin_inff()));
  ASSERT(0, !!__builtin_signbit(__builtin_inf()));
  ASSERT(1, !!__builtin_signbit(-__builtin_inf()));
  ASSERT(0, !!__builtin_signbitl(__builtin_infl()));
  ASSERT(1, !!__builtin_signbitl(-__builtin_infl()));
}

void test_nan(void) {
  ASSERT(0, __builtin_isnan(0));
  ASSERT(0, __builtin_isnan(1));
  ASSERT(1, __builtin_isnan(__builtin_nanf("")));
  ASSERT(1, __builtin_isnan(-__builtin_nanf("")));
  ASSERT(1, __builtin_isnan(__builtin_nan("")));
  ASSERT(1, __builtin_isnan(-__builtin_nan("")));
  ASSERT(1, __builtin_isnan(__builtin_nanl("")));
  ASSERT(1, __builtin_isnan(-__builtin_nanl("")));
  ASSERT(0, __builtin_isunordered(0, 0));
  ASSERT(0, __builtin_isunordered(-1, 1));
  ASSERT(1, __builtin_isunordered(0, __builtin_nanf("")));
  ASSERT(1, __builtin_isunordered(__builtin_nanf(""), 0));
  ASSERT(1, __builtin_isunordered(__builtin_nanf(""), __builtin_nanf("")));
}

void test_double(void) { /* TODO */
  /* ASSERT(1, __DBL_MIN__ < 0.0L); */
  /* ASSERT(1, __DBL_MAX__ > 0.0L); */
}

void test_types_compatible_p(void) {
  ASSERT(1, __builtin_types_compatible_p(int, int));
  ASSERT(1, __builtin_types_compatible_p(double, double));
  ASSERT(0, __builtin_types_compatible_p(int, long));
  ASSERT(0, __builtin_types_compatible_p(long, float));
  ASSERT(1, __builtin_types_compatible_p(int *, int *));
  ASSERT(0, __builtin_types_compatible_p(short *, int *));
  ASSERT(0, __builtin_types_compatible_p(int **, int *));
  ASSERT(1, __builtin_types_compatible_p(const int, int));
  ASSERT(0, __builtin_types_compatible_p(unsigned, int));
  ASSERT(1, __builtin_types_compatible_p(signed, int));
  ASSERT(0, __builtin_types_compatible_p(
                struct { int a; }, struct { int a; }));
  ASSERT(1, __builtin_types_compatible_p(int (*)(void), int (*)(void)));
  ASSERT(1, __builtin_types_compatible_p(void (*)(int), void (*)(int)));
  ASSERT(1, __builtin_types_compatible_p(void (*)(int, double),
                                         void (*)(int, double)));
  ASSERT(1, __builtin_types_compatible_p(int (*)(float, double),
                                         int (*)(float, double)));
  ASSERT(0, __builtin_types_compatible_p(int (*)(float, double), int));
  ASSERT(0,
         __builtin_types_compatible_p(int (*)(float, double), int (*)(float)));
  ASSERT(0, __builtin_types_compatible_p(int (*)(float, double),
                                         int (*)(float, double, int)));
  ASSERT(1, __builtin_types_compatible_p(double (*)(...), double (*)(...)));
  ASSERT(0, __builtin_types_compatible_p(double (*)(...), double (*)(void)));
  ASSERT(1, ({
           typedef struct {
             int a;
           } T;
           __builtin_types_compatible_p(T, T);
         }));
  ASSERT(1, ({
           typedef struct {
             int a;
           } T;
           __builtin_types_compatible_p(T, const T);
         }));
  ASSERT(1, ({
           struct {
             int a;
             int b;
           } x;
           __builtin_types_compatible_p(typeof(x.a), typeof(x.b));
         }));
}

void test_offsetof(void) {
  ASSERT(0, ({
           struct T {
             int a;
             int b;
           };
           __builtin_offsetof(struct T, a);
         }));
  ASSERT(4, ({
           struct T {
             int a;
             int b;
           };
           __builtin_offsetof(struct T, b);
         }));
}

int main() {
  test_constant();
  test_frame_address();
  test_types_compatible_p();
  test_clz();
  test_ctz();
  test_ffs();
  test_bswap();
  test_popcnt();
  test_inf();
  test_nan();
  test_double();
  test_fpclassify();
  test_signbit();
  test_memcpy();
  test_offsetof();
  test_ignored();
  test_add_overflow();
  test_sub_overflow();
  test_mul_overflow();
  test_neg_overflow();
  test_strlen();
  test_strchr();
  test_strpbrk();
  test_strstr();
  test_logb();
  test_fmax();
  return 0;
}
