/*
 * Copyright 2020 The Emscripten Authors.  All rights reserved.
 * Emscripten is available under two separate licenses, the MIT license and the
 * University of Illinois/NCSA Open Source License.  Both these licenses can be
 * found in the LICENSE file.
 */
#pragma once

#include <stdio.h>
#include <math.h>
#include <time.h>
#include <inttypes.h>
#include <float.h>
#include <assert.h>
#include <string.h>

#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#define align1_int emscripten_align1_int
#define align1_int64 emscripten_align1_int64
#define align1_float emscripten_align1_float
#define align1_double emscripten_align1_double
#else
#define align1_int64 int64_t
#define align1_int int
#define align1_float float
#define align1_double double
#endif

// Recasts floating point representation of f to an integer.
uint32_t fcastu(float f) { return *(uint32_t*)&f; }
uint64_t dcastu(double f) { return *(uint64_t*)&f; }
float ucastf(uint32_t t) { return *(float*)&t; }
double ucastd(uint64_t t) { return *(double*)&t; }

// Data used in test. Store them global and access via a getter to confuse optimizer to not "solve" the whole test suite at compile-time,
// so that the operation will actually be performed at runtime, and not at compile-time. (Testing the capacity of the compiler to perform
// SIMD ops at compile-time would be interesting as well, but that's for another test)
float interesting_floats_[] = { -INFINITY, -FLT_MAX, -2.5f, -1.5f, -1.4f, -1.0f, -0.5f, -0.2f, -FLT_MIN, -0.f, 0.f, 
                                1.401298464e-45f, FLT_MIN, 0.3f, 0.5f, 0.8f, 1.0f, 1.5f, 2.5f, 3.5f, 3.6f, FLT_MAX, INFINITY, NAN,
                                ucastf(0x01020304), ucastf(0x80000000), ucastf(0x7FFFFFFF), ucastf(0xFFFFFFFF)
                            };

double interesting_doubles_[] = { -INFINITY, -FLT_MAX, -2.5, -1.5, -1.4, -1.0, -0.5, -0.2, -FLT_MIN, -0.0, 0.0, 
                                1.401298464e-45, FLT_MIN, 0.3, 0.5, 0.8, 1.0, 1.5, 2.5, 3.5, 3.6, FLT_MAX, INFINITY, NAN,
                                ucastd(0x0102030405060708ULL), ucastd(0x8000000000000000ULL),
                                ucastd(0x7FFFFFFFFFFFFFFFULL), ucastd(0xFFFFFFFFFFFFFFFFULL)
                                };

uint32_t interesting_ints_[] = { 0, 1, 2, 3, 0x01020304, 0x10203040, 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0x12345678, 0x9ABCDEF1, 0x80000000,
                                 0x80808080, 0x7F7F7F7F, 0x01010101, 0x11111111, 0x20202020, 0x0F0F0F0F, 0xF0F0F0F0,
                                 fcastu(-INFINITY), fcastu(-FLT_MAX), fcastu(-2.5f), fcastu(-1.5f), fcastu(-1.4f), fcastu(-1.0f), fcastu(-0.5f),
                                 fcastu(-0.2f), fcastu(-FLT_MIN), 0xF9301AB9, 0x0039AB12, 0x19302BCD,
                                 fcastu(1.401298464e-45f), fcastu(FLT_MIN), fcastu(0.3f), fcastu(0.5f), fcastu(0.8f), fcastu(1.0f), fcastu(1.5f),
                                 fcastu(2.5f), fcastu(3.5f), fcastu(3.6f), fcastu(FLT_MAX), fcastu(INFINITY), fcastu(NAN) };

bool always_true() { return time(NULL) != 0; } // This function always returns true, but the compiler should not know this.

bool IsNan(float f) { return (fcastu(f) << 1) > 0xFF000000u; }

// Replaces all occurrences of 'src' in string 'str' with 'dst', operating in place. strlen(dst) <= strlen(src).
void contract_inplace(char *str, const char *src, const char *dst)
{
  int dstLen = strlen(dst);
  int srcLen = strlen(src);
  int diff = srcLen - dstLen;
  assert(diff >= 0);

  while(true)
  {
    char *pos = strstr(str, src);
    if (!pos) return;
    str = pos;
    strcpy(pos, dst);
    pos += dstLen;
    strcpy(pos, pos + diff);
  }
}

