#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "libgccjit.h"

#include "harness.h"

typedef int v4si __attribute__ ((vector_size (16)));
typedef unsigned int v4ui __attribute__ ((vector_size (16)));
typedef float v4f __attribute__ ((vector_size (16)));

static void
create_vec_fn (gcc_jit_context *ctxt, const char *fnname,
	       gcc_jit_type *vec_type,
	       gcc_jit_type *elem_type,
	       enum gcc_jit_binary_op op)
{
  /* Create equivalent to:

       static void
       FNNAME (V *dst, const V *lhs, E p, E q, E r, E s)
       {
	 V pqrs;
	 pqrs = {p, q, r, s};
	 *dst = *lhs OP pqrs;
       }

     where V is "vec_type" (e.g. v4si)
     and   E is "elem_type" (e.g. int).  */

  gcc_jit_type *ptr_type = gcc_jit_type_get_pointer (vec_type);

  gcc_jit_type *const_type = gcc_jit_type_get_const (vec_type);
  gcc_jit_type *ptr_to_const_type = gcc_jit_type_get_pointer (const_type);

  gcc_jit_param *dst =
    gcc_jit_context_new_param (ctxt, NULL, ptr_type, "dst");
  gcc_jit_param *lhs =
    gcc_jit_context_new_param (ctxt, NULL, ptr_to_const_type, "lhs");
  gcc_jit_param *p =
    gcc_jit_context_new_param (ctxt, NULL, elem_type, "p");
  gcc_jit_param *q =
    gcc_jit_context_new_param (ctxt, NULL, elem_type, "q");
  gcc_jit_param *r =
    gcc_jit_context_new_param (ctxt, NULL, elem_type, "r");
  gcc_jit_param *s =
    gcc_jit_context_new_param (ctxt, NULL, elem_type, "s");

  gcc_jit_type *return_type =
    gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_VOID);

  gcc_jit_param *params[6] = {dst, lhs, p, q, r, s};
  gcc_jit_function *func =
    gcc_jit_context_new_function (ctxt, NULL,
				  GCC_JIT_FUNCTION_EXPORTED,
				  return_type,
				  fnname,
				  6, params, 0);
  gcc_jit_block *initial =
    gcc_jit_function_new_block (func, "initial");

  /* V pqrs; */
  gcc_jit_lvalue *pqrs
    = gcc_jit_function_new_local (func, NULL,
				  vec_type, "pqrs");

  /* pqrs = {p, q, r, s}; */
  gcc_jit_rvalue *elems[4];
  elems[0] = gcc_jit_param_as_rvalue (p);
  elems[1] = gcc_jit_param_as_rvalue (q);
  elems[2] = gcc_jit_param_as_rvalue (r);
  elems[3] = gcc_jit_param_as_rvalue (s);
  gcc_jit_block_add_assignment (
    initial, NULL,
    pqrs,
    gcc_jit_context_new_rvalue_from_vector (ctxt, NULL, vec_type, 4, elems));

  /* (*lhs OP pqrs) */
  gcc_jit_rvalue *op_result =
    gcc_jit_context_new_binary_op (
      ctxt, NULL,
      op,
      vec_type,
      gcc_jit_lvalue_as_rvalue (gcc_jit_rvalue_dereference (gcc_jit_param_as_rvalue (lhs),
							    NULL)),
      gcc_jit_lvalue_as_rvalue (pqrs));
  /* *dst = *lhs OP pqrs; */
  gcc_jit_block_add_assignment (
    initial, NULL,
    gcc_jit_rvalue_dereference (gcc_jit_param_as_rvalue (dst), NULL),
    op_result);
  gcc_jit_block_end_with_void_return (initial, NULL);
}

