/***********************************************************************
 *
 *	Object Table maintenance module.
 *
 ***********************************************************************/

/***********************************************************************
 *
 * Copyright (C) 1990, 1991, 1992 Free Software Foundation, Inc.
 * Written by Steve Byrne.
 *
 * This file is part of GNU Smalltalk.
 *
 * GNU Smalltalk is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 1, or (at your option) any later 
 * version.
 * 
 * GNU Smalltalk 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 General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * GNU Smalltalk; see the file COPYING.  If not, write to the Free Software
 * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  
 *
 ***********************************************************************/


/*
 *    Change Log
 * ============================================================================
 * Author      Date       Change 
 * sbb	     31 Dec 91	  Added registered oops to root set.
 *
 * sbb	     31 Dec 91	  oopTable now allocated from memory instead of being
 *			  stored as part of the executable.
 *
 * sbb	      8 Dec 91	  Changed oopValid to only check the FREE bit, instead
 *			  of worrying about the even odd flags, which may not
 *			  be valid.
 *
 * sbb	     20 Oct 91	  Support for growing now fully operational (and no, it
 *			  hasn't taken me over a month to track down the
 *			  problems; free time has been nil).  Also removed more
 *			  vestiges of the incremental GC.
 *
 * sbb	     15 Sep 91	  Added support for loading larger semispaces from
 *			  saved images.
 *
 * sbb	      4 Aug 91	  Removed more vestiges of the incremental GC, began
 *			  switchover to automatically growing semi-spaces.
 *
 * sbb	     13 Oct 90	  Converted to use bit masks instead of bit fields,
 *			  hoping to improve performance somewhat.
 *
 * sbyrne     8 Apr 90	  Changed oopFree to oopValid to fix the bug with
 *			  someInstance losing after GC's due to objects that
 *			  have non-free OOP table entries, but point to freed
 *			  objects.
 *
 * sbyrne     7 Apr 90	  Increased mem space size to 4M.  This can be
 *			  decreased as necessary.
 *
 * sbyrne    24 Feb 90	  Update to change log: there are no longer any
 *			  explicitly allocated OOPs due to the new symbol table
 *			  structure; the comment below is now a noop.
 *
 * sbyrne    20 Sep 89	  Added oop table slot GC'ing.  I'm not dealing with
 *			  oop table slots that are explictly allocated; I
 *			  believe that most OOP slots are not explicitly chosen
 *			  and so not running the incremental reclaimer for that
 *			  case shouldn't hurt us.
 *
 * sbyrne    12 Sep 89	  Much of the garbage collector's operation depends on
 *			  the fact that only 1 flip will occur between any two
 *			  operations (such as a compilation, or a byte-code).
 *			  The code would be much more complex if this were not
 *			  the case, and I'm not sure that things would even be
 *			  possible if this were not the case.  Anyway, there is
 *			  code in this routine to check for that eventuality
 *			  and to halt the system if it occurs.
 *
 * sbyrne     6 Sep 89	  started implementing the garbage collector (YAY!!!)
 *
 * sbyrne    13 Jan 89	  Created.
 *
 */


#include <stdio.h>
#include "mst.h"
#include "mstoop.h"
#include "mstdict.h"
#include "mstsave.h"
#include "mstcomp.h"
#include "mstcallin.h"

/* Size of the object semi spaces, in bytes */
#define	K		1024

#ifndef atarist
/* Min for Kernel = 512K, Kernel+STIX min is 1M */
/* you can increase this value if you need more space, and it won't hurt
 * performance *if* your machine has enough physical memory (otherwise, you
 * thrash the pager) */
#ifdef AMIGA
#define INIT_MEM_SPACE_SIZE		/*(512 * K) */ (1500 * K)
#else
#define INIT_MEM_SPACE_SIZE		/*(512 * K) */ (2 * K * K)
#else
#endif
#define INIT_MEM_SPACE_SIZE		(1152 * K) 
#endif

#ifdef pre_full_gc /* Sun Aug  4 19:00:10 1991 */
/**/#define oopReclaimFactor	4 /* how many oops to reclaim per oop alloc */
#endif /* pre_full_gc Sun Aug  4 19:00:10 1991 */


/* Define this flag to turn on debugging code for OOP table management */
/* #define OOP_DEBUGGING */



#define alignSize(size) \
 ( ((size) + DOUBLE_ALIGNMENT - 1) & ~(DOUBLE_ALIGNMENT - 1) )

#define objSpace(obj) \
  ( (((char *)(obj) >= spaces[1].space) \
     && ((char *)(obj) < spaces[1].space + spaces[1].totalSize)) \
   ? F_SPACE : 0 )
 
/* Returns 1 if space flag represents the ODD space, 0 for the EVEN space */
#define boolSpace(spaceFlag) \
  ((spaceFlag) == F_SPACE)

#define EVEN_ODD_MASK (F_EVEN | F_ODD)

typedef struct CompiledMethodStruct *Method;

extern Boolean		regressionTesting;

/* These are the real OOPS for nil, true, and false */
OOP			nilOOP, trueOOP, falseOOP;

/* The OOP table.  This contains a pointer to the object, and some flag
   bits indicating which semispace the pointed-to object lives in.  Some
   of the bits indicate the difference between the allocated length (stored 
   in the object itself, and the real length, for things like byte strings 
   that may not be an even multiple of 4 (== sizeof(void *)). */
struct OOPStruct	*oopTable;

#ifdef pre_full_gc /* Sun Aug  4 19:00:44 1991 */
/**//* This is the head of the free list.  The free list is maintained in the 
/**/   oop table.  Each OOP on the free list has a bit indicating that it''s free,
/**/   a pointer to the next free OOP, and a pointer to the previous free OOP.
/**/   when this points at NIL, we''re out of space */
/**/OOP			freeOOPs;
#endif /* pre_full_gc Sun Aug  4 19:00:44 1991 */
int			numFreeOOPs;

/* The indices of which is the current new space (toSpace) and which is
   the current old space (fromSpace).  At GC flip time, these two are 
   interchanged. */
unsigned long		fromSpace, toSpace;

Boolean			gcFlipped, gcState, gcMessage;
int			gcFlipCounter;

/* If there is this much space used after a gcFlip, we need to grow the other
 * semi space by spaceGrowRate next time we gcFlip, so that the storage gets
 * copied into the new, larger area.
 */