// sprintf standard does not allow controlling how many leading zeros to use
// for printing out the exponent, and different compilers give different
// values. Perform a canonicalization step that enforces the printouts are
// the same.
void CanonicalizeStringComparisons(char *s)
{
  contract_inplace(s, "e+00", "e+");
  contract_inplace(s, "e-00", "e-");
  contract_inplace(s, "e+0", "e+");
  contract_inplace(s, "e-0", "e-");
  contract_inplace(s, "1.#INF", "inf");
}

// Global test state that is used per-test to determine whether to validate the state of exact NaN bits
// in specific functions.
extern bool testNaNBits;

char *SerializeFloat(float f, char *dstStr, bool approximate = false) {
  if (IsNan(f)) {
    uint32_t u = fcastu(f);
    int numChars = testNaNBits ? sprintf(dstStr, "NaN(0x%8X)", (unsigned int)u) : sprintf(dstStr, "NaN");
    return dstStr + numChars;
  } else {
    if (approximate > 0) {
      if (fabs(f) < FLT_MIN) // Flush denormals to zero (for _mm_rcp_ps)
        sprintf(dstStr, "%f", copysign(0.f, f));
      else if (fabs(f) >= 2.6e22f) // Flush large numbers to infinity (for _mm_rsqrt_ps)
        sprintf(dstStr, "%f", copysign(INFINITY, f));
      else
        sprintf(dstStr, "%.2g", f);
    } else {
      sprintf(dstStr, "%.9g", f);
    }
    CanonicalizeStringComparisons(dstStr);
    return dstStr + strlen(dstStr);
  }
}

char *SerializeDouble(double f, char *dstStr) {
  if (IsNan(f)) {
    uint64_t u = dcastu(f);
    int numChars = testNaNBits ? sprintf(dstStr, "NaN(0x%08X%08X)", (unsigned int)(u>>32), (unsigned int)u) : sprintf(dstStr, "NaN");
    return dstStr + numChars;
  } else {
    sprintf(dstStr, "%.17g", f);
    CanonicalizeStringComparisons(dstStr);
    return dstStr + strlen(dstStr);
  }
}

void tostr(__m128 *m, char *outstr) {
  union { __m128 m; float val[4]; } u;
  u.m = *m;
  char s[4][32];
  SerializeFloat(u.val[0], s[0]);
  SerializeFloat(u.val[1], s[1]);
  SerializeFloat(u.val[2], s[2]);
  SerializeFloat(u.val[3], s[3]);
  sprintf(outstr, "[%s,%s,%s,%s]", s[3], s[2], s[1], s[0]);
}

void tostr_approx(__m128 *m, char *outstr, bool approximate) {
  union { __m128 m; float val[4]; } u;
  u.m = *m;
  char s[4][32];
  SerializeFloat(u.val[0], s[0], approximate);
  SerializeFloat(u.val[1], s[1], approximate);
  SerializeFloat(u.val[2], s[2], approximate);
  SerializeFloat(u.val[3], s[3], approximate);
  sprintf(outstr, "[%s,%s,%s,%s]", s[3], s[2], s[1], s[0]);
}

void tostr(__m128i *m, char *outstr) {
  union { __m128i m; uint32_t val[4]; } u;
  u.m = *m;
  sprintf(outstr, "[0x%08X,0x%08X,0x%08X,0x%08X]", u.val[3], u.val[2], u.val[1], u.val[0]);
}

#ifdef __SSE2__

void tostr(__m128d *m, char *outstr) {
  union { __m128d m; double val[2]; } u;
  u.m = *m;
  char s[2][64];
  SerializeDouble(u.val[0], s[0]);
  SerializeDouble(u.val[1], s[1]);
  sprintf(outstr, "[%s,%s]", s[1], s[0]);
}

__m128i ExtractInRandomOrder(uint32_t *arr, int i, int n, int prime) {
  return _mm_set_epi32(arr[(i*prime)%n], arr[((i+1)*prime)%n], arr[((i+2)*prime)%n], arr[((i+3)*prime)%n]);
}

__m128d ExtractInRandomOrder(double *arr, int i, int n, int prime) {
  return _mm_set_pd(arr[(i*prime)%n], arr[((i+1)*prime)%n]);
}
#endif

void tostr(align1_int *m, char *outstr) {
  sprintf(outstr, "0x%08X", *m);
}

void tostr(align1_int64 *m, char *outstr) {
  sprintf(outstr, "0x%08X%08X", (int)(*m >> 32), (int)*m);
}

void tostr(align1_float *m, char *outstr) {
  SerializeFloat(*m, outstr);
}

void tostr(align1_double *m, char *outstr) {
  SerializeDouble(*m, outstr);
}

