#include <stdlib.h>
# include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <limits.h>
#include "gpk_std.h"
#include "gpklib.h"
#include "innerloop4.h"

/*
Copyright 2002-2008 Greg Kochanski. 
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

This program may be downloaded from http://www.sourceforge.org from the "speechresearch" project.

These functions allocate 1, 2, and 3 dimensional arrays.
They provide protection beyond the basic malloc() function
by adding guard words on each side of the data.
Writing immediately outside the bounds of an array is likely
to be detected.   The guard words also allow detection
of data that is freed twice.
   Such problems are noticed when you free the data (with O_FREE(),
   T_FREE(), or TH_FREE()), or when you explicitly check it
   with O_CHECK(), T_CHECK(), or TH_CHECK().

The arrays also carry size information, so you don't have to
hold it seperately.    O_SIZE(), T_SIZE() and TH_SIZE() report
the size of an array.

In the 1-dimensional case, they behave much like malloc(),
except that the functions are specialized to one data type,
so you don't have to multiply by the size of one item, or
cast to the desired type.   That's done automatically.

I the two- and three-dimensional cases, they also allow you the convenience
of addressing data as   x[i][j], instead of the more confusing
and cumbersome   x[i*stride+j] form.
Two-dimensional arrays are allocated as a single chunk,
so that the array has good memory locality, and can
also be handed to programs that expect it in the strided form.

These arrays are to be used as a unit.   It is an error 
to move or replace 1-d arrays inside a 2-d array.

*/


#	define MAGIC0	(size_t)(0x76654391^sizeof(RESULT))		/* Beginning marker. */
#	define MAGIC0F	(size_t)(0x113f2ee3^(sizeof(RESULT)<<6))	/* Marks stuff that has been freed. */
#	define MAGIC1	(size_t)(027451|sizeof(RESULT))		/* End marker. */
#	define MAGIC2	(unsigned)(07036&~sizeof(RESULT))
#	define MAGIC3	(unsigned)(0x37659f&~sizeof(RESULT))
#	define MAGIC4	(uintptr_t)(07317&~sizeof(RESULT))
#	define MAGIC4F	(uintptr_t)(07607&~sizeof(RESULT))


#define QUOTE(s)	QUOTE_GUTS(s)
#define QUOTE_GUTS(s)	#s

#define D if(1)
/* #define D if(0) */

union fi	{RESULT resultalign; void *ptralign; size_t ii;};
union fpi	{RESULT *fp;	int i; uintptr_t m;};
union fppi	{RESULT **fpp;	int i; uintptr_t m; };


static int o_test_guts(const RESULT *q, C char *where)
{
 C union fi *p = (C union fi *)q;
 if(p < &((C union fi*)0)[3])
	 return 1;
 if(p[-1].ii==MAGIC0F && p[-2].ii==0)	{
	fprintf(stderr, "WARN: O_TEST(%s): testing memory that has already been freed (array at %p).\n", where, q);
	return 4;
	}
 {
 C size_t n = p[-1].ii ^ MAGIC0;
 C size_t nfi = (n*sizeof(RESULT)+sizeof(union fi)-1)/sizeof(union fi);
 if(p[-2].ii != nfi) {
	fprintf(stderr, "WARN: O_TEST(%s): array at %p modified before beginning.\n", where, q);
	return 2;
	}
 }
 if(p[p[-2].ii].ii != MAGIC1) {
	fprintf(stderr, "WARN: O_TEST(%s): End of the array at %p was overwritten.\n", where, q);
	return 3;
	}
 return 0;
}


static int o_size_guts(const RESULT *q, const char *where)
{
 if(!q)
	return 0;

 const int tmp = o_test_guts(q, where);
 const union fi *p = (const union fi *)q;
 if(tmp != 0)	{
	fprintf(stderr, "ERR: allocate.cod: O_SIZE(%s)[%lu] bytes error %d (%s)\n",
			where, (long unsigned)(p[-2].ii),
			tmp, tmp==3 ? "end" : "beginning");
	abort();
	}

 return (int)(p[-1].ii ^ MAGIC0);
}