double			growThresholdPercent = 80.0;

/* Grow the semi spaces by this percentage when the amount of space used
 * exceeds growThresholdPercent.
 */
double			spaceGrowRate = 30.0;

/* This vector holds the storage for all the Character objects in the system.
   Since all character objects are unique, we pre-allocate space for 256 of
   them, and treat them as special built-ins when doing garbage collection.*/
CharObject		charObjectTable[NUM_CHAR_OBJECTS];

/* This is "nil" object in the system.  That is, the single instance of the
   UndefinedObject class, which is called "nil". */
struct NilObjectStruct	nilObject;

/* These represent the two boolean objects in the system, true and false.
   This is the object storage for those two objects. 
   false == &booleanObjects[0], true == &booleanObjects[1] */
struct BooleanObjectStruct booleanObjects[2];

struct memorySpaceStruct {
  char		*space;		/* base of allocated storage */
  char		*allocPtr;	/* new space ptr, starts hi, goes down */
  char		*copyPtr;	/* used by GC, points to highest copied space */
  char		*scanPtr;	/* used by GC, points to highest scanned addr  */
  unsigned long	size;		/* current remaining size */
  unsigned long	totalSize;	/* current allocated size */
  double	percentUsed;	/* amount used at just after copyReferences */
};

/* This contains the maximum size of a semi space.  It is checked before a
 * semi space gets used at gcflip time; if the space is too small, it is 
 * brought up to spec before being used
 */
unsigned long	maxSpaceSize;

/* These two variables represent information about the semispaces.  spaces
   holds the information for each semispace (basically the pointer to the
   base of the space, and the pointers into it for allocation, copying, and
   scanning.  curSpace holds the address of one of the two semispace data
   structures, and is used by the garbage collector */
static struct memorySpaceStruct spaces[2];
static struct memorySpaceStruct *curSpace;

/* This contains the bit mask of the current toSpace: it's F_EVEN when
   toSpace is 0, and F_ODD when toSpace is not zero */
static unsigned long	evenOddFlag;

#ifdef pre_full_gc /* Sun Aug  4 19:22:24 1991 */
/**/static Object		curObject;
/**/static long		curObjectSize/* , copyQuota*/;
#endif /* pre_full_gc Sun Aug  4 19:22:24 1991 */
#ifdef preserved /* Sat Aug  3 18:47:58 1991 */
/**/static double		copyRate;
/**/static double		copyRateAdjustment = 1.50; /* copy 50% more than last time */
#endif /* preserved Sat Aug  3 18:47:58 1991 */
static long		oopTableIndex;

static Object		moveObject();
static Boolean		isOOPAddr(), isObjAddr();
static void		initCharObject(), moveRootOOPs(),
			initSpace(), markBuiltinOOPs(), 
			copyReferencedObjects(), clearOldOOPs(),
			displayOOP(), displayObject();


/*
 *	void initOOPTable()
 *
 * Description
 *
 *	Initialize the OOP table.  Initially, all the OOPs are on the OOP free
 *	list so that's just how we initialize them.  We do as much
 *	initialization as we can, but we're called before classses are
 *	defined, so things that have definite classes must wait until
 *	the classes are defined.
 *
 */
void initOOPTable()
{
  int		i;

  allocOOPTable();

  numFreeOOPs = OOP_TABLE_SIZE;

  for (i = 0; i < OOP_TABLE_SIZE; i++) {
    /* forward chain free list of oops */
    oopTable[i].flags = F_FREE;
#ifdef pre_sc_gc /* Sat Jul 27 22:02:13 1991 */
/**/    oopTable[i].object = (Object)&oopTable[i+1];
/**/    oopTable[i].flags |= F_FREE;
#endif /* pre_sc_gc Sat Jul 27 22:02:13 1991 */
  }
#ifdef pre_sc_gc /* Sat Jul 27 22:03:03 1991 */
/**/  oopTable[i-1].object = nil;
/**/
/**/  freeOOPs = oopTable;
#endif /* pre_sc_gc Sat Jul 27 22:03:03 1991 */

  nilOOP	= &oopTable[nilOOPIndex];
  trueOOP	= &oopTable[trueOOPIndex];
  falseOOP	= &oopTable[falseOOPIndex];

  nilOOP->flags		= trueOOP->flags        = falseOOP->flags	= 0;

  nilOOP->object	= (Object)&nilObject;
  nilObject.objSize	= ROUNDED_WORDS(sizeof(struct NilObjectStruct));

  trueOOP->object		= (Object)&booleanObjects[0];
  falseOOP->object		= (Object)&booleanObjects[1];
  booleanObjects[0].objSize	= ROUNDED_WORDS(sizeof(struct BooleanObjectStruct));
  booleanObjects[1].objSize	= ROUNDED_WORDS(sizeof(struct BooleanObjectStruct));
  booleanObjects[0].booleanValue= trueOOP;
  booleanObjects[1].booleanValue= falseOOP;

dictInit();			/* ### TEMP HACK ### */

}


void allocOOPTable()
{
  oopTable = (struct OOPStruct *)malloc(sizeof(struct OOPStruct)
					*TOTAL_OOP_TABLE_SLOTS);
  if (oopTable == NULL) {
    errorf("Failed to allocate oopTable!!!");
    exit(1);
  }
}


/*
 *	void initNil()
 *
 * Description
 *
 *	Initialize the "nil" object.
 *
 */
void initNil()
{
  nilObject.objClass	= undefinedObjectClass;
}

/*
 *	void initBooleans()
 *
 * Description
 *
 *	Initialize the two boolean objects, after their respective classes have
 *	been created.
 *
 */
void initBooleans()
{
  booleanObjects[0].objClass	= trueClass;
  booleanObjects[1].objClass	= falseClass;
}

/*
 *	void initCharTable()
 *
 * Description
 *
 *	Initialize the instances of the class Character, after that class has
 *	been created.
 *
 */
void initCharTable()
{
  int		i;

  for (i = 0; i < NUM_CHAR_OBJECTS; i++) {
    initCharObject(i);
    oopTable[i + CHAR_OBJECT_BASE].object = (Object)&charObjectTable[i];
    oopTable[i + CHAR_OBJECT_BASE].flags = 0;
  }
}