void tostr(align1_double *m, int numElems, char *outstr) {
  char s[2][64];
  for(int i = 0; i < numElems; ++i)
    SerializeDouble(m[i], s[i]);
  switch(numElems) {
    case 1: sprintf(outstr, "{%s}", s[0]); break;
    case 2: sprintf(outstr, "{%s,%s}", s[0], s[1]); break;
  }
}

void tostr(align1_float *m, int numElems, char *outstr) {
  char s[4][64];
  for(int i = 0; i < numElems; ++i)
    SerializeFloat(m[i], s[i]);
  switch(numElems) {
    case 1: sprintf(outstr, "{%s}", s[0]); break;
    case 2: sprintf(outstr, "{%s,%s}", s[0], s[1]); break;
    case 3: sprintf(outstr, "{%s,%s,%s}", s[0], s[1], s[2]); break;
    case 4: sprintf(outstr, "{%s,%s,%s,%s}", s[0], s[1], s[2], s[3]); break;
  }
}

void tostr(align1_int *s, int numElems, char *outstr) {
  switch(numElems) {
    case 1: sprintf(outstr, "{0x%08X}", s[0]); break;
    case 2: sprintf(outstr, "{0x%08X,0x%08X}", s[0], s[1]); break;
    case 3: sprintf(outstr, "{0x%08X,0x%08X,0x%08X}", s[0], s[1], s[2]); break;
    case 4: sprintf(outstr, "{0x%08X,0x%08X,0x%08X,0x%08X}", s[0], s[1], s[2], s[3]); break;
  }
}

void tostr(align1_int64 *m, int numElems, char *outstr) {
  switch(numElems) {
    case 1: sprintf(outstr, "{0x%08X%08X}", (int)(*m >> 32), (int)*m); break;
    case 2: sprintf(outstr, "{0x%08X%08X,0x%08X%08X}", (int)(*m >> 32), (int)*m, (int)(m[1] >> 32), (int)m[1]);
  }
}

// Accessors to the test data in a way that the compiler can't optimize at compile-time.
__attribute__((noinline)) float *get_interesting_floats() {
  return always_true() ? interesting_floats_ : 0;
}

__attribute__((noinline)) uint32_t *get_interesting_ints() {
  return always_true() ? interesting_ints_ : 0;
}

__attribute__((noinline)) double *get_interesting_doubles() {
  return always_true() ? interesting_doubles_ : 0;
}

__m128 ExtractFloatInRandomOrder(float *arr, int i, int n, int prime) {
  return _mm_set_ps(arr[(i*prime)%n], arr[((i+1)*prime)%n], arr[((i+2)*prime)%n], arr[((i+3)*prime)%n]);
}

#ifdef __SSE2__
__m128d ExtractDoubleInRandomOrder(double *arr, int i, int n, int prime) {
  return _mm_set_pd(arr[(i*prime)%n], arr[((i+1)*prime)%n]);
}
#endif

__m128 ExtractIntInRandomOrder(unsigned int *arr, int i, int n, int prime) {
  return _mm_set_ps(*(float*)&arr[(i*prime)%n], *(float*)&arr[((i+1)*prime)%n], *(float*)&arr[((i+2)*prime)%n], *(float*)&arr[((i+3)*prime)%n]);
}

#define E1(arr, i, n) ExtractFloatInRandomOrder(arr, i, n, 1)
#define E2(arr, i, n) ExtractFloatInRandomOrder(arr, i, n, 1787)

#define E1_Double(arr, i, n) ExtractDoubleInRandomOrder(arr, i, n, 1)
#define E2_Double(arr, i, n) ExtractDoubleInRandomOrder(arr, i, n, 1787)

#define E1_Int(arr, i, n) ExtractIntInRandomOrder(arr, i, n, 1)
#define E2_Int(arr, i, n) ExtractIntInRandomOrder(arr, i, n, 1787)

