/*
 * Simple sanity test of memcpy, memmove, and memset intrinsics.
 * (fixed length buffers, variable length buffers, etc.)
 */

#include <stdint.h> /* cstdint requires -std=c++0x or higher */
#include <cstdlib>
#include <cstring>

#include "mem_intrin.h"
#include "xdefs.h"

typedef int elem_t;

/*
 * Reset buf to the sequence of bytes: n, n+1, n+2 ... length - 1
 */
static void __attribute__((noinline))
reset_buf(uint8_t *buf, uint8_t init, SizeT length) {
  SizeT i;
  SizeT v = init;
  for (i = 0; i < length; ++i)
    buf[i] = v++;
}

/* Do a fletcher-16 checksum so that the order of the values matter.
 * (Not doing a fletcher-32 checksum, since we are working with
 * smaller buffers, whose total won't approach 2**16).
 */
static int __attribute__((noinline))
fletcher_checksum(uint8_t *buf, SizeT length) {
  SizeT i;
  int sum = 0;
  int sum_of_sums = 0;
  const int kModulus = 255;
  for (i = 0; i < length; ++i) {
    sum = (sum + buf[i]) % kModulus;
    sum_of_sums = (sum_of_sums + sum) % kModulus;
  }
  return (sum_of_sums << 8) | sum;
}

int memcpy_test(uint8_t *buf, uint8_t *buf2, uint8_t init, SizeT length) {
  reset_buf(buf, init, length);
  memcpy((void *)buf2, (void *)buf, length);
  return fletcher_checksum(buf2, length);
}

int memmove_test(uint8_t *buf, uint8_t *buf2, uint8_t init, SizeT length) {
  int sum1;
  int sum2;
  const int overlap_bytes = 4 * sizeof(elem_t);
  if (length <= overlap_bytes)
    return 0;
  uint8_t *overlap_buf = buf + overlap_bytes;
  SizeT reduced_length = length - overlap_bytes;
  reset_buf(buf, init, length);

  /* Test w/ overlap. */
  memmove((void *)overlap_buf, (void *)buf, reduced_length);
  sum1 = fletcher_checksum(overlap_buf, reduced_length);
  /* Test w/out overlap. */
  memmove((void *)buf2, (void *)buf, length);
  sum2 = fletcher_checksum(buf2, length);
  return sum1 + sum2;
}

int memset_test(uint8_t *buf, uint8_t *buf2, uint8_t init, SizeT length) {
  memset((void *)buf, init, length);
  memset((void *)buf2, init + 4, length);
  return fletcher_checksum(buf, length) + fletcher_checksum(buf2, length);
}

#define X(NBYTES)                                                              \
  int memcpy_test_fixed_len_##NBYTES(uint8_t init) {                           \
    uint8_t buf[NBYTES];                                                       \
    uint8_t buf2[NBYTES];                                                      \
    reset_buf(buf, init, NBYTES);                                              \
    memcpy((void *)buf2, (void *)buf, NBYTES);                                 \
    return fletcher_checksum(buf2, NBYTES);                                    \
  }                                                                            \
                                                                               \
  int memmove_test_fixed_len_##NBYTES(uint8_t init) {                          \
    uint8_t buf[NBYTES + 16];                                                  \
    uint8_t buf2[NBYTES + 16];                                                 \
    reset_buf(buf, init, NBYTES + 16);                                         \
    reset_buf(buf2, init, NBYTES + 16);                                        \
    /* Move up */                                                              \
    memmove((void *)(buf + 16), (void *)buf, NBYTES);                          \
    /* Move down */                                                            \
    memmove((void *)buf2, (void *)(buf2 + 16), NBYTES);                        \
    return fletcher_checksum(buf, NBYTES + 16) +                               \
           fletcher_checksum(buf2, NBYTES + 16);                               \
  }                                                                            \
                                                                               \
  int memset_test_fixed_len_##NBYTES(uint8_t init) {                           \
    uint8_t buf[NBYTES];                                                       \
    memset((void *)buf, init, NBYTES);                                         \
    return fletcher_checksum(buf, NBYTES);                                     \
  }
MEMINTRIN_SIZE_TABLE
#undef X