static int t_test_guts(RESULT *C*q, C char *where)
{
 int m, n;
 C union fpi *C p = (C union fpi *C)q;
 uintptr_t hash = 0;

 if(p <= &((C union fpi *C)0)[5])
 	return 1;
 if(((uintptr_t)(p[0].fp)^MAGIC4F) == p[-2].m)	{
	fprintf(stderr, "WARN: T_TEST(%s): freeing memory that is already free (array at %p).\n", where, q);
	return 6;
	}
 if(((uintptr_t)(p[0].fp)^p[-2].m) != MAGIC4)
	return 10;
 m = p[-1].i;
 if(m < 0)
 	return 7;
 n = p[-4].i;
 if(n < 0)
 	return 8;
 if( p[-3].i != (int)((m+n)^MAGIC2)) {
	fprintf(stderr, "WARN: T_TEST(%s): m=%d, n=%d, p[-3].i=%d, should be %d\n",
				where, m, n,
				p[-3].i, (int)((m+n)^MAGIC2));
 	return 3;
 	}
 if(m>0 && p[m].i != p[-3].i)
 	return 4;
 if(m*n==0 && p[0].fp != 0)
 	return 300;
 if( o_size_guts(p[0].fp, where) < n*m)
 	return 9;

 int i;
 OPENMP(omp parallel for private(i) reduction(+: hash) schedule(guided, 512))
 for(i=0; i<m; i++)
 	/* We are assuming silent overflow here: */
	hash += (uintptr_t)(q[i])*(uintptr_t)17;
 if((int)hash != p[-5].i)
	return 5;
 return 0;
}


RESULT **T_ALLOC(C int m, C int n)
{
 union fpi *p;
 int i;
 uintptr_t hash = 0;

 assert(m>=0 && n>=0);
 p = (union fpi *)malloc(sizeof(union fpi) * ((size_t)m+6));
 if(!p)
 	return (RESULT **)p;

 p += 5;
 RESULT **pr = (RESULT **)p;
 p[-4].i = n;
 p[-3].i = (m+n)^MAGIC2;
 p[-1].i = m;

 if((size_t)n*(size_t)m > 0 && (size_t)n*(size_t)m == (size_t)(n*m)) {
	RESULT *base = O_ALLOC(n*m);
 	if(!base) {
		free(&p[-5]);
		return 0;
 		}
	OPENMP(omp parallel for reduction(+: hash) schedule(guided, 256))
 	for(i=0; i<m; i++)	{
 		pr[i] = base + (size_t)n*i;
		/* We are assuming silent overflow here: */
		hash += (uintptr_t)(pr[i])*(uintptr_t)17;
		}
	}
 else
 	p[0].fp = 0;

 p[-2].m = (uintptr_t)(p[0].fp)^MAGIC4;
 p[-5].i = (int)hash;
 if(m > 0)
	p[m].i = p[-3].i;
 D {
 int tmp = t_test_guts((RESULT**)p, QUOTE(T_ALLOC));
 if(tmp != 0) {
 	fprintf(stderr, "tmp=%d\n", tmp);
	abort();
 	}
 }
 return pr;
}


int O_SIZE(const RESULT *q)
{
 return o_size_guts(q, QUOTE(O_SIZE));
}


int T_TEST(RESULT *C*q)
{
 return t_test_guts(q, QUOTE(T_TEST));
}


static void o_free_guts(RESULT *q, C char *where)
{
 union fi *p = (union fi *)q;

 const int tmp = o_test_guts(q, where);
 if(tmp >= 2)	{
	/* o_free_guts(0, where) (which implies tmp=1) is not an error. */
	fprintf(stderr, "ERR: allocate.cod: O_FREE(%s)[%ld] bytes error %d (%s)\n",
			QUOTE(O_FREE),
			(long unsigned)(p[-2].ii), tmp,
			tmp==3 ? "end" : "beginning");
	abort();
	}

 if(tmp == 0)	{
	/* Mark as freed, to let O_TEST be more informative: */
	p[-1].ii = MAGIC0F;
	p[-2].ii = 0;
 	free((char *)(p-2));
	}
}


void T_FREE(RESULT **q)
{
 union fpi *p = (union fpi *)q;

 if(!p)	return;

 const int tmp = t_test_guts(q, QUOTE(T_FREE));
 if(tmp >= 2)	{
	fprintf(stderr, "ERR: allocate.cod: T_FREE(%s)[%dx%d] error %d\n",
			QUOTE(T_FREE), p[-1].i, p[-4].i, tmp);
	abort();
	}
 if(tmp == 1)
	return;

 o_free_guts(p[0].fp, QUOTE(T_FREE));
 /* Mark as freed, to allow T_FREE to catch double-free errors. */
 p[-1].i = 0;
 p[-2].m = (uintptr_t)(p[0].fp)^MAGIC4F;
 free((char *)(p-5));
}


int O_TEST(const RESULT *q)
{
 return o_test_guts(q, QUOTE(O_TEST));
}



void O_FREE(RESULT *q)
{
 return o_free_guts(q, QUOTE(O_FREE));
}