/*
 *	static void initCharObject(i)
 *
 * Description
 *
 *	Initialize a single character object.
 *
 * Inputs
 *
 *	i     : The index of the character object, in the range 0..255.
 *
 */
static void initCharObject(i)
int	i;
{
  charObjectTable[i].objSize = ROUNDED_WORDS(sizeof(CharObject));
  charObjectTable[i].objClass = charClass;
  charObjectTable[i].charVal = i;
}

/*
 *	void fixupMetaclassObjects()
 *
 * Description
 *
 *	Called after the fundamental class hierarchy has been defined, this
 *	function goes through and fixes up all the objects in the oop table
 *	that don't have a objClass (objClass == nilOOP).  It's a
 *	chicken-and-egg problem: the metaclassClass doesn't yet exist when the
 *	hierarchy is put together, so after it's created, we have to go back
 *	and fix all the metaclasses that we created.
 *
 */
void fixupMetaclassObjects()
{
  int		i;

  for (i = 0; i < OOP_TABLE_SIZE; i++) {
    if (!(oopTable[i].flags & F_FREE) && isNil(oopTable[i].object->objClass)) {
      oopTable[i].object->objClass = metaclassClass;
    }
  }
}

/*
 *	OOP findAnInstance(classOOP)
 *
 * Description
 *
 *	Finds and returns an instance of the class CLASSOOP.  Returns "nil" if
 *	there are no instances present.
 *
 * Inputs
 *
 *	classOOP: 
 *		OOP for a class for which to find an instance
 *
 * Outputs
 *
 *	The first instance of the given class in the OOP table.
 */
OOP findAnInstance(classOOP)
OOP	classOOP;
{
  register OOP	oop;

  for (oop = oopTable; oop < &oopTable[OOP_TABLE_SIZE]; oop++) {
    if (!(oop->flags & F_FREE)
	&& oop->object->objClass == classOOP) {
      return (oop);
    }
  }

  return (nilOOP);
}

#ifndef INLINE_MACROS
/*
 *	long oopIndex(oop)
 *
 * Description
 *
 *	Returns the index within the OOP table of the given OOP.
 *
 * Inputs
 *
 *	oop   : OOP to return index of
 *
 * Outputs
 *
 *	Returned index in the OOP table, in range 0..TOTAL_OOP_TABLE_SLOTS.
 */
long oopIndex(oop)
OOP	oop;
{
  return (oopIndexMac(oop));
}
#endif /* INLINE_MACROS */

/*
 *	Boolean oopIndexValid(index)
 *
 * Description
 *
 *	Checks to see if index represents a valid OOP.
 *
 * Inputs
 *
 *	index : a long index into the OOP table, apparently 1 based due to
 *		being called from Smalltalk via a primitive.
 *
 * Outputs
 *
 *	True if the index represents a valid OOP table element, false
 *	otherwise.
 */
Boolean oopIndexValid(index)
long	index;
{
  return (index >= 1 && index <= TOTAL_OOP_TABLE_SLOTS);
}

#ifndef INLINE_MACROS

OOP oopAt(index)
long	index;
{
  return (oopAtMac(index));
}

void prepareToStore(destOOP, srcOOP)
OOP	destOOP, srcOOP;
{
  prepareToStoreMac(destOOP, srcOOP);
}

#endif  /* INLINE_MACROS */

void swapObjects(oop1, oop2)
OOP	oop1, oop2;
{
  struct OOPStruct tempOOP;

  tempOOP = *oop2;
  *oop2 = *oop1;
  *oop1 = tempOOP;
}

OOP charOOPAt(c)
Byte	c;
{
  return (&oopTable[c + CHAR_OBJECT_BASE]);
}

Byte charOOPValue(charOOP)
OOP	charOOP;
{
  return (charOOP - &oopTable[CHAR_OBJECT_BASE]);
}

void printObject(oop)
OOP	oop;
{
  if (isInt(oop)) {
    printf("%d", toInt(oop));
  } else if (isNil(oop)) {
    printf("nil");
  } else if (oop == trueOOP) {
    printf("true");
  } else if (oop == falseOOP) {
    printf("false");
  } else if (oopClass(oop) == charClass) {
    printf("$%c", charOOPValue(oop));
  } else if (oopClass(oop) == floatClass) {
    printf("%#g", floatOOPValue(oop));
  } else if (oopClass(oop) == symbolClass) {
    printf("#"); printSymbol(oop);
  } else if (oopClass(oop) == stringClass) {
    /* ### have to quote embedded quote chars */
    printf("'");
    printString(oop);
    printf("'");
  } else {
    printOOPConstructor(oop);
  }
}

void classifyAddr(addr)
void	*addr;
{
  if (isOOPAddr(addr)) {
    displayOOP(addr);
  } else if (isObjAddr(addr)) {
    displayObject(addr);
  } else if isInt(addr) {
    printf("Smalltalk Integer %d\n", toInt(addr));
  } else {
    printf("Address %#x is not a Smalltalk entity\n", addr);
  }
}

static Boolean isOOPAddr(addr)
OOP	addr;
{
  if (addr >= oopTable && addr < &oopTable[TOTAL_OOP_TABLE_SLOTS]) {
    if ((long)addr % 4 == 0) {
      return (true);
    }
  }

  return (false);
}


static Boolean isObjAddr(addr)
char	*addr;
{
  if ((addr >= spaces[0].space && addr < spaces[0].space + spaces[0].totalSize)
      || (addr >= spaces[1].space && addr < spaces[1].space + spaces[1].totalSize)) {
    if ((long)addr % 4 == 0) {
      return (true);
    }
  }

  return (false);
}
  
static void displayOOP(oop)
OOP	oop;
{
  Boolean	isBuiltin;
  
  if (!isOOPAddr(oop)) {
    printf("Parameter %#x does not appear to be an OOP!\n", oop);
    return;
  }

  isBuiltin = (oop >= &oopTable[OOP_TABLE_SIZE]) ? true : false;

  if (!isBuiltin) {
    printf ("OOP %#x [%d]\n", oop, oop - oopTable);
  }

  if (oop->flags & F_FREE) {
    printf("Free ");
  }
  printf("Space=%d ", (oop->flags & F_SPACE) ? 1 : 0);
  if (oop->flags & F_EVEN) {
    printf("Even ");
  }
  if (oop->flags & F_ODD) {
    printf("Odd ");
  }
  if (oop->flags & F_FAKE) {
    printf("Fake ");
  }

  printf("   Empty bytes = %d\n", oop->flags & EMPTY_BYTES);
  if (!(oop->flags & F_FREE)) {
    printObject(oop);
  }
  printf("\n");
}


