#include <stdio.h>
#include "libgccjit.h"
#include "harness.h"

/* This testcase checks that gcc_jit_context_new_constructor() works
   with locals. Tests that constructors can be used as return
   values or function call values. Test that constructors can have side
   effects and be assigned to locals.
 */

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 *pint_type = gcc_jit_type_get_pointer (int_type);
  gcc_jit_type *double_type = gcc_jit_context_get_type (ctxt,
    GCC_JIT_TYPE_DOUBLE);
  gcc_jit_type *float_type = gcc_jit_context_get_type (ctxt,
    GCC_JIT_TYPE_FLOAT);
  gcc_jit_type *bool_type = gcc_jit_context_get_type (ctxt,
    GCC_JIT_TYPE_BOOL);
  gcc_jit_type *char_type = gcc_jit_context_get_type (ctxt,
    GCC_JIT_TYPE_CHAR);
  gcc_jit_type *size_type = gcc_jit_context_get_type (ctxt,
    GCC_JIT_TYPE_SIZE_T);
  gcc_jit_type *voidptr_type = gcc_jit_context_get_type (ctxt,
    GCC_JIT_TYPE_VOID_PTR);
  gcc_jit_type *void_type = gcc_jit_context_get_type (ctxt,
    GCC_JIT_TYPE_VOID);

  /* Make a struct: struct fi { float f; int i;} */
  gcc_jit_field *fi_f = gcc_jit_context_new_field (ctxt,
						 0,
						 float_type,
						 "f");
  gcc_jit_field *fi_i = gcc_jit_context_new_field (ctxt,
						 0,
						 int_type,
						 "i");
  gcc_jit_field *fields[] = {fi_f, fi_i};

  gcc_jit_type *struct_fi_type =
    gcc_jit_struct_as_type (
      gcc_jit_context_new_struct_type (ctxt,
				       0,
				       "fi",
				       2,
				       fields));

  /* Make a struct:

     struct bar {
       int ii;
       int arr[50];
       float ff;
       char cc;
     }
  */
  gcc_jit_field *bar_ff = gcc_jit_context_new_field (ctxt,
				  0,
				  float_type,
				  "ff");
  gcc_jit_field *bar_ii = gcc_jit_context_new_field (ctxt,
				  0,
				  int_type,
				  "ii");
  gcc_jit_field *bar_cc = gcc_jit_context_new_field (ctxt,
				  0,
				  char_type,
				  "cc");
  gcc_jit_type *int50arr_type =
    gcc_jit_context_new_array_type (ctxt,
				    0,
				    int_type,
				    50);
  gcc_jit_field *bar_fi = gcc_jit_context_new_field (ctxt,
						 0,
						 int50arr_type,
						 "arr");
  gcc_jit_field *fields2[] = {bar_ff, bar_fi, bar_ii, bar_cc};

  gcc_jit_type *struct_bar_type =
    gcc_jit_struct_as_type (
      gcc_jit_context_new_struct_type (ctxt,
				       0,
				       "bar",
				       4,
				       fields2));

  /* Make an union:

     union ubar {
       float ff;
       int ii;
     };
  */
  gcc_jit_field *ubar_ff = gcc_jit_context_new_field (ctxt,
						      0,
						      float_type,
						      "ff");
  gcc_jit_field *ubar_ii = gcc_jit_context_new_field (ctxt,
						      0,
						      int_type,
						      "ii");
  gcc_jit_field *fields3[] = {ubar_ff, ubar_ii};
  gcc_jit_type *ubar = gcc_jit_context_new_union_type (ctxt,
						       0,
						       "ubar",
						       2,
						       fields3);

  (void) ubar;
  (void) struct_bar_type;
  (void) struct_fi_type;
  (void) bool_type;
  (void) double_type;
  (void) pint_type;
  (void) voidptr_type;
  (void) size_type;

  gcc_jit_function *fn_int_3;
  { /* int foo () { int local = 3; return local;} */
    fn_int_3 =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    int_type,
				    "fn_int_3",
				    0,
				    0,
				    0);
    gcc_jit_block *block = gcc_jit_function_new_block (fn_int_3, "start");
    gcc_jit_lvalue *local = gcc_jit_function_new_local (fn_int_3,
							0,
							int_type,
							"local");
    gcc_jit_rvalue *rval = gcc_jit_context_new_rvalue_from_int (
      ctxt, int_type, 3);

    gcc_jit_block_add_assignment (block, 0, local, rval);

    gcc_jit_block_end_with_return (block,
				   0,
				   gcc_jit_lvalue_as_rvalue(local));
  }
  { /* struct fi foo() { return (struct fi){1,2};} */
    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    struct_fi_type,
				    "fn_fi_1_2",
				    0,
				    0,
				    0);
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    gcc_jit_rvalue *rval_f1 = gcc_jit_context_new_rvalue_from_int (
      ctxt, float_type, 1);
    gcc_jit_rvalue *rval_i2 = gcc_jit_context_new_rvalue_from_int (
      ctxt, int_type, 2);

    gcc_jit_rvalue *vals[] = { rval_f1, rval_i2};
    gcc_jit_field *fields[] = {fi_f, fi_i};

    gcc_jit_rvalue *ctor = gcc_jit_context_new_struct_constructor
      (ctxt, 0,
       struct_fi_type,
       2,
       fields,
       vals);

    gcc_jit_block_end_with_return (block,
				   0,
				   ctor);
  }
  { /*
       struct fi foo()
       {
	 struct fi local = {1,2};
	 local = (struct fi){5,6};
	 return local;
       }
     */
    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    struct_fi_type,
				    "fn_fi_5_6",
				    0,
				    0,
				    0);
    gcc_jit_lvalue *local = gcc_jit_function_new_local (fn,
							0,
							struct_fi_type,
							"local");
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    {
      gcc_jit_rvalue *rval_f1 =
	gcc_jit_context_new_rvalue_from_int (ctxt, float_type, 1);
      gcc_jit_rvalue *rval_i2 =
	gcc_jit_context_new_rvalue_from_int (ctxt, int_type, 2);

      gcc_jit_rvalue *vals[] = { rval_f1, rval_i2};
      gcc_jit_field *fields[] = {fi_f, fi_i};

      gcc_jit_rvalue *ctor = gcc_jit_context_new_struct_constructor
	(ctxt, 0,
	 struct_fi_type,
	 2,
	 fields,
	 vals);
      gcc_jit_block_add_assignment (block, 0, local, ctor);
    }
    {
      gcc_jit_rvalue *rval_f1 =
	gcc_jit_context_new_rvalue_from_int (ctxt, float_type, 5);
      gcc_jit_rvalue *rval_i2 =
	gcc_jit_context_new_rvalue_from_int (ctxt, int_type, 6);

      gcc_jit_rvalue *vals[] = { rval_f1, rval_i2};
      gcc_jit_field *fields[] = {fi_f, fi_i};

      gcc_jit_rvalue *ctor = gcc_jit_context_new_struct_constructor
	(ctxt, 0,
	 struct_fi_type,
	 2,
	 fields,
	 vals);
      gcc_jit_block_add_assignment (block, 0, local, ctor);
    }

    gcc_jit_block_end_with_return (block,
				   0,
				   gcc_jit_lvalue_as_rvalue(local));
  }
  { /* struct fi foo() { struct fi local = {1, fn_int_3()};
			 return local;}

       The ctor has a side effect (funccall) */
    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    struct_fi_type,
				    "fn_fi_1_3",
				    0,
				    0,
				    0);
    gcc_jit_lvalue *local = gcc_jit_function_new_local (fn,
							0,
							struct_fi_type,
							"local");
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    {
      gcc_jit_rvalue *rval_f1 =
	gcc_jit_context_new_rvalue_from_int (ctxt, float_type, 1);
      gcc_jit_rvalue *rval_i2 =
	gcc_jit_context_new_call (ctxt, 0, fn_int_3, 0, 0);

      gcc_jit_rvalue *vals[] = { rval_f1, rval_i2};
      gcc_jit_field *fields[] = {fi_f, fi_i};

      gcc_jit_rvalue *ctor = gcc_jit_context_new_struct_constructor
	(ctxt, 0,
	 struct_fi_type,
	 2,
	 fields,
	 vals);
      gcc_jit_block_add_assignment (block, 0, local, ctor);
    }

    gcc_jit_block_end_with_return (block,
				   0,
				   gcc_jit_lvalue_as_rvalue(local));
  }
  { /* struct fi foo(fi) { return fi;}
       struct fi bar() { return foo((struct fi){3, 4}); }
     */

    gcc_jit_param *fi_param =
      gcc_jit_context_new_param (ctxt, 0, struct_fi_type, "fi");

    gcc_jit_function *fn0 =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    struct_fi_type,
				    "fn_fi_x_x",
				    1,
				    &fi_param,
				    0);
    gcc_jit_block *block0 = gcc_jit_function_new_block (fn0, "start");
    gcc_jit_block_end_with_return (block0,
				   0,
				   gcc_jit_param_as_rvalue (
				     gcc_jit_function_get_param (fn0, 0)));

    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    struct_fi_type,
				    "fn_fi_3_4",
				    0,
				    0,
				    0);
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    gcc_jit_rvalue *rval_f1 = gcc_jit_context_new_rvalue_from_int (
      ctxt, float_type, 3);
    gcc_jit_rvalue *rval_i2 = gcc_jit_context_new_rvalue_from_int (
      ctxt, int_type, 4);

    gcc_jit_rvalue *vals[] = { rval_f1, rval_i2};
    gcc_jit_field *fields[] = {fi_f, fi_i};

    gcc_jit_rvalue *ctor = gcc_jit_context_new_struct_constructor
      (ctxt, 0,
       struct_fi_type,
       2,
       fields,
       vals);

    gcc_jit_rvalue *call = gcc_jit_context_new_call (ctxt, 0, fn0, 1, &ctor);

    gcc_jit_block_end_with_return (block,
				   0,
				   call);
  }
  { /*
       void foo(struct bar *b) { *b = (struct bar) {.arr = {1,2}; }
     */

    gcc_jit_param *param =
      gcc_jit_context_new_param (ctxt, 0,
				 gcc_jit_type_get_pointer (struct_bar_type),
				 "b");


    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    void_type,
				    "fn_pbar_12",
				    1,
				    &param,
				    0);
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    gcc_jit_rvalue *rval_i1 = gcc_jit_context_new_rvalue_from_int (
      ctxt, int_type, 1);
    gcc_jit_rvalue *rval_i2 = gcc_jit_context_new_rvalue_from_int (
      ctxt, int_type, 2);

    gcc_jit_rvalue *arr_vals[] = { rval_i1, rval_i2};

    gcc_jit_rvalue *arr_ctor = gcc_jit_context_new_array_constructor
      (ctxt, 0,
       int50arr_type,
       2,
       arr_vals);

    gcc_jit_rvalue *str_ctor = gcc_jit_context_new_struct_constructor
      (ctxt,
       0,
       struct_bar_type,
       1,
       &bar_fi,
       &arr_ctor);

    gcc_jit_param *p0 = gcc_jit_function_get_param (fn, 0);
    gcc_jit_lvalue *lv0 =  gcc_jit_param_as_lvalue (p0);
    gcc_jit_lvalue *deref =
      gcc_jit_rvalue_dereference (gcc_jit_lvalue_as_rvalue (lv0), 0);

    gcc_jit_block_add_assignment (block, 0,
				  deref,
				  str_ctor);

    gcc_jit_block_end_with_void_return (block, 0);
  }
  { /* struct bar foo() { struct bar local = {};
			  return local;}
     */
    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    struct_bar_type,
				    "fn_bar_0s",
				    0,
				    0,
				    0);
    gcc_jit_lvalue *local =
      gcc_jit_function_new_local (fn,
				  0,
				  struct_bar_type,
				  "local");
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    gcc_jit_rvalue *ctor = gcc_jit_context_new_struct_constructor
	(ctxt, 0,
	 struct_bar_type,
	 0,
	 0,
	 0);
    gcc_jit_block_add_assignment (block, 0, local, ctor);

    gcc_jit_block_end_with_return (block,
				   0,
				   gcc_jit_lvalue_as_rvalue(local));
  }
  { /* struct bar foo() { struct bar local;
			  local.arr = (int [50]){1,2,3,4,5,6};
			  return local;}
     */
    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    struct_bar_type,
				    "fn_bar_123s",
				    0,
				    0,
				    0);
    gcc_jit_lvalue *local =
      gcc_jit_function_new_local (fn,
				  0,
				  struct_bar_type,
				  "local");
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    gcc_jit_rvalue *values[6];

    for (int i = 0; i < 6; i++)
      values[i] = gcc_jit_context_new_rvalue_from_int (ctxt, int_type, i + 1);

    gcc_jit_rvalue *ctor = gcc_jit_context_new_array_constructor
	(ctxt, 0,
	 int50arr_type,
	 6,
	 values);

    gcc_jit_lvalue *arr_lv = gcc_jit_lvalue_access_field (local,
							  0,
							  bar_fi);
    gcc_jit_block_add_assignment (block, 0, arr_lv, ctor);

    gcc_jit_block_end_with_return (block,
				   0,
				   gcc_jit_lvalue_as_rvalue(local));
  }
  { /* int[50] foo() { int arr[50];
		       arr = (int [50]){1,2,3,4,5,6};
		       return arr;}

       N.B: Not a typo, returning an array.
     */
    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    int50arr_type,
				    "fn_int50arr_123s",
				    0,
				    0,
				    0);
    gcc_jit_lvalue *local =
      gcc_jit_function_new_local (fn,
				  0,
				  int50arr_type,
				  "local");
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    gcc_jit_rvalue *values[6];

    for (int i = 0; i < 6; i++)
      values[i] = gcc_jit_context_new_rvalue_from_int (ctxt, int_type, i + 1);

    gcc_jit_rvalue *ctor = gcc_jit_context_new_array_constructor (
	 ctxt,
	 0,
	 int50arr_type,
	 6,
	 values);

    gcc_jit_block_add_assignment (block, 0, local, ctor);

    gcc_jit_block_end_with_return (block,
				   0,
				   gcc_jit_lvalue_as_rvalue(local));
  }
  { /*
      Verify that circular linked lists compiles, .e.g.
      that visit_children does not run in circles or something.

      struct llist { struct llist *next; };

      bool foo (void)
      {
	volatile struct llist a;
	volatile struct llist b;

	a = (struct llist) {.next = &b};
	b = (struct llist) {.next = &a};

	return a.next == &b;
      }
    */
    gcc_jit_struct *llist =
      gcc_jit_context_new_opaque_struct(ctxt,
					0, "llist_lcl");
    gcc_jit_field *fields[] =
      {
	gcc_jit_context_new_field (ctxt, 0,
				   gcc_jit_type_get_pointer (
				     gcc_jit_struct_as_type (llist)),
				   "next")
      };
    gcc_jit_struct_set_fields (llist, 0, 1, fields);
    gcc_jit_type *t_llist = gcc_jit_struct_as_type (llist);

    gcc_jit_function *fn =
      gcc_jit_context_new_function (ctxt,
				    0,
				    GCC_JIT_FUNCTION_EXPORTED,
				    bool_type,
				    "fn_llist",
				    0,
				    0,
				    0);
    gcc_jit_block *block = gcc_jit_function_new_block (fn, "start");

    gcc_jit_lvalue *a =
      gcc_jit_function_new_local (fn,
				  0,
				  gcc_jit_type_get_volatile (t_llist),
				  "a");
    gcc_jit_lvalue *b =
      gcc_jit_function_new_local (fn,
				  0,
				  gcc_jit_type_get_volatile (t_llist),
				  "b");

    gcc_jit_rvalue *a_addr = gcc_jit_lvalue_get_address( a, 0);
    gcc_jit_rvalue *b_addr = gcc_jit_lvalue_get_address( b, 0);

    gcc_jit_rvalue *a_ctor = gcc_jit_context_new_struct_constructor (
	 ctxt,
	 0,
	 t_llist,
	 1,
	 0,
	 &b_addr);

    gcc_jit_rvalue *b_ctor = gcc_jit_context_new_struct_constructor (
	 ctxt,
	 0,
	 t_llist,
	 1,
	 0,
	 &a_addr);

    gcc_jit_block_add_assignment (block, 0,
				  a, a_ctor);
    gcc_jit_block_add_assignment (block, 0,
				  b, b_ctor);

    gcc_jit_rvalue *cmp =
      gcc_jit_context_new_comparison (
	ctxt, 0,
	GCC_JIT_COMPARISON_EQ,
	gcc_jit_rvalue_access_field (gcc_jit_lvalue_as_rvalue (a),
				     0, fields[0]),
	gcc_jit_context_new_cast (ctxt, 0,
				  gcc_jit_lvalue_get_address (b, 0),
				  gcc_jit_type_get_pointer (t_llist)));

    gcc_jit_block_end_with_return (block,
				   0, cmp);
  }
}