RESULT *O_ALLOC(C int n)
{
 assert(n >= 0);
 union fi *p;
 C size_t nfi = ((size_t)n*sizeof(RESULT)+sizeof(union fi)-1)/sizeof(union fi);

 p = (union fi *)malloc((3+nfi)*sizeof(union fi));
 if(!p)	return (RESULT *)p;
 p += 2;
 p[-1].ii = MAGIC0^n;
 p[-2].ii = nfi;
 p[nfi].ii = MAGIC1;
 return (RESULT *)p;
}




void O_SET(RESULT *q, RESULT v)
{
 C int nfi = o_size_guts(q, QUOTE(O_SET));
 INNERLOOP(i, 0, nfi, q[i] = v);
}





void T_SET(RESULT **q, RESULT v)
{
 union fpi *p = (union fpi *)q;

 if(!p)	return;

 const int tmp = t_test_guts(q, QUOTE(T_SET));
 if(tmp >= 2)	{
	fprintf(stderr, "ERR: allocate.cod: T_FREE(%s)[%dx%d] error %d\n",
			QUOTE(T_SET), p[-1].i, p[-4].i, tmp);
	abort();
	}
 if(tmp == 1)
	return;

 O_SET(p[0].fp, v);
}





struct alloc_size T_SIZE(RESULT *C* q)
{
 struct alloc_size rv;
 C union fpi *p = (C union fpi *)q;

 if(!p) {
 	rv.rows = rv.cols = 0;
	return rv;
	}

 const int tmp = t_test_guts(q, QUOTE(T_SIZE));
 if(tmp >= 2)	{
	fprintf(stderr, "ERR: allocate.cod: T_FREE(%s)[%dx%d] error %d\n",
			QUOTE(T_SIZE), p[-1].i, p[-4].i, tmp);
	abort();
	}

 rv.rows = p[-1].i;
 rv.cols = p[-4].i;
 return rv;
}




/* space is to be added later -- int3 add does not allocate space, it
sets stuff up so that when you attach arrays allocated with T_ALLOC,
int3free will work properly. */

RESULT ***TH_ADD(C int c, C int m, C int n)
{
 union fppi *p;
 int i;

 assert(c>=0 && m>=0 && n>=0);
 p = (union fppi *)malloc(((size_t)c+3) * sizeof(union fppi));
 if(!p)	return (RESULT ***)0;
 p += 2;
/* #pragma omp parallel for private(i) */
 for(i=0; i<c; i++)
 	p[i].fpp = (RESULT **)0;
 p[-1].i = MAGIC3 ^ c;
 p[-2].i = c;
 p[c].i = p[-1].i;

 return (RESULT ***)p;
}


long TH_TEST(RESULT ***q)
{
 C union fppi *C p = (C union fppi *C)q;
 int tmp, c, i;

 if(!p)	return 1;
 if((p[-1].i^p[-2].i) != MAGIC3)
 	return 3;
 c = p[-2].i;
 if(p[c].i != p[-1].i)
 	return 4;
 for(i=0; i<c; i++)
	if((tmp=t_test_guts(p[i].fpp, QUOTE(TH_TEST))) != 0)
 		return 200L + 100L * tmp + i;
 return 0;
}


void TH_FREE(RESULT ***q)
{
 int i;
 union fppi *p = (union fppi *)q;

 if(!p)	return;
 if(TH_TEST(q) != 0)	{
	fprintf(stderr, "ERR: TH_TEST(%s)=%ld\n", QUOTE(TH_FREE), TH_TEST(q));
	assert(0);
 	}
 int c = p[-2].i;
 for(i=0; i<c && p[i].fpp; i++)
 	T_FREE(p[i].fpp);
 free((char *)(p-2));
}


void TH_SET(RESULT ***q, RESULT v)
{
 if(!q)	return;
 if(TH_TEST(q) != 0)	{
	fprintf(stderr, "ERR: TH_TEST(%s)=%ld\n", QUOTE(TH_SET), TH_TEST(q));
	assert(0);
 	}

 union fppi *p = (union fppi *)q;
 C int c = p[-2].i;
 int i;
 for(i=0; i<c && p[i].fpp; i++)
 	T_SET(p[i].fpp, v);
}



RESULT ***TH_ALLOC(C int c, C int m, C int n)
{
 union fppi *p;
 int i;

 assert(c>=0 && m>=0 && n>=0);
 p = (union fppi *)malloc(((size_t)c+3) * sizeof(union fppi));
 if(!p)	return (RESULT ***)0;
 p+=2;
 for(i=0; i<c; i++)	{
 	p[i].fpp = T_ALLOC(m, n);
	if(!p[i].fpp)	break;
	}
 p[-1].i = MAGIC3 ^ i;
 p[-2].i = i;
 p[i].i = p[-1].i;

 if(i<c)	{
 	TH_FREE((RESULT ***)p);
	return (RESULT ***)0;
	}

 return (RESULT ***)p;
}