static void displayObject(obj)
Object	obj;
{
  int		space;

  if (!isObjAddr(obj)) {
    printf("Parameter %#x does not appear to be an object!\n", obj);
    return;
  }
    
  space = objSpace(obj) != 0;
  printf("Object at %#x, in space %d (curSpace is %d), ", obj, space,
	 curSpace == &spaces[1]);
  if ((char *)obj >= spaces[space].allocPtr) {
    printf("allocated this GC pass\n");
  } else if ((char *)obj >= spaces[space].scanPtr) {
    printf("copied in this GC pass, not scanned\n");
  } else {
    printf("copied in this GC pass, scanned\n");
  }
  
  printf("Size %d\n", numOOPs(obj));
  printf("Class ");
  printObject(obj->objClass);
  printf("\n");
}


Boolean oopValid(oop)
OOP	oop;
{
  /* In the non-incremental GC world, being FREE is all that matters for
   * validity.
   */
  return (!(oop->flags & F_FREE));
#ifdef old_code /* Sun Dec  8 16:01:28 1991 */
/**/  return (!(oop->flags & F_FREE) && (oop->flags & (F_EVEN|F_ODD)) );
#endif /* old_code Sun Dec  8 16:01:28 1991 */
}

#ifndef INLINE_MACROS

Boolean oopAvailable(index)
long	index;
{
  return (oopAvailableMac(index));
}

#endif /* INLINE_MACROS */

/*
 *	OOP allocOOP(obj)
 *
 * Description
 *
 *	Given an object OBJ, this routine allocates an OOP table slot for it
 *	and returns it.  It marks the OOP so that it indicates the object is in
 *	new space, and that the oop has been referenced on this pass (to keep
 *	the OOP table reaper from reclaiming this OOP).
 *
 * Inputs
 *
 *	obj   : Object that the new OOP should point to.
 *
 * Outputs
 *
 *	An OOP, which is the address of an element in the OOP table.
 */
OOP allocOOP(obj)
Object	obj;
{
  register OOP		oop;

  for (oop = &oopTable[oopTableIndex];
       oop < &oopTable[OOP_TABLE_SIZE]; oop++) {
    if (oop->flags & F_FREE) {
      oopTableIndex = oop - oopTable + 1;

      numFreeOOPs--;
      /* !!! not sure if this is needed */
      if (objSpace(obj) != toSpace) {
/* dprintf("doing move for oldspace object in allocOOP\n"); */
	obj = moveObject(obj);
      }
      oop->object = obj;
      oop->flags = toSpace | evenOddFlag;
      return (oop);
    }
  }

  errorf("Ran out of OOP Table slots!!!");
  exit(1);
}

#ifdef preserved /* Sat Jul 27 16:48:12 1991 */
/**/OOP allocOOP(obj)
/**/Object	obj;
/**/{
/**/#ifndef NORMAL_ALLOC_OOP
/**/  register OOP		oop;
/**/  register int		i;
/**/
/**/  for (i = 0, oop = &oopTable[oopTableIndex];
/**/       i < oopReclaimFactor && oop < &oopTable[OOP_TABLE_SIZE]; i++, oop++) {
/**/    if (!(oop->flags & F_FREE)) {
/**/      if (!(oop->flags & (F_EVEN|F_ODD))) {
/**/	/* we've found a dead one...add it to the free list */
/**/	numFreeOOPs++;
/**/	oop->object = (Object)freeOOPs;
/**/	freeOOPs = oop;
/**/	freeOOPs->flags |= F_FREE;
/**/      } else {
/**/	/* turn off the bit for the space we're not in */
/**/	oop->flags &= ~(evenOddFlag ^ EVEN_ODD_MASK);
/**/      }
/**/    }
/**/  }
/**/
/**/  oopTableIndex = oop - oopTable;
/**/
/**/#else
/**/  OOP		oop;
/**/  register int		i;
/**/
/**/  for (i = 0; i < oopReclaimFactor; i++) {
/**/    if (oopTableIndex >= OOP_TABLE_SIZE) {
/**/      break;
/**/    }
/**/    oop = &oopTable[oopTableIndex++];
/**/    if (!oop->isFree) {
/**/      if (!oop->evenMark && !oop->oddMark) {
/**/	/* we've found a dead one...add it to the free list */
/**/	numFreeOOPs++;
/**/	oop->object = (Object)freeOOPs;
/**/	freeOOPs = oop;
/**/	freeOOPs->isFree = true;
/**/      } else if (toSpace) {
/**/	oop->evenMark = 0;
/**/      } else {
/**/	oop->oddMark = 0;
/**/      }
/**/    }
/**/  }
/**/#endif
/**/
/**/  oop = freeOOPs;
/**/
/**/  numFreeOOPs--;
/**/
/**/  if (oop == nil) {
/**/    errorf("Ran out of OOP Table slots!!!");
/**/    exit(1);
/**/    /* ### this needs to be fixed */
/**/  }
/**/
/**/  if (!(oop->flags & F_FREE)) {
/**/    errorf("Allocating allocated OOP!!!");
/**/    exit(0);
/**/  }
/**/
/**/  freeOOPs = (OOP)oop->object;
/**/
/**/  if (objSpace(obj) != toSpace) {
/**/    obj = moveObject(obj);
/**/  }
/**/
/**/
/**/  oop->object = obj;
/**/  oop->flags = toSpace | evenOddFlag;
/**/
/**/  return (oop);
/**/}
#endif /* preserved Sat Jul 27 16:48:12 1991 */


/*
 *	void setOOPObject(oop, object)
 *
 * Description
 *
 *	Sets the object of OOP to be OBJECT.  Makes sure that the object is in
 *	new space before it assigns it to OOP.
 *
 * Inputs
 *
 *	oop   : an OOP table entry to be assigned into
 *	object: an object that the OOP should point to.
 *
 */
void setOOPObject(oop, object)
OOP	oop;
Object	object;
{
#ifndef OPTIMIZE
  if (isFake(oop)) {
    printf("found a fake oop %x\n", oop);
    debug();
  }
#endif /* !OPTIMIZE */

  if (objSpace(object) != toSpace) {
/* dprintf("doing move for oldspace object in setOOPObject\n"); */
    object = moveObject(object);
  }
  oop->object = object;
  oop->flags = (oop->flags & ~F_SPACE) | toSpace;
}