void
create_code (gcc_jit_context *ctxt, void *user_data)
{
  gcc_jit_type *int_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
  gcc_jit_type *unsigned_type
    = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_UNSIGNED_INT);
  gcc_jit_type *float_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_FLOAT);

  gcc_jit_type *v4si_type = gcc_jit_type_get_vector (int_type, 4);
  gcc_jit_type *v4ui_type = gcc_jit_type_get_vector (unsigned_type, 4);
  gcc_jit_type *v4f_type = gcc_jit_type_get_vector (float_type, 4);

  create_vec_fn (ctxt, "jit_v4si_add",
		 v4si_type, int_type, GCC_JIT_BINARY_OP_PLUS);
  create_vec_fn (ctxt, "jit_v4si_sub",
		 v4si_type, int_type, GCC_JIT_BINARY_OP_MINUS);
  create_vec_fn (ctxt, "jit_v4si_mult",
		 v4si_type, int_type, GCC_JIT_BINARY_OP_MULT);
  create_vec_fn (ctxt, "jit_v4si_div",
		 v4si_type, int_type, GCC_JIT_BINARY_OP_DIVIDE);

  create_vec_fn (ctxt, "jit_v4ui_add",
		 v4ui_type, unsigned_type, GCC_JIT_BINARY_OP_PLUS);
  create_vec_fn (ctxt, "jit_v4ui_sub",
		 v4ui_type, unsigned_type, GCC_JIT_BINARY_OP_MINUS);
  create_vec_fn (ctxt, "jit_v4ui_mult",
		 v4ui_type, unsigned_type, GCC_JIT_BINARY_OP_MULT);
  create_vec_fn (ctxt, "jit_v4ui_div",
		 v4ui_type, unsigned_type, GCC_JIT_BINARY_OP_DIVIDE);

  create_vec_fn (ctxt, "jit_v4f_add",
		 v4f_type, float_type, GCC_JIT_BINARY_OP_PLUS);
  create_vec_fn (ctxt, "jit_v4f_sub",
		 v4f_type, float_type, GCC_JIT_BINARY_OP_MINUS);
  create_vec_fn (ctxt, "jit_v4f_mult",
		 v4f_type, float_type, GCC_JIT_BINARY_OP_MULT);
  create_vec_fn (ctxt, "jit_v4f_div",
		 v4f_type, float_type, GCC_JIT_BINARY_OP_DIVIDE);
}

template <typename V>
void
check_add (const V &a, const V &b, const V &c)
{
  for (int i = 0; i < 4; i++)
    CHECK_VALUE (c[i], a[i] + b[i]);
}

template <typename V>
void
check_sub (const V &a, const V &b, const V &c)
{
  for (int i = 0; i < 4; i++)
    CHECK_VALUE (c[i], a[i] - b[i]);
}

template <typename V>
void
check_mult (const V &a, const V &b, const V &c)
{
  for (int i = 0; i < 4; i++)
    CHECK_VALUE (c[i], a[i] * b[i]);
}

template <typename V>
void
check_div (const V &a, const V &b, const V &c)
{
  for (int i = 0; i < 4; i++)
    CHECK_VALUE (c[i], a[i] / b[i]);
}

template <>
void
check_div<v4f> (const v4f &a, const v4f &b, const v4f &c)
{
  for (int i = 0; i < 4; i++)
    CHECK_DOUBLE_VALUE (c[i], a[i] / b[i]);
}

template <typename V, typename E>
void
verify_vec_code (gcc_jit_context *ctxt, gcc_jit_result *result,
		 const char *fnname,
		 void (*check_cb) (const V &a, const V &b, const V &c))
{
  typedef void (*binop_type) (const V *a, V *b, E p, E q, E r, E s);
  CHECK_NON_NULL (result);
  binop_type fn =
    (binop_type)gcc_jit_result_get_code (result, fnname);
  CHECK_NON_NULL (fn);

  V dst, lhs, pqrs;

  /* Init.  */
  for (int i = 0; i < 4; i++)
    {
      lhs[i] = i + 5;
      pqrs[i] = (i + 4) * 3;
    }

  /* Run jit-compiled code and verify result.  */
  fn (&dst, &lhs, pqrs[0], pqrs[1], pqrs[2], pqrs[3]);
  check_cb (lhs, pqrs, dst);
}

void
verify_code (gcc_jit_context *ctxt, gcc_jit_result *result)
{
  verify_vec_code<v4si, int> (ctxt, result, "jit_v4si_add", check_add);
  verify_vec_code<v4si, int> (ctxt, result, "jit_v4si_sub", check_sub);
  verify_vec_code<v4si, int> (ctxt, result, "jit_v4si_mult", check_mult);
  verify_vec_code<v4si, int> (ctxt, result, "jit_v4si_div", check_div);

  verify_vec_code<v4ui, unsigned int> (ctxt, result, "jit_v4ui_add", check_add);
  verify_vec_code<v4ui, unsigned int> (ctxt, result, "jit_v4ui_sub", check_sub);
  verify_vec_code<v4ui, unsigned int> (ctxt, result, "jit_v4ui_mult", check_mult);
  verify_vec_code<v4ui, unsigned int> (ctxt, result, "jit_v4ui_div", check_div);

  verify_vec_code<v4f, float> (ctxt, result, "jit_v4f_add", check_add);
  verify_vec_code<v4f, float> (ctxt, result, "jit_v4f_sub", check_sub);
  verify_vec_code<v4f, float> (ctxt, result, "jit_v4f_mult", check_mult);
  verify_vec_code<v4f, float> (ctxt, result, "jit_v4f_div", check_div);
}
