///////////////////////////////////////////////////////////////////////
// Copyright 1991-1993 MetaWare Incorporated.  All rights reserved.  //
///////////////////////////////////////////////////////////////////////
// new/delete and array new/delete for C++.

#ifndef DEBUG
#define DEBUG 0
#endif
#include <stdlib.h>

// Array init initializes an array and, if required, allocates it
// as well.  The allocation occurs only as a request from "new".
// Correspondingly a "delete" request may come for that same array.
// For an array allocation we allocate the array and possibly some extra
// space.  The extra space stores the array count and is needed in certain
// circumstances.

// Here is how the size is stored:
// If the array alignment is 4 bytes or less and the number of elements
// is <= 65536:
//	struct {
//          ushort nelts;
//          ushort magic;	// 0xabcd
//          char user_array[];
//          };
// If the array alignment is > 4 bytes or the # elements is > 65536:
//	struct {	
//          ulong nelts;        // # elements
//          short how_much_pad; // # how much padding above user array?
//          short magic;        // 0xabce
//          char user_array[];
//          }
// Thus, for the typical case we use only 4 bytes.  For non-typical case
// we use 8 (for <= 8 align) and 16 (for <= 16 align).  Such severe
// alignments are relevant only for machines for which alignemnt is
// enforced, such as 860 or sun4.
// A simpler scheme would have been to put elemcount*2 in the preceding
// word, and if there was more space than 4 bytes, or in bit 1 in that
// word.  This doesn't work as well on the 286 and also we don't get
// the checking with the magic number.

#define AL 1    // Define 0 if you don't want it.
#if _I386 || _I286
#define MAXALIGN 4
#elif _I860
#define MAXALIGN 16
#else
#define MAXALIGN 8
#endif
static const int magic_meaning_4_bytes = 0xabcd,
	     magic_meaning_more = 0xabce;

// We used to pass "as_base", but arrays are always constructed at
// the top, so that's not necessary.
// Format of mode:
//	mode << 8 = alignment (1,2,4,8,16,32)
//	mode & 1 = please store the count; the class type has destructors
//		   and so we'll be calling array_dest later.
//	mode & 2 = please call passed function.  We need this because 0
//		   may in fact be a valid function address!
//	mode & 4 = please deallocate this storage when done.  Should
//		   be set only when the count was stored.
enum {ARRAY_STORE_SIZE=1, ARRAY_CALL_FUNC=2, ARRAY_DEALLOCATE=4,};
#define ALIGN_OF(x) ((x)>>8)

#define determine_extra_space(mode) \
    unsigned extra_space = 0;                                          \
    if (mode & ARRAY_STORE_SIZE) {                                     \
    	unsigned alignment = ALIGN_OF(mode);                           \
     	extra_space = _max(4,_min(MAXALIGN,alignment));                \
    	if (elemcount > 65535) extra_space = _max(extra_space,8);      \
    	}

// Compute the size, including overhead, without actually allocating
// the array.  This is for supporting user-defined operator new for
// arrays.
extern "C" long __array_size(unsigned elemcount, int elemsize, unsigned mode) {
    determine_extra_space(mode);
    return elemcount*elemsize+extra_space;
    }

typedef unsigned short ushort;

extern "C" void *__array_init(char *base, 
	unsigned elemcount, int elemsize, void (*f)(char *,int),
	unsigned mode) {
    determine_extra_space(mode);
    if (DEBUG) printf("array_init(%x,%d,%d,%x,%d,%d)\n", 
    	base,elemcount,elemsize,f,mode,extra_space);
    // If the pointer is 0, allocate it as well.
    // ARRAY_STORE_SIZE should not be requested for a non-newed array,
    // so that extra space will be 0 for such.
    if (base == 0) base = new char[elemcount*elemsize+extra_space];
    if (extra_space) {
    	base += extra_space;
    	ushort *q = (ushort*)base; 
    	if (extra_space == 4) {
    	    q[-1] = magic_meaning_4_bytes;
    	    q[-2] = elemcount;
    	    }
    	else {
    	    q[-1] = magic_meaning_more;
    	    q[-2] = extra_space;
    	    long *p = (long*)base;
    	    p[-2] = elemcount;
    	    }
    	}
    char *p = base;
    // Sorry, we may be given an address of 0 because the constructor
    // is the first thing in the program executable!  Anyway, the compiler
    // always guarantees a valid procedure.
    if ((mode & ARRAY_CALL_FUNC) && base) {
    	for (int i = 0; i < elemcount; i++) {
            if (DEBUG) printf("init %p elemsize=%d\n",p,elemsize);
     	    f(p,0), p+=elemsize;
     	    }
     	}
    return base;
    }

// A SOM destructor needs three parameters:  object, should-i-delete,
// and the destruction info pointer.  So we always pass a third 0 to
// a destructor, even though for native C++ it is not used.

extern "C" void *__array_dest(char *base, 
	unsigned elemcount, int elemsize, void (*f)(char *, int, int), 
	unsigned mode) {
    // delete[] p; has to work even if p is nil.  So check here, because
    // the compiler doesn't emit code to do so.
    if (base == 0) return 0;
    if (DEBUG) printf("array_dest(%x,%d,%d,%x,%d)\n",
    	base,elemcount,elemsize,f,mode);
    unsigned extra_space = 0;
    if (mode & ARRAY_STORE_SIZE) {	
    	ushort *q = (ushort*)base;
    	if (q[-1] == magic_meaning_4_bytes) {
    	    extra_space = 4;
    	    elemcount = q[-2];
    	    }
    	else if (q[-1] == magic_meaning_more) {
    	    extra_space = q[-2];
    	    long *p = (long*)base;
            elemcount = p[-2];
            }
    	else {
    	    printf("Array deletion:  non-array or corrupted array passed.\n");
    	    exit(1);
    	    }
    	}
    char *p = ((char *)base)+elemsize*elemcount;
    if (mode & ARRAY_CALL_FUNC) 
    	for (int i = 0; i < elemcount; i++) {
    	    if (DEBUG) printf("dest %p elemsize=%d\n",p,elemsize);
            p-=elemsize, f(p,0,0);	// Third 0 needed for SOM.
            }
    if (mode & ARRAY_DEALLOCATE) {
	delete (base-extra_space);
	base = 0;
	}
    return base;
    }

extern "C" void *__array_copy(char *dest, char *source,
	unsigned elemcount, int elemsize, void (*copy)(char *,int,char *)) {
    if (DEBUG) printf("array_copy(%x,%x,%d,%d,%x)\n", 
    	dest,source,elemcount,elemsize,copy);
    // Sorry, we may be given an address of 0 because the constructor
    // is the first thing in the program executable!  Anyway, the compiler
    // always guarantees a valid procedure.
    for (int i = 0; i < elemcount; i++) {
        if (DEBUG) printf("copy (%p<-%p) elemsize=%d\n",dest,source,elemsize);
     	copy(dest,0,source), dest+=elemsize, source+=elemsize;
     	}
    return dest;
    }