#define M128i_M128i_M128i(func) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingInts / 4; ++j) \
      { \
        __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
        __m128i m2 = (__m128i)E2_Int(interesting_ints, j*4, numInterestingInts); \
        __m128i ret = func(m1, m2); \
        /* a op b */ \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
        /* b op a */ \
        ret = func(m2, m1); \
        tostr(&m1, str); \
        tostr(&m2, str2); \
        tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128_Tint_body(Ret_type, func, Tint) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
    { \
      __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
      Ret_type ret = func(m1, Tint); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s, %d) = %s\n", #func, str, Tint, str2); \
    }

#define Ret_M128d_Tint_body(Ret_type, func, Tint) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
    { \
      __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
      Ret_type ret = func(m1, Tint); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s, %d) = %s\n", #func, str, Tint, str2); \
    }

#define Ret_M128i_Tint_body(Ret_type, func, Tint) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
    { \
      __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
      Ret_type ret = func(m1, Tint); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s, %d) = %s\n", #func, str, Tint, str2); \
    }

#define Ret_M128i_int_Tint_body(Ret_type, func, Tint) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int j = 0; j < numInterestingInts; ++j) \
      for(int k = 0; k < 4; ++k) \
      { \
        __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
        Ret_type ret = func(m1, interesting_ints[j], Tint); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&ret, str2); \
        printf("%s(%s, 0x%08X, %d) = %s\n", #func, str, interesting_ints[j], Tint, str2); \
      }

#define Ret_M128d_M128d_Tint_body(Ret_type, func, Tint) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
      for(int j = 0; j < numInterestingDoubles / 2; ++j) \
      { \
        __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
        __m128d m2 = E2_Double(interesting_doubles, j*2, numInterestingDoubles); \
        Ret_type ret = func(m1, m2, Tint); \
        /* a op b */ \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \
        /* b op a */ \
        ret = func(m2, m1, Tint); \
        tostr(&m1, str); \
        tostr(&m2, str2); \
        tostr(&ret, str3); \
        printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \
      }

#define Ret_M128i_M128i_Tint_body(Ret_type, func, Tint) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingInts / 4; ++j) \
      { \
        __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
        __m128i m2 = (__m128i)E2_Int(interesting_ints, j*4, numInterestingInts); \
        Ret_type ret = func(m1, m2, Tint); \
        /* a op b */ \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \
        /* b op a */ \
        ret = func(m2, m1, Tint); \
        tostr(&m1, str); \
        tostr(&m2, str2); \
        tostr(&ret, str3); \
        printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \
      }

#define Ret_M128_M128_Tint_body(Ret_type, func, Tint) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingFloats / 4; ++j) \
      { \
        __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
        __m128 m2 = E2(interesting_floats, j*4, numInterestingFloats); \
        Ret_type ret = func(m1, m2, Tint); \
        /* a op b */ \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \
        /* b op a */ \
        ret = func(m2, m1, Tint); \
        tostr(&m1, str); \
        tostr(&m2, str2); \
        tostr(&ret, str3); \
        printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \
      }

#define const_int8_unroll(Ret_type, F, func) \
  F(Ret_type, func, 0); \
  F(Ret_type, func, 1); \
  F(Ret_type, func, 2); \
  F(Ret_type, func, 3); \
  F(Ret_type, func, 5); \
  F(Ret_type, func, 7); \
  F(Ret_type, func, 11); \
  F(Ret_type, func, 13); \
  F(Ret_type, func, 15); \
  F(Ret_type, func, 16); \
  F(Ret_type, func, 17); \
  F(Ret_type, func, 23); \
  F(Ret_type, func, 29); \
  F(Ret_type, func, 31); \
  F(Ret_type, func, 37); \
  F(Ret_type, func, 43); \
  F(Ret_type, func, 47); \
  F(Ret_type, func, 59); \
  F(Ret_type, func, 127); \
  F(Ret_type, func, 128); \
  F(Ret_type, func, 191); \
  F(Ret_type, func, 254); \
  F(Ret_type, func, 255);

#define const_int5_full_unroll(Ret_type, F, func) \
  F(Ret_type, func, 0); \
  F(Ret_type, func, 1); \
  F(Ret_type, func, 2); \
  F(Ret_type, func, 3); \
  F(Ret_type, func, 4); \
  F(Ret_type, func, 5); \
  F(Ret_type, func, 6); \
  F(Ret_type, func, 7); \
  F(Ret_type, func, 8); \
  F(Ret_type, func, 9); \
  F(Ret_type, func, 10); \
  F(Ret_type, func, 11); \
  F(Ret_type, func, 12); \
  F(Ret_type, func, 13); \
  F(Ret_type, func, 14); \
  F(Ret_type, func, 15); \
  F(Ret_type, func, 16); \
  F(Ret_type, func, 17); \
  F(Ret_type, func, 18); \
  F(Ret_type, func, 19); \
  F(Ret_type, func, 20); \
  F(Ret_type, func, 21); \
  F(Ret_type, func, 22); \
  F(Ret_type, func, 23); \
  F(Ret_type, func, 24); \
  F(Ret_type, func, 25); \
  F(Ret_type, func, 26); \
  F(Ret_type, func, 27); \
  F(Ret_type, func, 28); \
  F(Ret_type, func, 29); \
  F(Ret_type, func, 30); \
  F(Ret_type, func, 31);

#define Ret_M128_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128_Tint_body, func)
#define Ret_M128d_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128d_Tint_body, func)
#define Ret_M128i_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128i_Tint_body, func)
#define Ret_M128i_int_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128i_int_Tint_body, func)
#define Ret_M128i_M128i_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128i_M128i_Tint_body, func)
#define Ret_M128d_M128d_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128d_M128d_Tint_body, func)
#define Ret_M128d_M128d_Tint_5bits(Ret_type, func) const_int5_full_unroll(Ret_type, Ret_M128d_M128d_Tint_body, func)
#define Ret_M128_M128_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128_M128_Tint_body, func)
#define Ret_M128_M128_Tint_5bits(Ret_type, func) const_int5_full_unroll(Ret_type, Ret_M128_M128_Tint_body, func)

#define Ret_M128d_M128d(Ret_type, func) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
      for(int j = 0; j < numInterestingDoubles / 2; ++j) \
      { \
        __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
        __m128d m2 = E2_Double(interesting_doubles, j*2, numInterestingDoubles); \
        Ret_type ret = func(m1, m2); \
        /* a op b */ \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
        /* b op a */ \
        ret = func(m2, m1); \
        tostr(&m1, str); \
        tostr(&m2, str2); \
        tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128d_M128d_M128d(Ret_type, func) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
      for(int j = 0; j < numInterestingDoubles / 2; ++j) \
        for(int l = 0; l < numInterestingDoubles / 2; ++l) \
        { \
          __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
          __m128d m2 = E2_Double(interesting_doubles, j*2, numInterestingDoubles); \
          __m128d m3 = E1_Double(interesting_doubles, l*2, numInterestingDoubles); \
          Ret_type ret = func(m1, m2, m3); \
          /* a, b, c */ \
          char str[256]; tostr(&m1, str); \
          char str2[256]; tostr(&m2, str2); \
          char str3[256]; tostr(&m3, str3); \
          char str4[256]; tostr(&ret, str4); \
          printf("%s(%s, %s, %s) = %s\n", #func, str, str2, str3, str4); \
          /* b, c, a */ \
          ret = func(m2, m3, m1); \
          tostr(&m1, str); \
          tostr(&m2, str2); \
          tostr(&m3, str3); \
          tostr(&ret, str4); \
          printf("%s(%s, %s, %s) = %s\n", #func, str, str2, str3, str4); \
          /* c, a, b */ \
          ret = func(m3, m1, m2); \
          tostr(&m1, str); \
          tostr(&m2, str2); \
          tostr(&m3, str3); \
          tostr(&ret, str4); \
          printf("%s(%s, %s, %s) = %s\n", #func, str, str2, str3, str4); \
        }

#define Ret_M128d_M128(Ret_type, func) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
      for(int j = 0; j < numInterestingDoubles / 2; ++j) \
      { \
        __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
        __m128 m2 = E2(interesting_floats, i*4+k, numInterestingFloats); \
        Ret_type ret = func(m1, m2); \
        /* a op b */ \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128d_int(Ret_type, func) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
      for(int j = 0; j < numInterestingInts; ++j) \
      { \
        __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
        int m2 = interesting_ints[j]; \
        Ret_type ret = func(m1, m2); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128d_int64(Ret_type, func) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
      for(int j = 0; j < numInterestingInts; ++j) \
        for(int l = 0; l < numInterestingInts; ++l) \
        { \
          __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
          int64_t m2 = (int64_t)(((uint64_t)interesting_ints[j]) << 32 | (uint64_t)interesting_ints[l]); \
          Ret_type ret = func(m1, m2); \
          char str[256]; tostr(&m1, str); \
          char str2[256]; tostr(&m2, str2); \
          char str3[256]; tostr(&ret, str3); \
          printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
        }

#define Ret_M128d(Ret_type, func) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
    { \
      __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
      Ret_type ret = func(m1); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s) = %s\n", #func, str, str2); \
    }

#define Ret_DoublePtr(Ret_type, func, numElemsAccessed, inc) \
  for(int i = 0; i+numElemsAccessed <= numInterestingDoubles; i += inc) \
  { \
    double *ptr = interesting_doubles + i; \
    Ret_type ret = func(ptr); \
    char str[256]; tostr(ptr, numElemsAccessed, str); \
    char str2[256]; tostr(&ret, str2); \
    printf("%s(%s) = %s\n", #func, str, str2); \
  }

#define Ret_DoublePtr_M128i(Ret_type, func, numElemsAccessed, inc) \
  for(int i = 0; i+numElemsAccessed <= numInterestingDoubles; i += inc) \
    for(int j = 0; j < numInterestingInts / 4; ++j) \
    { \
      double *ptr = interesting_doubles + i; \
      __m128i m1 = (__m128i)E2_Int(interesting_ints, j*4, numInterestingInts); \
      Ret_type ret = func(ptr, m1); \
      char str[256]; tostr(ptr, numElemsAccessed, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s) = %s\n", #func, str, str2); \
    }

float tempOutFloatStore[16];
float *getTempOutFloatStore(int alignmentBytes) {
  memset(tempOutFloatStore, 0, sizeof(tempOutFloatStore));
  uintptr_t addr = (uintptr_t)tempOutFloatStore;
  addr = (addr + alignmentBytes - 1) & ~(alignmentBytes-1);
  return (float*)addr;
}

int *getTempOutIntStore(int alignmentBytes) { return (int*)getTempOutFloatStore(alignmentBytes); }
double *getTempOutDoubleStore(int alignmentBytes) { return (double*)getTempOutFloatStore(alignmentBytes); }

#define void_OutFloatPtr_M128(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
      for(int k = 0; k < 4; ++k) \
      { \
        uintptr_t base = (uintptr_t)getTempOutFloatStore(16); \
        __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
        align1_float *out = (align1_float*)(base + offset); \
        func((Ptr_type)out, m1); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(out, numBytesWritten/sizeof(float), str2); \
        printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \
      }

#define void_OutFloatPtr_M128i_M128(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int j = 0; j < numInterestingInts / 4; ++j) \
      for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
        for(int k = 0; k < 4; ++k) \
        { \
          uintptr_t base = (uintptr_t)getTempOutFloatStore(16); \
          __m128i m1 = (__m128i)E1_Int(interesting_ints, j*4, numInterestingInts); \
          __m128 m2 = E1(interesting_floats, i*4+k, numInterestingFloats); \
          align1_float *out = (align1_float*)(base + offset); \
          func((Ptr_type)out, m1, m2); \
          char str[256]; tostr(&m1, str); \
          char str2[256]; tostr(&m2, str2); \
          char str3[256]; tostr(out, numBytesWritten/sizeof(float), str3); \
          printf("%s(p:align=%d, %s, %s) = %s\n", #func, offset, str, str2, str3); \
        }

#define void_OutDoublePtr_M128d(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
      for(int k = 0; k < 2; ++k) \
      { \
        uintptr_t base = (uintptr_t)getTempOutDoubleStore(16); \
        __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
        align1_double *out = (align1_double*)(base + offset); \
        func((Ptr_type)out, m1); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(out, numBytesWritten/sizeof(double), str2); \
        printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \
      }

#define void_OutDoublePtr_M128i_M128d(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int j = 0; j < numInterestingInts / 4; ++j) \
      for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
        for(int k = 0; k < 2; ++k) \
        { \
          uintptr_t base = (uintptr_t)getTempOutDoubleStore(16); \
          __m128i m1 = (__m128i)E1_Int(interesting_ints, j*4, numInterestingInts); \
          __m128d m2 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
          align1_double *out = (align1_double*)(base + offset); \
          func((Ptr_type)out, m1, m2); \
          char str[256]; tostr(&m1, str); \
          char str2[256]; tostr(&m2, str2); \
          char str3[256]; tostr(out, numBytesWritten/sizeof(double), str3); \
          printf("%s(p:align=%d, %s, %s) = %s\n", #func, offset, str, str2, str3); \
        }

#define void_OutIntPtr_M128i(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
      for(int k = 0; k < 4; ++k) \
      { \
        uintptr_t base = (uintptr_t)getTempOutIntStore(16); \
        __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
        align1_int *out = (align1_int*)(base + offset); \
        func((Ptr_type)out, m1); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(out, (numBytesWritten+sizeof(int)-1)/sizeof(int), str2); \
        printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \
      }

#define void_OutIntPtr_int(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingInts; ++i) \
    for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
      for(int k = 0; k < 4; ++k) \
      { \
        uintptr_t base = (uintptr_t)getTempOutIntStore(16); \
        int m1 = interesting_ints[i]; \
        align1_int *out = (align1_int*)(base + offset); \
        func((Ptr_type)out, m1); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(out, numBytesWritten/sizeof(int), str2); \
        printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \
      }

#define void_OutIntPtr_int64(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingInts; ++i) \
    for(int j = 0; j < numInterestingInts; ++j) \
      for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
      { \
        uintptr_t base = (uintptr_t)getTempOutIntStore(16); \
        int64_t m1 = (int64_t)(((uint64_t)interesting_ints[i]) << 32 | (uint64_t)interesting_ints[j]); \
        align1_int64 *out = (align1_int64*)(base + offset); \
        func((Ptr_type)out, m1); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(out, numBytesWritten/sizeof(int64_t), str2); \
        printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \
      }

#define void_M128i_M128i_OutIntPtr(func, Ptr_type, numBytesWritten, alignmentBytes) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int j = 0; j < numInterestingInts / 4; ++j) \
      for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \
        for(int k = 0; k < 4; ++k) \
        { \
          uintptr_t base = (uintptr_t)getTempOutIntStore(16); \
          __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
          __m128i m2 = (__m128i)E2_Int(interesting_ints, j*4, numInterestingInts); \
          align1_int *out = (int*)(base + offset); \
          func(m1, m2, (Ptr_type)out); \
          char str[256]; tostr(&m1, str); \
          char str2[256]; tostr(&m2, str2); \
          char str3[256]; tostr(out, numBytesWritten/sizeof(int), str3); \
          printf("%s(%s, %s, p:align=%d) = %s\n", #func, str, str2, offset, str3); \
        }

#define Ret_M128(Ret_type, func) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
    { \
      __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
      Ret_type ret = func(m1); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s) = %s\n", #func, str, str2); \
    }

#define Ret_M128approx(Ret_type, func) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
    { \
      __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
      Ret_type ret = func(m1); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr_approx(&ret, str2, true/*approximate*/); \
      printf("%s(%s) = %s\n", #func, str, str2); \
    }

#define Ret_FloatPtr(Ret_type, func, numElemsAccessed, inc) \
  for(int i = 0; i+numElemsAccessed <= numInterestingFloats; i += inc) \
  { \
    float *ptr = interesting_floats + i; \
    Ret_type ret = func(ptr); \
    char str[256]; tostr(ptr, numElemsAccessed, str); \
    char str2[256]; tostr(&ret, str2); \
    printf("%s(%s) = %s\n", #func, str, str2); \
  }

#define Ret_FloatPtr_M128i(Ret_type, func, numElemsAccessed, inc) \
  for(int i = 0; i+numElemsAccessed <= numInterestingFloats; i += inc) \
    for(int j = 0; j < numInterestingInts / 4; ++j) \
    { \
      float *ptr = interesting_floats + i; \
      __m128i m1 = (__m128i)E1_Int(interesting_ints, j*4, numInterestingInts); \
      Ret_type ret = func(ptr, m1); \
      char str[256]; tostr(ptr, numElemsAccessed, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s) = %s\n", #func, str, str2); \
    }

#define Ret_Float4(Ret_type, func, inc) \
  for(int i = 0; i+4 <= numInterestingFloats; i += inc) \
  { \
    float *ptr = interesting_floats + i; \
    Ret_type ret = func(ptr[0], ptr[1], ptr[2], ptr[3]); \
    char str[256]; tostr(ptr, 4, str); \
    char str2[256]; tostr(&ret, str2); \
    printf("%s(%s) = %s\n", #func, str, str2); \
  }

#define Ret_Float(Ret_type, func, inc) \
  for(int i = 0; i+1 <= numInterestingFloats; i += inc) \
  { \
    float *ptr = interesting_floats + i; \
    Ret_type ret = func(*ptr); \
    char str[256]; tostr(ptr, 1, str); \
    char str2[256]; tostr(&ret, str2); \
    printf("%s(%s) = %s\n", #func, str, str2); \
  }

#define Ret_IntPtr(Ret_type, func, Ptr_type, numElemsAccessed, inc) \
  for(int i = 0; i+numElemsAccessed <= numInterestingInts; i += inc) \
  { \
    uint32_t *ptr = interesting_ints + i; \
    Ret_type ret = func((Ptr_type)ptr); \
    char str[256]; tostr((int*)ptr, numElemsAccessed, str); \
    char str2[256]; tostr(&ret, str2); \
    printf("%s(%s) = %s\n", #func, str, str2); \
  }

#define Ret_M128_FloatPtr(Ret_type, func, Ptr_type, numElemsAccessed, inc) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j+numElemsAccessed <= numInterestingFloats; j += inc) \
      { \
        __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
        float *ptr = interesting_floats + j; \
        Ret_type ret = func(m1, (Ptr_type)ptr); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(ptr, numElemsAccessed, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128d_DoublePtr(Ret_type, func, Ptr_type, numElemsAccessed, inc) \
  for(int i = 0; i < numInterestingDoubles / 2; ++i) \
    for(int k = 0; k < 2; ++k) \
      for(int j = 0; j+numElemsAccessed <= numInterestingDoubles; j += inc) \
      { \
        __m128d m1 = E1_Double(interesting_doubles, i*2+k, numInterestingDoubles); \
        double *ptr = interesting_doubles + j; \
        Ret_type ret = func(m1, (Ptr_type)ptr); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(ptr, numElemsAccessed, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128i(Ret_type, func) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
    { \
      __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
      Ret_type ret = func(m1); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s) = %s\n", #func, str, str2); \
    }

#define Ret_M128i_M128i(Ret_type, func) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingInts / 4; ++j) \
      { \
        __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
        __m128i m2 = (__m128i)E2_Int(interesting_ints, j*4, numInterestingInts); \
        Ret_type ret = func(m1, m2); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128i_M128i_M128i(Ret_type, func) \
  for(int i = 0; i < numInterestingInts / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingInts / 4; ++j) \
        for(int l = 0; l < numInterestingInts / 4; ++l) \
        { \
          __m128i m1 = (__m128i)E1_Int(interesting_ints, i*4+k, numInterestingInts); \
          __m128i m2 = (__m128i)E2_Int(interesting_ints, j*4, numInterestingInts); \
          __m128i m3 = (__m128i)E1_Int(interesting_ints, l*4, numInterestingInts); \
          Ret_type ret = func(m1, m2, m3); \
          char str[256]; tostr(&m1, str); \
          char str2[256]; tostr(&m2, str2); \
          char str3[256]; tostr(&m3, str3); \
          char str4[256]; tostr(&ret, str4); \
          printf("%s(%s, %s, %s) = %s\n", #func, str, str2, str3, str4); \
        }

#define Ret_int(Ret_type, func) \
  for(int i = 0; i < numInterestingInts; ++i) \
  { \
    Ret_type ret = func(interesting_ints[i]); \
    char str[256]; tostr((int*)&interesting_ints[i], str); \
    char str2[256]; tostr(&ret, str2); \
    printf("%s(%s) = %s\n", #func, str, str2); \
  }

#define Ret_int64(Ret_type, func) \
  for(int i = 0; i < numInterestingInts; ++i) \
    for(int j = 0; j < numInterestingInts; ++j) \
    { \
      int64_t m1 = (int64_t)(((uint64_t)interesting_ints[i]) << 32 | (uint64_t)interesting_ints[j]); \
      Ret_type ret = func(m1); \
      char str[256]; tostr(&m1, str); \
      char str2[256]; tostr(&ret, str2); \
      printf("%s(%s) = %s\n", #func, str, str2); \
    }

#define Ret_M128_M128(Ret_type, func) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingFloats / 4; ++j) \
      { \
        __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
        __m128 m2 = E2(interesting_floats, j*4, numInterestingFloats); \
        Ret_type ret = func(m1, m2); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }

#define Ret_M128_M128_M128(Ret_type, func) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingFloats / 4; ++j) \
        for(int l = 0; l < numInterestingFloats / 4; ++l) \
        { \
          __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
          __m128 m2 = E2(interesting_floats, j*4, numInterestingFloats); \
          __m128 m3 = E1(interesting_floats, l*4, numInterestingFloats); \
          Ret_type ret = func(m1, m2, m3); \
          char str[256]; tostr(&m1, str); \
          char str2[256]; tostr(&m2, str2); \
          char str3[256]; tostr(&m3, str3); \
          char str4[256]; tostr(&ret, str4); \
          printf("%s(%s, %s, %s) = %s\n", #func, str, str2, str3, str4); \
        }

#define Ret_M128_int(Ret_type, func) \
  for(int i = 0; i < numInterestingFloats / 4; ++i) \
    for(int k = 0; k < 4; ++k) \
      for(int j = 0; j < numInterestingInts; ++j) \
      { \
        __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \
        int m2 = interesting_ints[j]; \
        Ret_type ret = func(m1, m2); \
        char str[256]; tostr(&m1, str); \
        char str2[256]; tostr(&m2, str2); \
        char str3[256]; tostr(&ret, str3); \
        printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \
      }