/*
 *	void initMem()
 *
 * Description
 *
 *	Initialize the memory allocator.  Both semispaces are allocated, and
 *	the various garbage collection flags are set to their initial values.
 *
 */
void initMem()
{
  int		i;

  maxSpaceSize = INIT_MEM_SPACE_SIZE;

  for (i = 0; i < 2; i++) {
    spaces[i].space = (char *)malloc(maxSpaceSize);
    spaces[i].totalSize = maxSpaceSize;
    spaces[i].percentUsed = 0.0;
    if (spaces[i].space == NULL) {
      printf("Malloc failure; you're out of paging/swapping space\n");
      exit(1);
    }
    initSpace(&spaces[i]);
  }

  curSpace = &spaces[0];
  toSpace = 0;
#ifdef bogus_old_code /* Sat Oct 13 15:37:50 1990 */
/**/  fromSpace = !toSpace;
#endif /* bogus_old_code Sat Oct 13 15:37:50 1990 */
  fromSpace = F_SPACE;
  evenOddFlag = F_EVEN;
  gcFlipped = false;
  gcState = false;
  gcMessage = true;
#ifdef preserved /* Sat Aug  3 18:48:45 1991 */
/**/  copyRate = 0.0;		/* don't copy anything until first flip */
/**/  copyQuota = 0;
#endif /* preserved Sat Aug  3 18:48:45 1991 */
  oopTableIndex = 0;
#ifdef testing_out /* Sat Feb 29 10:55:51 1992 */
/**/  markBuiltinOOPs();
#endif /* testing_out Sat Feb 29 10:55:51 1992 */
  clearGCFlipFlags();
}

Object curSpaceAddr()
{
  return ((Object)curSpace->space);
}

void setSpaceInfo(size)
long	size;
{
  curSpace->copyPtr = curSpace->scanPtr = curSpace->space + size;
  curSpace->size -= size;
}

#ifndef INLINE_MACROS

void clearGCFlipFlags()
{
  clearGCFlipFlagsMac();
}

#endif /* INLINE_MACROS */

/*
 *	Object allocObj(size)
 *
 * Description
 *
 *	Allocate and return space for an object of SIZE bytes.  This basically
 *	means moving the allocation pointer for the current space down by SIZE
 *	bytes, and, if there isn't enough space left, flipping the garbage
 *	collector to switch semispaces.  The space is merely allocated; it is
 *	not initialized.
 *
 * Inputs
 *
 *	size  : size in bytes of the object to allocate.  This will be rounded
 *		by this routine up to a suitable boundary, typically to a 4
 *		byte boundary.
 *
 * Outputs
 *
 *	Address of the newly allocated object.
 */
Object allocObj(size)
long	size;
{
  size = alignSize(size);

#ifdef preserved /* Sat Jul 27 16:43:00 1991 */
/**/  copyReferencedObjects((long)(size * copyRate));
#endif /* preserved Sat Jul 27 16:43:00 1991 */
  curSpace->allocPtr -= size;
  while (curSpace->allocPtr <= curSpace->copyPtr) {
    gcFlip();
    curSpace->allocPtr -= size;
  }

#ifdef preserved /* Fri Oct 18 21:46:22 1991 */
/**/  if (curSpace->allocPtr <= curSpace->copyPtr) {
/**/    gcFlip();
/**/    curSpace->allocPtr -= size;
/**/  }
#endif /* preserved Fri Oct 18 21:46:22 1991 */
    
  return ((Object)curSpace->allocPtr);
}


/*
 *	static void copyReferencedObjects()
 *
 * Description
 *
 *	This is the heart of the garbage collector.  It fully scans all objects
 *	in new space, copying object that it finds that are still in old space
 *	to new space and adding them to the set of objects to be scanned.
 *	It has to special case
 *	CompiledMethod objects due to their unusual structure.
 *	
 */
static void copyReferencedObjects()
{
  Object	object;
  register OOP	curClass, *oop;
  int		stepSize, count;
  Method	method;
  register int	i;

  while (curSpace->scanPtr < curSpace->copyPtr) { /* there's more to scan */
    /* if there is no current object, start off with the object's class */
    object = (Object)curSpace->scanPtr;
    curClass = object->objClass;
      
#ifdef debugging /* Mon Oct 28 12:29:31 1991 */
/**/  if (curClass == stringClass) {
/**/    fwrite(object->data, sizeof(Byte), numOOPs(object) * sizeof(OOP), stdout);
/**/    fflush(stdout);
/**/    printf("\n----------------------------------------\n");
/**/  }
#endif /* debugging Mon Oct 28 12:29:31 1991 */


/*    { extern Boolean gcDebug;
      if (gcDebug) {
 	dprintf("copying at %#8x, size %d\n", curSpace->scanPtr, object->objSize);
      }
    }
*/

    /* don't need to do the isInt test for this case */
    localMaybeMoveOOP(curClass);
      
    stepSize = object->objSize * sizeof(OOP);
    if (curClass == compiledMethodClass) {
      /* Compiled methods have to be dealt with specially since they
       * have a structure that's unlike a regular Smalltalk object:
       * it has two fixed instance variables (description and 
       * methodHeader), a variable number of literals, and then
       * a bunch of bytecodes, which must be skipped over */
      method = (Method)object;
      localMaybeMoveOOP(method->descriptor);
      if (method->header.headerFlag == 0 || method->header.headerFlag == 3) {
	count = method->header.numLiterals;
	for (i = 0; i < count; i++) {
	  localMaybeMoveOOP(method->literals[i]);
	}
      }

    } else if (!classIsPointers(curClass)) {
      /* nothing to scan, just skip over it */
    } else {
      /* we've got an object with sub structure, so we set the object
       * size pointer to 2 words less than the object size (ignore
       * the header; we've already copied the class) and set the
       * scan pointer to the first word of the object.  We then continue
       * to go through the normal scanning procedure (fall out the
       * bottom of the if and go back to the top of the loop again)
       */
      count = numOOPs(object);
      for (i = 0, oop = object->data; i < count; i++, oop++) {
	localMaybeMoveOOP(*oop);
      }
      
    }
    curSpace->scanPtr += stepSize;
  }
}

