/*********************************************************************
(C) Copyright 1994;       MetaWare Incorporated;  Santa Cruz, CA 95060
This program is the unpublished property and trade secret of MetaWare.
It is to be  utilized  solely  under  license  from  MetaWare  and  it 
is to be maintained on a confidential basis for internal  company  use
only.  The  security  and  protection  of  the program is paramount to
maintenance of the trade secret status.  It is to  be  protected  from
disclosure  to  unauthorized parties, both within the Licensee company
and outside, in a manner not less stringent than that utilized for Li-
censee's own proprietary  internal  information.   No  copies  of  the
source or Object Code are to leave the premises of Licensee's business
except  in  strict accordance with the license agreement signed by Li-
censee with MetaWare.
*********************************************************************/
/*
 * $Id: arrays.cpp,v 1.11 1994/11/01 21:23:18 marcusm Exp $
 *
 * $Log: arrays.cpp,v $
 * Revision 1.11  1994/11/01  21:23:18  marcusm
 * Integrated Tom's changes.  he added opdelete input to array_dest.
 *
// 
//    Rev 1.1   10/28/94 17:18:50   tom
// array_dest can take an operator delete passed it; for delete[].
// 
//    Rev 1.0   08/06/94 02:17:22   tom
// Initial revision.
 * Revision 1.8  1994/03/09  03:02:23  marka
 * Add Tom's exception handling support.
 *
 */

#include <stdio.h>
#include <stdlib.h>
// #define __TYPEINFO_IMPLEMENTATION 1
// #include "tinfo.h"
#include "array.h"

#ifdef DEBUG
    #undef DEBUG
    #define DEBUG 1
    #define DPRINTF(x) printf x
#else
    #define DEBUG 0
    #define DPRINTF(x)
#endif

// 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, turn on bit 0 in that
// word.  This doesn't work as well on the 286 and also we don't get
// the checking with the magic number.

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.
//	mode & 8 = reserved for compiler internal use.
//	mode & 10= function passed in is the address of the function instead.
//		   This is for SOM, where ptr-to-func-member is the address
//		   of the function to be called.
// These are in array.h:
// enum {ARRAY_STORE_SIZE=1, ARRAY_CALL_FUNC=2, ARRAY_DEALLOCATE=4,
//   	COMPILER_RESERVED=8, ARRAY_FUNCTION_IS_INDIRECT=16, 
//	ARRAY_DELETE=32};
// #define ALIGN_OF(x) ((x)>>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;
    }

using namespace __array;
typedef unsigned short ushort;

extern "C" void *__array_start(char *base) {
    // We will have used either 4, 8, 16, ... MAXALIGN words of header.
    // "base" points to the header.  Find the actual array.
    int al = 2; // 2**2
    char *start = base;
    while (al <= MAXALIGN) {
	start = base+ (1 << al);
	// See if the magic number is there.
	ushort magic = ((ushort*)start)[-1];
	DPRINTF(("Checking for magic at %x:%x\n",start,magic));
	if (magic == magic_meaning_4_bytes || magic == magic_meaning_more)
	    return start;
	// try greater alignment.
	al *= 2;
	}
    // Failed.  
    return base;
    }
		
extern "C" void *__array_init(char *base, 
	unsigned elemcount, int elemsize, void (*f)(char *,int),
	unsigned mode) {
    determine_extra_space(mode);
    DPRINTF(("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];
    base = setup_base(extra_space,base,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++) {
	    DPRINTF(("init %p elemsize=%d\n",p,elemsize));
	    f(p,0), p+=elemsize;
	    }
	}
    return base;
    }

char *__array::setup_base(
	unsigned extra_space, char *base, unsigned elemcount) {
    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;
	    }
	}
    return base;
    }
int __array::stored_elemcount(char *base, unsigned &extra_space) {
    ushort *q = (ushort*)base;
    if (q[-1] == magic_meaning_4_bytes) {
	extra_space = 4;
	return q[-2];
	}
    else if (q[-1] == magic_meaning_more) {
	extra_space = q[-2];
	long *p = (long*)base;
	return p[-2];
	}
    else {
	printf("Array deletion:  non-array or corrupted array passed.\n");
	exit(1);
	}
    }

extern "C" long __array_nelements(char *base) {
    unsigned dummy;
    if (base == 0) return 0;
    return stored_elemcount(base,dummy);
    }

extern "C" char* __array_header(char *base) {
    unsigned extra;
    if (base == 0) return 0;
    stored_elemcount(base,extra);
    return base-extra;
    }

// 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, void (*opdelete)(void *, unsigned size)) {
    // 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;
    DPRINTF(("array_dest(%x,%d,%d,%x,%d,%x)\n",
	base,elemcount,elemsize,f,mode,opdelete));
    unsigned extra_space = 0;
    if (mode & ARRAY_STORE_SIZE) elemcount = stored_elemcount(base,extra_space);
    char *p = ((char *)base)+elemsize*elemcount;
    if (mode & ARRAY_CALL_FUNC) {
	typedef void (*dtor_type)(char *,int,int);
	dtor_type func_to_call = f;
	if (mode & ARRAY_FUNCTION_IS_INDIRECT)   
	    // We have been passed the pointer to the cell containing
	    // the function address -- the SOM pointer-to-func-member.
	    // Extract it.
	    func_to_call = *(dtor_type*)func_to_call;
	for (int i = 0; i < elemcount; i++) {
	    DPRINTF(("dest %p elemsize=%d\n",p,elemsize));
	    p-=elemsize, func_to_call(p,0,0);	// Third 0 needed for SOM.
	    }
	}
    if (mode & ARRAY_DEALLOCATE) {
	// Call user opdelete if passed in.  WE ASSUME functions can't
	// start at location 0.  If this is a bad assumption, we'll have
	// to pass in another bit.
	if (mode & ARRAY_DELETE) 
	    opdelete(base-extra_space,elemsize*elemcount+extra_space);
	else delete (base-extra_space);
	base = 0;
	}
    return base-extra_space;
    }

extern "C" void *__array_copy(char *dest, char *source,
	unsigned elemcount, int elemsize, void (*copy)(char *,int,char *)) {
    DPRINTF(("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++) {
	DPRINTF(("copy (%p<-%p) elemsize=%d\n",dest,source,elemsize));
	copy(dest,0,source), dest+=elemsize, source+=elemsize;
	}
    return dest;
    }

extern "C" void *__array_assign(char *dest, char *source,
	unsigned elemcount, int elemsize, void (*asn)(char *,char *)) {
    DPRINTF(("array_assign(%x,%x,%d,%d,%x)\n",
	dest,source,elemcount,elemsize,asn));
    for (int i = 0; i < elemcount; i++) {
	DPRINTF(("asn (%p<-%p) elemsize=%d\n",dest,source,elemsize));
	asn(dest,source), dest+=elemsize, source+=elemsize;
	}
    return dest;
    }