struct fi2 {
  float f;
  int i;
};

struct bar2 {
  float ff;
  int arr[50];
  int ii;
  char c;
};

union ubar2 {
  float ff;
  int ii;
};

struct int50arr {
  int arr[50];
};

void __attribute__((optimize(0)))
scramble_stack(void)
  {
    char *p = alloca(100);
    for (int i = 0; i < 100; i++)
      *p++ = 0xF0;
    asm(""); /* Mark for side-effect */
  }

void __attribute__((optimize(0)))
scramble_arr (char *arr, int len)
{
  for (int i = 0; i < len; i++)
    *arr++ = i;
  asm(""); /* Mark for side-effect */
}

void
verify_code (gcc_jit_context *ctxt, gcc_jit_result *result)
{
  CHECK_NON_NULL (result);

  {
    struct fi2 (*fn) (void) = gcc_jit_result_get_code (result, "fn_fi_1_2");
    scramble_stack ();
    struct fi2 fi = fn ();
    CHECK_VALUE (fi.f, 1);
    CHECK_VALUE (fi.i, 2);
  }
  {
    struct fi2 (*fn) (void) = gcc_jit_result_get_code (result, "fn_fi_5_6");
    struct fi2 fi = fn ();
    CHECK_VALUE (fi.f, 5);
    CHECK_VALUE (fi.i, 6);
  }
  {
    struct fi2 (*fn) (void) = gcc_jit_result_get_code (result, "fn_fi_1_3");
    struct fi2 fi = fn ();
    CHECK_VALUE (fi.f, 1);
    CHECK_VALUE (fi.i, 3);
  }
  {
    struct fi2 (*fn) (void) = gcc_jit_result_get_code (result, "fn_fi_3_4");
    struct fi2 fi = fn ();
    CHECK_VALUE (fi.f, 3);
    CHECK_VALUE (fi.i, 4);
  }
  {
    scramble_stack();
    struct bar2 (*fn) (void) = gcc_jit_result_get_code (result, "fn_bar_0s");
    struct bar2 bar = fn ();
    struct bar2 key = {};

    CHECK_VALUE (bar.ff, 0);
    CHECK_VALUE (bar.ii, 0);
    CHECK_VALUE (memcmp (&bar.arr, &key.arr, sizeof (key.arr)), 0);
  }
  {

    void (*fn) (struct bar2 *) = gcc_jit_result_get_code (result, "fn_pbar_12");

    struct bar2 bar = (struct bar2) {};

    scramble_arr ((char*)&bar, sizeof bar);
    scramble_stack();

    fn (&bar);

    struct bar2 key = {.arr = {1,2}};
    __builtin_clear_padding (&key);

    CHECK_VALUE (memcmp (&bar, &key, sizeof (key)), 0);
  }
  {
    scramble_stack();
    struct bar2 (*fn) (void) = gcc_jit_result_get_code (result, "fn_bar_123s");
    struct bar2 bar = fn ();
    struct bar2 key = {.arr = {1,2,3,4,5,6} };

    CHECK_VALUE (memcmp (&bar.arr, &key.arr, sizeof (key.arr)), 0);
  }
  {
    scramble_stack ();
    /* This is abit shady. Lets just pretend that array returns à la Fortran
       is the same thing as returning a struct with an array in it in C. */
    struct int50arr (*fn) (void) =
      gcc_jit_result_get_code (result, "fn_int50arr_123s");
    struct int50arr ans = fn ();
    int key[50] = {1,2,3,4,5,6};

    CHECK_VALUE (memcmp (ans.arr, key, sizeof (key)), 0);
  }
  {
    _Bool (*fn) (void) = gcc_jit_result_get_code (result, "fn_llist");
    CHECK_VALUE (fn (), 1);
  }
}