#ifdef preserved /* Sat Jul 27 17:06:09 1991 */
/**/static void copyReferencedObjects(numBytes)
/**/long	numBytes;
/**/{
/**/  Object	object;
/**/  OOP		curClass;
/**/  int		stepSize, i;
/**/  Method	method;
/**/
/**/  copyQuota += numBytes;
/**/  while (curSpace->scanPtr < curSpace->copyPtr
/**/	 && copyQuota > 0) { /* there's more to scan */
/**/    if (curObject == nil) {
/**/      /* if there is no current object, start off with the object's class */
/**/      object = (Object)curSpace->scanPtr;
/**/      curClass = object->objClass;
/**/      maybeMoveOOP(curClass);
/**/      
/**/      if (curClass == compiledMethodClass) {
/**/	/* Compiled methods have to be dealt with specially since they
/**/	 * have a structure that''s unlike a regular Smalltalk object:
/**/	 * it has two fixed instance variables (description and 
/**/	 * methodHeader), a variable number of literals, and then
/**/	 * a bunch of bytecodes, which must be skipped over */
/**/	stepSize = object->objSize * sizeof(OOP);
/**/	method = (Method)object;
/**/	maybeMoveOOP(method->descriptor);
/**/	if (method->header.headerFlag == 0 || method->header.headerFlag == 3) {
/**/	  for (i = 0; i < method->header.numLiterals; i++) {
/**/	    maybeMoveOOP(method->literals[i]);
/**/	  }
/**/	}
/**/
/**/	curSpace->scanPtr += stepSize;
/**/	copyQuota -= stepSize;
/**/      } else if (!classIsPointers(curClass)) {
/**/	/* nothing to scan, just skip over it */
/**/	stepSize = object->objSize * sizeof(OOP);
/**/	curSpace->scanPtr += stepSize;
/**/	copyQuota -= stepSize;
/**/      } else {
/**/	/* we've got an object with sub structure, so we set the object
/**/	 * size pointer to 2 words less than the object size (ignore
/**/	 * the header; we''ve already copied the class) and set the
/**/	 * scan pointer to the first word of the object.  We then continue
/**/	 * to go through the normal scanning procedure (fall out the
/**/	 * bottom of the if and go back to the top of the loop again)
/**/	 */
/**/	curObject = object;
/**/	curObjectSize = numOOPs(curObject) * sizeof(OOP);
/**/	curSpace->scanPtr = (char *)curObject->data;
/**/      }
/**/
/**/    } else {
/**/      /* we're part way through scanning an object, continue to scan... */
/**/      if (curObjectSize <= 0) {
/**/	curObject = nil;
/**/      } else {
/**/	maybeMoveOOP(*(OOP *)curSpace->scanPtr);
/**/	stepSize = sizeof(OOP);
/**/	curSpace->scanPtr += stepSize;
/**/	curObjectSize -= stepSize;
/**/	copyQuota -= stepSize;
/**/      }
/**/    }
/**/  }
/**/
/**/  if (copyQuota < 0) {
/**/    copyQuota = 0;
/**/  }
/**/}
/**/
#endif /* preserved Sat Jul 27 17:06:09 1991 */
/*
 *	Boolean gcOff()
 *
 * Description
 *
 *	Turns off the garbage collector.  Returns the previous on/off state.
 *
 * Outputs
 *
 *	Previous state of the garbage collector (on or off).
 */
Boolean gcOff()
{
  Boolean	oldGCState;

  oldGCState = gcState;
  gcState = false;
  return (oldGCState);
}

/*
 *	void gcOn()
 *
 * Description
 *
 *	Turns on the garbage collector.
 *
 */
void gcOn()
{
  gcState = true;
}

/*
 *	void setGCState(state)
 *
 * Description
 *
 *	Set the garbage collector flag to the specified state (either on or
 *	off).
 *
 * Inputs
 *
 *	state : Boolean, true => gc on.
 *
 */
void setGCState(state)
Boolean	state;
{
  gcState = state;
}


/*
 *	static void clearOldOOPs()
 *
 * Description
 *
 *	Scans through the OOP table, removing OOPS that have died.  Only
 *	called at the end of a full GC to remove any stragglers.
 *
 */
static void clearOldOOPs()
{
  register OOP	oop;

  for (oop = oopTable; oop<&oopTable[OOP_TABLE_SIZE]; oop++) {
    if (!(oop->flags & F_FREE)) {
      if (!(oop->flags & evenOddFlag)) {
	/* we've found a dead one...add it to the free list */
	numFreeOOPs++;
	oop->flags = F_FREE;
      } else {
	 /* !!! may not want to clear completey to 0? */
	oop->flags &= ~EVEN_ODD_MASK;
      }
    }
  
#ifdef pre_sc_gc /* Sat Jul 27 17:33:01 1991 */
/**/  for (oop = &oopTable[oopTableIndex]; oop<&oopTable[OOP_TABLE_SIZE]; oop++) {
/**/    if (!(oop->flags & F_FREE)) {
/**/      if (!(oop->flags & evenOddFlag)) {
/**/	/* we've found a dead one...add it to the free list */
/**/	numFreeOOPs++;
/**/	oop->object = (Object)freeOOPs;
/**/	freeOOPs = oop;
/**/	freeOOPs->flags |= F_FREE;
/**/      } else {
/**/	/* turn off the bit for the space we're not in  */
/**/	oop->flags &= ~(evenOddFlag ^ EVEN_ODD_MASK);
/**/      }
/**/    }
/**/  
#endif /* pre_sc_gc Sat Jul 27 17:33:01 1991 */
  }
}

/*
 *	void gcFlip()
 *
 * Description
 *
 *	Switches the garbage collector's notion of which space is "new" space
 *	and which is "old" space.  Readjusts the garbage collection parameters
 *	based on things like the allocation to copying ratio.  Copies the root
 *	set to new space.
 *
 */
void gcFlip()
{
  long		oldCopySize, oldNewSize;
  double	lastPercent;

  if (!gcState) {
    errorf("Attempted to do a gcFlip with garbage collector off!");
    exit(1);
  }

#ifndef OPTIMIZE
  if (gcFlipCounter >= 1) {
    errorf("Attempted to do a gcFlip too soon after a gcFlip!");
    exit(1);
  }
#endif


#ifdef OOP_DEBUGGING
  printf("%d free oops = %.2f%%, scanner was at %d/%d\n", numFreeOOPs,
       100.0 * numFreeOOPs / OOP_TABLE_SIZE, oopTableIndex, OOP_TABLE_SIZE);
#endif

  if (gcMessage && !regressionTesting) {
    /* print the first part of this message before we finish scanning 
     * oop table for live ones, so that the delay caused by this scanning
     * is apparent.
     */
    printf("\"GC flipping "); fflush(stdout);
  }

  oldCopySize = curSpace->copyPtr - curSpace->space;
  oldNewSize = curSpace->space + curSpace->totalSize - curSpace->allocPtr;

if (oldCopySize == 0) {  /* ### Experimental */
  oldCopySize = oldNewSize;
}

  lastPercent = curSpace->percentUsed;

#ifdef preserved /* Sat Jul 27 17:30:33 1991 */
/**/  copyRate = ((double)oldCopySize) / oldNewSize;
/**/  copyRate *= copyRateAdjustment;
#endif /* preserved Sat Jul 27 17:30:33 1991 */

  toSpace ^= F_SPACE;
  fromSpace ^= F_SPACE;
  evenOddFlag ^= EVEN_ODD_MASK;	/* switch which bit is in use */
  curSpace = &spaces[boolSpace(toSpace)];
#ifdef pre_full_gc /* Sun Aug  4 19:24:21 1991 */
/**/  curObject = nil;
#endif /* pre_full_gc Sun Aug  4 19:24:21 1991 */
#ifdef preserved /* Sat Aug  3 18:49:00 1991 */
/**/  copyQuota = 0;
#endif /* preserved Sat Aug  3 18:49:00 1991 */
  oopTableIndex = 0;

  if (lastPercent > growThresholdPercent) {
    maxSpaceSize *= 1.0 + spaceGrowRate/100.0;
    maxSpaceSize &= ~3;		/* round to word boundary */
  }
  
  initSpace(curSpace);

  moveRootOOPs();
  copyReferencedObjects(); /* copy what we can */
  clearOldOOPs();

  curSpace->percentUsed = (curSpace->scanPtr - curSpace->space) * 100.0
    / curSpace->totalSize; 

  /* if oldCopySize / size of space > threshold
        allocate new old space
     */

  /* note the use of quotation marks around the printed message.  The
     idea here was to make them appear as Smalltalk comments, so that 
     generated output could be fed to another Smalltalk without harm. */
  if (gcMessage && !regressionTesting) {
    printf("to space %d...", boolSpace(toSpace)); fflush(stdout);
    printf("copied space = %.1f%%...", curSpace->percentUsed);
#ifdef pre_full_gc /* Sun Aug  4 20:09:15 1991 */
/**/    printf("copied space = %.1f%%...", oldCopySize * 100.0 / MEM_SPACE_SIZE);
#endif /* pre_full_gc Sun Aug  4 20:09:15 1991 */
  }
  if (gcMessage && !regressionTesting) {
    printf("done\"\n");
  }
  gcFlipped = true;
}


#ifdef preserved /* Sat Jul 27 16:37:34 1991 */
/**/void gcFlip()
/**/{
/**/  long		oldCopySize, oldNewSize;
/**/
/**/  if (!gcState) {
/**/    errorf("Attempted to do a gcFlip with garbage collector off!");
/**/    exit(1);
/**/  }
/**/
/**/  if (gcFlipCounter >= 1) {
/**/    errorf("Attempted to do a gcFlip too soon after a gcFlip!");
/**/    exit(1);
/**/  }
/**/
/**/
/**/#ifdef OOP_DEBUGGING
/**/  printf("%d free oops = %.2f%%, scanner was at %d/%d\n", numFreeOOPs,
/**/       100.0 * numFreeOOPs / OOP_TABLE_SIZE, oopTableIndex, OOP_TABLE_SIZE);
/**/#endif
/**/
/**/  if (gcMessage && !regressionTesting) {
/**/    /* print the first part of this message before we finish scanning 
/**/     * oop table for live ones, so that the delay caused by this scanning
/**/     * is apparent.
/**/     */
/**/    printf("\"GC flipping "); fflush(stdout);
/**/  }
/**/
/**/  finishOOPScan();		/* make sure we're done */
/**/
/**/  oldCopySize = curSpace->copyPtr - curSpace->space;
/**/  oldNewSize = curSpace->space + MEM_SPACE_SIZE - curSpace->allocPtr;
/**/
/**/if (oldCopySize == 0) {  /* ### Experimental */
/**/  oldCopySize = oldNewSize;
/**/}
/**/
/**/
/**/  copyRate = ((double)oldCopySize) / oldNewSize;
/**/  copyRate *= copyRateAdjustment;
/**/
/**/  toSpace ^= F_SPACE;
/**/  fromSpace ^= F_SPACE;
/**/  evenOddFlag ^= EVEN_ODD_MASK;	/* switch which bit is in use */
/**/#ifdef bogus /* Sat Oct 13 15:38:55 1990 */
/**//**/  toSpace = !toSpace;
/**//**/  fromSpace = !fromSpace;
/**/#endif /* bogus Sat Oct 13 15:38:55 1990 */
/**/  curSpace = &spaces[boolSpace(toSpace)];
/**/  curObject = nil;
/**/  copyQuota = 0;
/**/  oopTableIndex = 0;
/**/  initSpace(curSpace);
/**/
/**/#ifdef remove_soon /* Mon May 14 23:41:55 1990 */
/**//**/zeroMarks();
/**/#endif /* remove_soon Mon May 14 23:41:55 1990 */
/**/
/**/  /* note the use of quotation marks around the printed message.  The
/**/     idea here was to make them appear as Smalltalk comments, so that 
/**/     generated output could be fed to another Smalltalk without harm. */
/**/  if (gcMessage && !regressionTesting) {
/**/    printf("to space %d...", boolSpace(toSpace)); fflush(stdout);
/**/    printf("copied space = %.1f%%...", oldCopySize * 100.0 / MEM_SPACE_SIZE);
/**/  }
/**/  moveRootOOPs();
/**/  if (gcMessage && !regressionTesting) {
/**/    printf("done\"\n");
/**/  }
/**/  gcFlipped = true;
/**/}
#endif /* preserved Sat Jul 27 16:37:34 1991 */

/*
 *	static void moveRootOOPs()
 *
 * Description
 *
 *	Copies the root objects from old space to new space.  All of the root
 *	objects are those that are mentioned in the set of objects that are
 *	known specially by the interpreter, those that are being used by the
 *	interpreter, and some information about compilation state.  Also, the
 *	built-in oops (Characters, nil, true, false) are marked as being in new
 *	space so that they won't ever be moved.
 *
 */
static void moveRootOOPs()
{
  OOP		**oopPtr, oop;

  markBuiltinOOPs();

  /* copy objects that have global pointers */
  for (oopPtr = globalOOPs; *oopPtr; oopPtr++) {
    oop = **oopPtr;
    localMaybeMoveOOP(oop);		/* use the maybe form here so that we don't
				 * accidentally move builtins, which have
				 * already been marked as being in the new
				 * space
				 */
  }

  moveProcessorRegisters();

  copyRegisteredOOPs();

  copyCompileContext();
}

/*
 *	static void markBuiltinOOPs()
 *
 * Description
 *
 *	Marks all of the builtin OOPS (nil, true, false, and the Characters) as
 *	being in the current new space.
 *
 */
static void markBuiltinOOPs()
{
  register OOP	oop;


  for (oop = &oopTable[OOP_TABLE_SIZE]; oop < &oopTable[TOTAL_OOP_TABLE_SLOTS];
       oop++) {
    oop->flags = (oop->flags & ~F_SPACE) | toSpace | F_EVEN | F_ODD;
  }
}

/*
 *	static void initSpace(space)
 *
 * Description
 *
 *	Initializes the allocation and copying pointers for semispace SPACE.
 *
 * Inputs
 *
 *	space : Semispace index number.
 *
 */
static void initSpace(space)
struct memorySpaceStruct *space;
{
  char		*oldSpace;
  if (space->totalSize < maxSpaceSize) {
    oldSpace = space->space;
    space->space = (char *)realloc(oldSpace, maxSpaceSize);
    if (space->space == NULL) {
      space->space = oldSpace;
      errorf("Could not grow space to %d", maxSpaceSize);
      /* ??? Should print some kind of warning here, like we can't reallocate
       * more space */
    } else {
      space->totalSize = maxSpaceSize;
    }
  }

  space->copyPtr = space->scanPtr = space->space;
  space->size = space->totalSize;
  space->allocPtr = space->space + space->size;
}


void growBothSpaces(newSize)
unsigned long newSize;
{
  int		i;

  maxSpaceSize = newSize;

  for (i = 0; i < 2; i++) {
    initSpace(&spaces[i]);
  }
}

#ifndef INLINE_MACROS

/*
 *	void maybeMoveOOP(oop)
 *
 * Description
 *
 *	Move OOP to new space if it's not already there.
 *
 * Inputs
 *
 *	oop   : OOP to be examined, and, if it's in old space, moved to new
 *		space.
 *
 */
void maybeMoveOOP(oop)
OOP	oop;
{
  maybeMoveOOPMac(oop);
}

#endif /* INLINE_MACROS */


/*
 *	void moveOOP(oop)
 *
 * Description
 *
 *	Moves an OOP from old space to new space unconditionally.  Basically
 *	marks the OOP as being in the current new space, copies the object that
 *	the oop points to to new space, and sets the even/odd flags to keep the
 *	OOP table garbage collector from reaping this OOP.
 *
 * Inputs
 *
 *	oop   : OOP to be moved.  Should always be in OLD space.
 *
 */
void moveOOP(oop)
OOP	oop;
{
  Object	object;

#ifndef OPTIMIZE
  if (isFake(oop)) {
    printf("moving fake object!!! %x\n", oop);
    debug();
  }
#endif /* !OPTIMIZE */

  object = oopToObj(oop);
  /* !!! this F_SPACE and TO_SPACE stuff should go */
  oop->flags = (oop->flags & ~F_SPACE) | toSpace | evenOddFlag;
  oop->object = moveObject(object);
}

/*
 *	static Object moveObject(object)
 *
 * Description
 *
 *	Copies OBJECT from old space to new space.  Adjusts the garbage
 *	collectors pointers to indicate that the object has been added to new
 *	space so that the scanner will see it.
 *
 * Inputs
 *
 *	object: Object to be moved to new space.
 *
 */
static Object moveObject(object)
Object	object;
{
  long		size;

/* dprintf("moving %8x, size %d\n", curSpace->copyPtr, object->objSize); */
if ((long) object->objSize  >= 1000) {
  debug();
}
  size = object->objSize * sizeof(OOP);
  memcpy(curSpace->copyPtr, object, size);
  object = (Object)curSpace->copyPtr;
  curSpace->copyPtr += size;
  curSpace->size -= size;
  if (curSpace->copyPtr >= curSpace->allocPtr) {
    errorf("Garbage collector failed...ran out of room while copying!!!");
    exit(0);
  }

  return (object);
}

#ifdef pre_full_gc /* Sun Aug  4 19:31:08 1991 */
/**//*
/**/ *	void printFreeList()
/**/ *
/**/ * Description
/**/ *
/**/ *	Debugging support routine.  Prints the free list.  Meant only to be
/**/ *	called from a debugger.
/**/ *
/**/ */
/**/void printFreeList()
/**/{
/**/  OOP		oop;
/**/  for (oop = freeOOPs; oop != nil; oop = (OOP)oop->object) {
/**/    printf("oop %x\n", oop);
/**/    printf("oop isfree %d\n", (oop->flags & F_FREE) != 0);
/**/  }
/**/}
#endif /* pre_full_gc Sun Aug  4 19:31:08 1991 */

/*
 *	debug()
 *
 * Description
 *
 *	Used for debugging.  You set a breakpoint in the debug routine in the
 *	debugger, and have code call it when you want it to stop.  Performs no
 *	action normally.
 *
 */
debug()
{
}


#ifdef remove_soon /* Mon May 14 23:42:01 1990 */
/**/static void zeroMarks()
/**/{
/**/  char	*p;
/**/
/**/  for (p = marks; p < &marks[TOTAL_OOP_TABLE_SLOTS]; ) {
/**/    *p++ = 0;
/**/  }
/**/}
#endif /* remove_soon Mon May 14 23:42:01 1990 */
