/* Copyright 1990 - 1996 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_mman[] =
"$Id: mman.c 88 2005-05-25 03:59:37Z ctm $";
#endif
/* Implementation of MAC memory manager routines */

/* Forward declarations in MemoryMgr.h (DO NOT DELETE THIS LINE) */

#include "rsys/common.h"
#include "MemoryMgr.h"
#include "SegmentLdr.h"
#include "QuickDraw.h"
#include "SysErr.h"
#include "TextEdit.h"
#include "OSEvent.h"

#include "rsys/mman_private.h"
#include "rsys/file.h"
#include "rsys/smash.h"
#include "rsys/memory_layout.h"
#include "rsys/hook.h"
#include "rsys/memsize.h"
#include "rsys/executor.h"
#include "rsys/options.h"
#include "rsys/toolutil.h"
#include "rsys/gestalt.h"

#if defined (MSDOS)
#include "dpmilock.h"
#endif

#if defined (LINUX)
#include <sys/mman.h>
#endif /* LINUX */

#if defined(NEXT) && !defined (STRICT_OPENSTEP)
#include <mach/mach_error.h>
#endif

int ROMlib_applzone_size = DEFAULT_APPLZONE_SIZE;
int ROMlib_syszone_size  = DEFAULT_SYSZONE_SIZE;
int ROMlib_stack_size    = DEFAULT_STACK_SIZE;

/* these two variables define, in ROMlib space, the beginning of mac-memory
   and the end of mac memory.  They're purpose is to try to prevent routines
   like DisposHandle () from crashing when passed a bogus pointer.
   Specifically, I know an application that picks up 4 bytes from low-memory
   global 0x100 and then calls DisposHandle () on it.  That location contains
   0xFFFF0048 both here and on a Mac.  On a Mac, this doesn't cause a crash.
   */

PUBLIC unsigned long ROMlib_syszone;
PUBLIC unsigned long ROMlib_memtop;

#if defined (MM_MANY_APPLZONES)
/* for debugging, we can have multiple applzones which are used
   roundrobin */
int mm_n_applzones = 1;
static int mm_current_applzone;
#endif

/* for routines that simply set MemErr */
#define SET_MEM_ERR(err)			\
  do {						\
    GEN_MEM_ERR (err);				\
    MemErr = CWV (err);				\
  } while (FALSE)

SignedByte
hlock_return_orig_state (Handle h)
{
  block_header_t *block;
  SignedByte state;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return 0;
    }
  
  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return 0;
    }

  state = HANDLE_STATE (h, block);
  SET_HANDLE_STATE (h, block, state | LOCKBIT);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
  return state;
}

Size
zone_size (THz zone)
{
  return (char *) ZONE_BK_LIM (zone) - (char *) zone;
}

SignedByte
HGetState (Handle h)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  /* there used to be a spewy check here that returned noErr (zero
     state) if the incoming handle address was `spewy' (less than
     0x2000 or between the end of the applzone and the current stack
     frame.  it got axed */
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {    
      SET_MEM_ERR (nilHandleErr);
      return nilHandleErr;
    }

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return memWZErr;
    }
  
  SET_MEM_ERR (noErr);
  return HANDLE_STATE (h, block);
}

void
HSetState (Handle h, SignedByte flags)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }
  
  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }
  
  SET_HANDLE_STATE (h, block, flags);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

void
HLock (Handle h)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }

  SET_HANDLE_STATE (h, block, HANDLE_STATE (h, block) | LOCKBIT);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

void
HUnlock (Handle h)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }

  SET_HANDLE_STATE (h, block, HANDLE_STATE (h, block) & ~LOCKBIT);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

void
HPurge (Handle h)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }

  SET_HANDLE_STATE (h, block, HANDLE_STATE (h, block) | PURGEBIT);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

void
HNoPurge (Handle h)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }

  SET_HANDLE_STATE (h, block, HANDLE_STATE (h, block) & ~PURGEBIT);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

void
HSetRBit (Handle h)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }

  SET_HANDLE_STATE (h, block, HANDLE_STATE (h, block) | RSRCBIT);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

void
HClrRBit (Handle h)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }

  SET_HANDLE_STATE (h, block, HANDLE_STATE (h, block) & ~RSRCBIT);
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}


/* Zone sizes will be zero modulo this number (which must be a power of 2). */
#define ZONE_ALIGN_SIZE 8192

static void
pin_and_align (int *vp, int min, int max)
{
  int v = *vp;

  v = (v + ZONE_ALIGN_SIZE - 1) & ~(ZONE_ALIGN_SIZE - 1);
  
  if (v < min)
    v = min;
  else if (v > max)
    v = max;

  *vp = v;
}

static void
canonicalize_memory_sizes (void)
{
  pin_and_align (&ROMlib_applzone_size, MIN_APPLZONE_SIZE, MAX_APPLZONE_SIZE);
  pin_and_align (&ROMlib_syszone_size,  MIN_SYSZONE_SIZE,  MAX_SYSZONE_SIZE);
  pin_and_align (&ROMlib_stack_size,    MIN_STACK_SIZE,    MAX_STACK_SIZE);
}


void
InitApplZone (void)
{
  /* ApplZone must already be set before getting here */

  /* nisus writer demands that `ApplLimit - ZONE_BK_LIM (ApplZone)' be
     greater than (or equal to?) 16384 */
#define APPLZONE_SLOP		16384

  canonicalize_memory_sizes ();

#define INIT_APPLZONE_SIZE \
  (ROMlib_applzone_size + APPLZONE_SLOP)
  
  HeapEnd = (Ptr) RM ((char *) MR (ApplZone)
		      + INIT_APPLZONE_SIZE
		      - (MIN_BLOCK_SIZE + APPLZONE_SLOP));
  
  InitZone (0, 64, (Ptr) HEAPEND, (Zone *) MR (ApplZone));
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

#define MANDELSLOP      (32L * 1024)


void
print_mem_full_message (void)
{
  fprintf (stderr,
	   "Executor has run out of memory.  Try specifying "
	   "a smaller -memory size.\n");
#if defined (MSDOS)
  fprintf (stderr,
	   "Under Windows and Warp, try increasing the DPMI memory "
	   "available to Executor.\n");
#endif
}

void
ROMlib_InitZones (offset_enum which)
{
  static boolean_t beenhere = FALSE;
  static Ptr stack_begin, stack_end;
  static unsigned long applzone_memory_segment_size;
  int init_syszone_size;
  
#if ERROR_SUPPORTED_P (ERROR_MEMORY_MANAGER_SLAM)
  /* Temporarily turn off heap slamming while the heap is being set up. */
  boolean_t old_debug_enabled_p;
  old_debug_enabled_p = error_set_enabled (ERROR_MEMORY_MANAGER_SLAM, FALSE);
#endif
  
#define INIT_SYSZONE_SIZE \
  (ROMlib_syszone_size)

  if (!beenhere)
    {
      char *memory;
      unsigned long total_allocated_memory, total_mac_visible_memory;
      Ptr mem_top;
      
      canonicalize_memory_sizes ();
      
#define STACK_SIZE ROMlib_stack_size
      
#if defined (MM_MANY_APPLZONES)
      applzone_memory_segment_size = (mm_n_applzones * INIT_APPLZONE_SIZE);
#else /* !MM_MANY_APPLZONES */
      applzone_memory_segment_size = INIT_APPLZONE_SIZE;
#endif /* !MM_MANY_APPLZONES */      
      
      /* Determine total allocated memory.  Round up to next 8K
       * since that's the page size we pretend to have.
       */
      total_mac_visible_memory = (INIT_SYSZONE_SIZE
				  + INIT_APPLZONE_SIZE
				  + STACK_SIZE);
      total_mac_visible_memory = (total_mac_visible_memory + 8191) & ~8191;
      
      total_allocated_memory = (INIT_SYSZONE_SIZE
				+ applzone_memory_segment_size
				+ STACK_SIZE);
      total_allocated_memory = (total_allocated_memory + 8191) & ~8191;
      
      /* Note the memory in gestalt, rounded down the next 8K page multiple. */
      gestalt_set_memory_size (total_mac_visible_memory);
      
      /* Allocate memory for SysZone, ApplZone, and stack, contiguously. */
      memory = NULL;

#if defined (TRY_TO_MMAP_ZONES)
      /* Try to mmap if either malloc failed or bits are set in the high
       * byte of any address in the space just allocated.
       */
      if (memory == NULL)
	memory = mmap_permanent_memory (total_allocated_memory);
#endif /* TRY_TO_MMAP_ZONES */

#if defined (SBRK_PERMANENT_MEMORY)
      if (memory == NULL)
	{
	  memory = sbrk (total_allocated_memory);
	  if (memory == (char *) -1)
	    memory = NULL;
	}
#endif /* SBRK_PERMANENT_MEMORY) */

      /* Allocate with malloc if we don't have it yet. */
      if (memory == NULL)
	memory = malloc (total_allocated_memory);

      if (memory == NULL)
	{
	  print_mem_full_message ();
	  exit (-1);
	}

      /* can't assign to low-memory globals yet */

      mem_top = (Ptr) RM (memory + total_allocated_memory);
      
      stack_begin = ((Ptr) memory
		     + INIT_SYSZONE_SIZE
		     + applzone_memory_segment_size);
      stack_end = stack_begin + STACK_SIZE;

      init_syszone_size = INIT_SYSZONE_SIZE;
      
      switch (which)
	{
	case offset_none:
	  ROMlib_offset = 0;
	  break;
	case offset_8k:
	  ROMlib_offset = 8192;
	  break;
	case offset_big:
	  ROMlib_offset = (uint32) memory;
	  {
	    int low_global_room = (char *) &lastlowglobal -
	                          (char *) &nilhandle_H;
#if defined (MSDOS)
	    dpmi_lock_memory (&nilhandle_H, low_global_room);
#endif
	    memory += low_global_room;
	    init_syszone_size -= low_global_room;
	  }
	  break;
	default:
	  /* shouldn't get here */
	  break;
	}

      MemTop = mem_top;

      SysZone = (void *) RM (memory);
      ROMlib_syszone = (unsigned long) memory;
      ROMlib_memtop = (unsigned long) (memory + total_allocated_memory);
      InitZone (0, 32, (Ptr) ((long) MR (SysZone) + init_syszone_size),
		(Zone *) MR (SysZone));
      beenhere = TRUE;
    }
  
#if defined (MM_MANY_APPLZONES)
  if (mm_n_applzones != 1)
    {
      if (munmap ((char *) MR (SysZone)
		  + INIT_SYSZONE_SIZE,
		  applzone_memory_segment_size) == -1)
	warning_errno ("unable to `munmap ()' previous applzone");
  
      ApplZone = (THz) RM ((long) MR (SysZone)
			   + INIT_SYSZONE_SIZE
			   + (mm_current_applzone * INIT_APPLZONE_SIZE));
      mm_current_applzone = (mm_current_applzone + 1) % mm_n_applzones;
  
      if (mmap ((char *) MR (ApplZone), INIT_APPLZONE_SIZE,
		PROT_READ | PROT_WRITE,
		MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1, 0) == (void *) -1)
	errno_fatal ("unable to `mmap ()' new applzone");
    }
  else
    ApplZone = (THz) RM ((long) MR (SysZone) + INIT_SYSZONE_SIZE);
#else /* !MM_MANY_APPLZONES */
  ApplZone = (THz) RM ((long) MR (SysZone) + INIT_SYSZONE_SIZE);
#endif
  
  InitApplZone ();
  
  ApplLimit = RM ((Ptr) ((long) MR (ApplZone)
			 + INIT_APPLZONE_SIZE));
  
  EM_A7 = (long) US_TO_SYN68K(stack_end - 16 - MANDELSLOP);
  
  MemErr = CWC (noErr);
  
#if ERROR_SUPPORTED_P (ERROR_MEMORY_MANAGER_SLAM)
  error_set_enabled (ERROR_MEMORY_MANAGER_SLAM, old_debug_enabled_p);
#endif
}

void
SetApplBase (Ptr newbase)
{
  int32 totend;
  
  if ((char *) newbase < (char *) ZONE_BK_LIM (MR (SysZone)) + MIN_BLOCK_SIZE)
    {
      SET_MEM_ERR (noErr);
      return;
    }
  
  /* Find out how big this makes the last bit of the system zone */
  totend = (char *) newbase - (char *) ZONE_BK_LIM (MR (SysZone));

  if (totend >= 24)		/* Make two blocks */
    {
      block_header_t *newfree = ZONE_BK_LIM (MR (SysZone));
      block_header_t *newlast = POINTER_TO_BLOCK (newbase);
      
      mm_set_block_fields_offset (newfree,
			   FREE_BLOCK_STATE, FREE, 0, 
			   (char *) newlast - (char *) newfree, 0);
      
      mm_set_block_fields_offset (newlast,
			   FREE_BLOCK_STATE, FREE, 0,
			   MIN_BLOCK_SIZE, 0);
    }
  /* Otherwise, blow it off.  There isn't room for another block at the end,
     so we just let the old bkLim stand. */
  ApplZone = RM ((THz) newbase);
  
  InitApplZone ();
  SET_MEM_ERR (noErr);
}

void
MoreMasters (void)
{
  THz current_zone;
  uint32 *handles;
  int i;
  
  MM_SLAM ("entry");
  
  current_zone = MR (TheZone);

  handles = (uint32 *) NewPtr (ZONE_MORE_MAST (current_zone)
			       * sizeof (uint32));
  
  if (handles == NULL)
    {
      SET_MEM_ERR (memFullErr);
      return;
    }
  
  handles[0] = (uint32) ZONE_HFST_FREE_X (current_zone);
  ZONE_HFST_FREE_X (current_zone)
    = RM ((Ptr) &handles[ZONE_MORE_MAST (current_zone) - 1]);

  for (i = ZONE_MORE_MAST (current_zone) - 1; i > 0; i--)
    handles[i] = RM ((uint32) &handles[i - 1]);
  
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
  return;
}

#if !defined (NDEBUG)
void
print_free (void)
{
  printf ("%d %d\n", CL (MR(ApplZone)->zcbFree), CL (MR(SysZone)->zcbFree));
}
#endif

void
InitZone (ProcPtr pGrowZone, int16 cMoreMasters,
	  Ptr limitPtr, THz zone)
{
  block_header_t *last_block;
  block_header_t *first_block;


  /* following line is needed for PPC version of Illustrator 5.5
     TODO: check mac carefully to see how they handle the final block */

  limitPtr = limitPtr - 8;
  
  last_block = (block_header_t *) ((char *) limitPtr - MIN_BLOCK_SIZE);
  first_block = ZONE_HEAP_DATA (zone);
  
  zone->bkLim     = RM ((Ptr) last_block);
  zone->purgePtr  = CLC_NULL;
  zone->hFstFree  = CLC_NULL;
  zone->zcbFree   = CL ((uint32) limitPtr - (uint32) zone - 64);
  zone->gzProc    = RM (pGrowZone);
  zone->moreMast  = CW (cMoreMasters);
  zone->flags     = CWC (0);
  zone->minCBFree = CWC (0);
  zone->purgeProc = CLC_NULL;
  zone->cntRel = zone->cntNRel = zone->cntEmpty = zone->cntHandles
    = CWC (0);
  
  /* hypercard checks byte `30' (the high byte of the `maxNRel' field)
     of the Zone structure to determine if it can set the alloc
     pointer to the address of a non-relocatable block it just
     `DisposePtr ()'ed (which is allowed in 24bit zones).
     
     #### testing on the mac to see what the `max*' fields actually
     mean in a 32bit zone is required, but this is a good a guess as
     anything */
  zone->maxRel = zone->maxNRel = CWC (-1);
  
#if 0
  zone->sparePtr = CLC ((Ptr) 0x83d2);	/* From experimentation */
#else
  zone->sparePtr = CLC (0);	/* Better safe than sorry */
#endif

  zone->allocPtr = RM ((Ptr) first_block);

  mm_set_block_fields_offset (first_block,
		       FREE_BLOCK_STATE, FREE, 0,
		       (char *) last_block - (char *) first_block, 0);
  
  mm_set_block_fields_offset (last_block,
		       FREE_BLOCK_STATE, FREE, 0,
		       MIN_BLOCK_SIZE, 0);
  
  TheZone = RM (zone);
  MoreMasters ();
  
  MM_SLAM_ZONE (zone, "exit");
  SET_MEM_ERR (noErr);
}

THz
GetZone (void)
{
  MM_SLAM ("entry");
  
  SET_MEM_ERR (noErr);
  return MR (TheZone);
}

void
SetZone (THz hz)
{
  MM_SLAM ("entry");
  TheZone = RM (hz);
  SET_MEM_ERR (noErr);
}


Handle
_NewEmptyHandle_flags (boolean_t sys_p)
{
  THz save_zone, current_zone;
  Handle h;

  MM_SLAM ("entry");
  
  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  
  current_zone = MR (TheZone);
  
  for (;;)
    {
      h = (Handle) ZONE_HFST_FREE (current_zone);
      if (h)
	{
	  ZONE_HFST_FREE_X (current_zone) = h->p;
	  h->p = NULL;
	  SET_MEM_ERR (noErr);
	  break;
	}
      else
	{
	  MoreMasters ();
	  if (MemError () != noErr)
	    {
	      SET_MEM_ERR (memFullErr);
	      break;
	    }
	}
    }
  
  MM_SLAM ("exit");
  TheZone = save_zone;
  return h;
}

Handle
_NewHandle_flags (Size size, boolean_t sys_p, boolean_t clear_p)
{
  Handle newh;
  block_header_t *block;
  THz save_zone, current_zone;
  
  MM_SLAM ("entry");
  
  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  current_zone = MR (TheZone);
  
  newh = NewEmptyHandle ();
  if (newh == NULL)
    {
      SET_MEM_ERR (memFullErr);
      goto done;
    }
  
  size += HDRSIZE;
  if (ROMlib_relalloc (size, &block) != noErr)
    {
      DisposHandle (newh);
      newh = NULL;
      SET_MEM_ERR (memFullErr);
      goto done;
    }
  
  gui_assert (block < ZONE_BK_LIM (current_zone));
  
  ROMlib_setupblock (block, size, REL, newh, 0);
  newh->p = BLOCK_DATA_X (block);
  
  if (clear_p)
    memset (BLOCK_DATA (block), CRACKER_ZERO, size - HDRSIZE);
  
  SET_MEM_ERR (noErr);
  
  /* fall through */
 done:
  
  MM_SLAM ("exit");
  TheZone = save_zone;
  return newh;
}

#define TTS_HACK (ROMlib_options & ROMLIB_DISPOSHANDLE_HACK_BIT)

void
DisposHandle (Handle h)
{
  MM_SLAM ("entry");

  if (TTS_HACK && 
      h == (Handle) (CL (*(long *)SYN68K_TO_US (256)) + ROMlib_offset))
    h = 0;
  
  if (h)
    {
      block_header_t *block;
      THz current_zone;
      THz save_zone;
      
      save_zone = TheZone;
      current_zone = HandleZone (h);
      if (!current_zone)
	{
	  SET_MEM_ERR (memAZErr);
	  return;
	}
      TheZone = RM (current_zone);
      
      block = HANDLE_TO_BLOCK (h);

      if (h->p && BLOCK_TO_HANDLE (MR (TheZone), block) != h)
	{
	  TheZone = save_zone;
	  SET_MEM_ERR (memAZErr);
	  return;
	}
      
      if (block)
	{
	  if (USE (block) == FREE)
	    {
	      TheZone = save_zone;
	      SET_MEM_ERR (memWZErr);
	      return;
	    }

	  if (HANDLE_STATE (h, block) & RSRCBIT)
	    {
	      if (h == (Handle) ROMlib_phoney_name_string)
		{
		  SET_MEM_ERR (memAZErr);
		  return;
		}
	      warning_unexpected ("disposing resource handle");
	    }
	  
	  ROMlib_freeblock (block);
	}
      
      h->p = ZONE_HFST_FREE_X (current_zone);
      ZONE_HFST_FREE_X (current_zone) = RM ((Ptr) h);
      
      TheZone = save_zone;
    }

  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

Size
GetHandleSize (Handle h)
{
  block_header_t *block;
  Size retval;

  MM_SLAM ("entry");
  
  block = HANDLE_TO_BLOCK (h);
  if (block == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      retval = 0;
    }
  else if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return 0;
    }
  else
    {
      SET_MEM_ERR (noErr);
      retval = LSIZE (block);
    }

  return retval;
}

void
SetHandleSize (Handle h, Size newsize)
{
  block_header_t *block;
  int32 oldpsize;
  THz save_zone, current_zone;
  boolean_t save_memnomove_p;
  unsigned int state;
  
#if defined (X) /* what about NEXTSTEP? */
  if (h == MR (TEScrpHandle))
    WeOwnScrapX ();
#endif  

  MM_SLAM ("entry");
  
  newsize += HDRSIZE;		/* Header */
  
  block = HANDLE_TO_BLOCK (h);
  if (!block)
    {
      SET_MEM_ERR (nilHandleErr);
      return;
    }
  
  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }
  
  save_zone = TheZone;
  current_zone = HandleZone (h);
  TheZone = RM (current_zone);
  state = HANDLE_STATE (h, block);

  oldpsize = PSIZE (block);
  if (newsize <= oldpsize)
    ROMlib_setupblock (block, newsize, REL, h, state);
  else
    {
      block_header_t *nextblock;
      block_header_t *newblock;
      
      nextblock = BLOCK_NEXT (block);
      
      /* First try and grow it forward */
      save_memnomove_p = ROMlib_memnomove_p;
      if (USE (nextblock) == REL && ROMlib_locked (block))
	ROMlib_memnomove_p = FALSE;
      if (ROMlib_makespace (&nextblock, newsize - oldpsize))
	{
	  ROMlib_memnomove_p = save_memnomove_p;
	  ZONE_ZCB_FREE_X (current_zone)

	    = CL (ZONE_ZCB_FREE (current_zone) - PSIZE (nextblock));
	  
	  SETPSIZE (block, oldpsize + PSIZE (nextblock));
	  SETSIZEC (block, 0);
	  ROMlib_setupblock (block, newsize, REL, h, state);
	}
      else
	{
	  ROMlib_memnomove_p = save_memnomove_p;
	  /* At this point, if the block is locked, we lose */
	  if (ROMlib_locked (block))
	    goto bad;
	  
	  GZRootHnd = RM (h);
	  /* Now try and find the space elsewhere */
	  if (ROMlib_relalloc (newsize, &newblock))
	    {
	      GZRootHnd = NULL;
	      goto bad;
	    }
	  GZRootHnd = NULL;
	  ROMlib_moveblock (block, newblock, newsize);
	}
    }

  /* And now we're done. */
  TheZone = save_zone;
  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
  return;

 bad:
  TheZone = save_zone;
  MM_SLAM ("exit");
  SET_MEM_ERR (memFullErr);
}

#define HANDLE_IN_ZONE_P(handle, z)					\
  (   (unsigned long) (handle) >= (unsigned long) MR (z)		\
   && (unsigned long) (handle) <  (unsigned long) ZONE_BK_LIM (MR (z)))

#define PTR_IN_ZONE_P(ptr, z)					\
  (   (unsigned long) (ptr) >= (unsigned long) MR (z)		\
   && (unsigned long) (ptr) <=  (unsigned long) ZONE_BK_LIM (MR (z)))

THz
HandleZone (Handle h)
{
  THz zone;
  block_header_t *block;
  boolean_t applzone_p;
  boolean_t syszone_p;
  
  MM_SLAM ("entry");
  
  if (h == NULL)
    {
      SET_MEM_ERR (nilHandleErr);
      return NULL;
    }
  
  applzone_p = FALSE;
  syszone_p = FALSE;
  if (HANDLE_IN_ZONE_P (h, ApplZone))
    {
      Ptr p;

      p = STARH (h);
      if (p && !PTR_IN_ZONE_P (p, ApplZone))
	{
	  SET_MEM_ERR (memAZErr);
	  return NULL;
	}
      applzone_p = TRUE;
    }
  else if (HANDLE_IN_ZONE_P (h, SysZone))
    {
      Ptr p;

      p = STARH (h);
      if (p && !PTR_IN_ZONE_P (p, SysZone))
	{
	  SET_MEM_ERR (memAZErr);
	  return NULL;
	}
      syszone_p = TRUE;
    }
  /*
   * Prevent us from returning a zone when a dereference of the handle would
   * cause a segmentation fault.
   *
   * NOTE: we don't use the SysZone or MemTop low-memory globals, because
   *       it's possible that they have been modified in such a way that
   *       this test would fail even with an address that can legitimately
   *       be dereferenced.
   */
  else if (!VALID_ADDRESS (h))
    {
      SET_MEM_ERR (memAZErr);
      return NULL;
    }

  block = HANDLE_TO_BLOCK (h);
  if (block && USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return NULL;
    }
  
  if (block)
    zone = (THz) ((int32) h - (int32) BLOCK_LOCATION_OFFSET (block));
  else if (applzone_p)
    zone = MR (ApplZone);
  else if (syszone_p)
    zone = MR (SysZone);
  else
    zone = MR (TheZone);
  
  SET_MEM_ERR (noErr);
  return zone;
}

Handle
_RecoverHandle_flags (Ptr p, boolean_t sys_p)
{
  block_header_t *block;
  THz zones[3];
  Handle h;
  int i;
  
  MM_SLAM ("entry");
  
  block = POINTER_TO_BLOCK (p);
  
  if (sys_p)
    {
      zones[0] = MR (SysZone);
      zones[1] = MR (TheZone);
    }
  else
    {
      zones[0] = MR (TheZone);
      zones[1] = MR (SysZone);
    }
  zones[2] = MR (ApplZone);
  
  for (i = 0; i < 3; i++)
    {
      h = BLOCK_TO_HANDLE (zones[i], block);

      if ((uint32) h > (uint32) zones[i]
	  && (uint32) h < (uint32) ZONE_BK_LIM (zones[i])
	  && STARH (h) == p)
	break;
    }

  if (i < 3)
    SET_MEM_ERR (noErr);
  else
    {
      h = 0;
#warning FIND OUT WHAT A REAL MAC DOES HERE
    }
  return h;
}

void
ReallocHandle (Handle h, Size size)
{
  block_header_t *oldb, *newb;
  int32 newsize;
  THz save_zone;
  THz current_zone;
  unsigned int state;

  MM_SLAM ("entry");
  
  if (h == NULL)
    warning_unexpected ("called with NULL_STRING handle");
  
  oldb = HANDLE_TO_BLOCK (h);
  
  save_zone = TheZone;
  current_zone = HandleZone (h);
  TheZone = RM (current_zone);
  
  size += HDRSIZE;
  
  if (!oldb)
    state = 0;
  else
    {
      if (ROMlib_locked (oldb))
	{
	  TheZone = save_zone;
	  SET_MEM_ERR (memPurErr);
	  return;
	}

      if (USE (oldb) == FREE)
	{
	  TheZone = save_zone;
	  SET_MEM_ERR (memWZErr);
	  return;
	}

      state = BLOCK_STATE (oldb);
      if (PSIZE (oldb) >= (uint32) size)
	{
	  ROMlib_setupblock (oldb, size, REL, h, state);
	  goto done;
	}

      newb = BLOCK_NEXT (oldb);
      if (USE (newb) == FREE)
	{
	  ROMlib_coalesce (newb);
	  newsize = PSIZE (oldb) + PSIZE (newb);

	  if (newsize >= size)
	    {
	      SETPSIZE (oldb, newsize);
	      ZONE_ZCB_FREE_X (current_zone)
		= CL (ZONE_ZCB_FREE (current_zone) - PSIZE (newb));
	      ROMlib_setupblock (oldb, size, REL, h, state);
	      goto done;
	    }
	}
    }

  if (ROMlib_relalloc (size, &newb))
    {
      TheZone = save_zone;
      SET_MEM_ERR (memFullErr);
      return;
    }
  
  ROMlib_setupblock (newb, size, REL, h, state);
  SETMASTER (h, BLOCK_DATA (newb));
  
  if (oldb)
    ROMlib_freeblock (oldb);
  /* fall through */
 done:
  TheZone = save_zone;
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

#if 1 && !defined(NDEBUG)
int do_save_alloc = 0;
#endif

Ptr
_NewPtr_flags (Size size, boolean_t sys_p, boolean_t clear_p)
{
  Ptr p;
  block_header_t *b;
  THz save_zone, current_zone;
#if 1
  block_header_t *save_alloc_ptr;
#endif
  
  MM_SLAM ("entry");
  
  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;

  current_zone = MR (TheZone);

  size += HDRSIZE;

#if 1
  save_alloc_ptr = (typeof (save_alloc_ptr)) ZONE_ALLOC_PTR_X (current_zone);
#endif

  ZONE_ALLOC_PTR_X (current_zone) = CLC_NULL;

  ResrvMem (size);
  if (ROMlib_relalloc (size, &b))
    {
#if 0
      ZONE_ALLOC_PTR_X (current_zone) = save_alloc_ptr;
#endif
      TheZone = save_zone;
      SET_MEM_ERR (memFullErr);
      MM_SLAM ("exit");
      return NULL;
    }

#if 1 && !defined(NDEBUG)
  if (do_save_alloc)
    ZONE_ALLOC_PTR_X (current_zone)
      = (typeof (ZONE_ALLOC_PTR_X (current_zone))) save_alloc_ptr;
  else
    checkallocptr ();
#endif

  gui_assert (b < ZONE_BK_LIM (current_zone));

  ROMlib_setupblock (b, size, NREL, 0);
  p = BLOCK_DATA (b);

  if (clear_p)
    memset (p, CRACKER_ZERO, size - HDRSIZE);

  TheZone = save_zone;
  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
  return p;
}

void
DisposPtr (Ptr p)
{
  MM_SLAM ("entry");
  
  if (p)
    {
      block_header_t *block;
      THz zone;

      block = POINTER_TO_BLOCK (p);

      if (USE (block) == FREE)
	{
	  SET_MEM_ERR (memWZErr);
	  return;
	}
      
      zone = PtrZone (p);
      if (zone)
	{
	  ZONE_SAVE_EXCURSION
	    (RM (zone),
	     {
	       ROMlib_freeblock (block);
	     });
	}
      else
	warning_unexpected (NULL_STRING);
    }
  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
}


Size
GetPtrSize (Ptr p)
{
  block_header_t *block;
  
  MM_SLAM ("entry");
  
  block = POINTER_TO_BLOCK (p);
  
  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return 0;
    }

  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
  return LSIZE (block);
}

void
SetPtrSize (Ptr p, Size newsize)
{
  block_header_t *block;
  LONGINT oldpsize;
  THz save_zone, current_zone;
  
  MM_SLAM ("entry");
  
  if (p == NULL)
    warning_unexpected ("attempt to set NULL_STRING pointer size");
  
  newsize += HDRSIZE;
  block = POINTER_TO_BLOCK (p);
  
  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      return;
    }

  save_zone = TheZone;
  current_zone = PtrZone (p);

  if (!current_zone)
    {
      warning_unexpected (NULL_STRING);
      SET_MEM_ERR (memWZErr); /* Not really sure what we should return in
				 this case.  It's not like the Mac has any
				 decent error semantics */
    }
  else
    {
      TheZone = RM (current_zone);
      
      oldpsize = PSIZE (block);
      if (newsize <= oldpsize)
	ROMlib_setupblock (block, newsize, NREL, 0);
      else
	{
	  block_header_t *nextblock;
      
	  nextblock = BLOCK_NEXT (block);

	  /* First try and grow it forward */
	  if (ROMlib_makespace (&nextblock, newsize - oldpsize))
	    {
	      ZONE_ZCB_FREE_X (current_zone)
		= CL (ZONE_ZCB_FREE_X (current_zone) - PSIZE (nextblock));

	      SETPSIZE (block, oldpsize + PSIZE (nextblock));
	      SETSIZEC (block, 0);
	      ROMlib_setupblock (block, newsize, NREL, 0);
	    }
	  else
	    {
	      TheZone = save_zone;
	      SET_MEM_ERR (memFullErr);
	      return;
	    }
	}
    }

  /* And now we're done. */
  TheZone = save_zone;
  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
}

PRIVATE boolean_t
legit_addr_p (void *addr)
{
  boolean_t retval;

  retval = TRUE; /* all addresses are valid for now */
  return retval;
}


PRIVATE boolean_t
legit_zone_p (THz zone)
{
  boolean_t retval;

  if (!legit_addr_p (zone))
    retval = FALSE;
  else
    {
      block_header_t *blockp;

      blockp = ZONE_HEAP_DATA (zone);
      retval = (THz) blockp->location_u == RM (zone);
    }

  return retval;
}

THz
PtrZone (Ptr p)
{
  block_header_t *block;
  THz zone;
  
  MM_SLAM ("entry");
  
  if (p == NULL)
    warning_unexpected ("attempt to set NULL_STRING pointer size");

  block = POINTER_TO_BLOCK (p);

  if (USE (block) == FREE)
    {
      SET_MEM_ERR (memWZErr);
      /* don't count on this value (IMII-38) */
      return NULL;
    }
  
  zone = (THz) BLOCK_LOCATION_ZONE (block);

  if (!legit_zone_p (zone))
    {
      SET_MEM_ERR (memWZErr); /* not sure what this should be */
      return NULL;
    }

  SET_MEM_ERR (noErr);
  return zone;
}

int32
_FreeMem_flags (boolean_t sys_p)
{
  uint32 freespace;
  
  MM_SLAM ("entry");
  
  if (sys_p)
    freespace = ZONE_ZCB_FREE (MR (SysZone));
  else
    freespace = ZONE_ZCB_FREE (MR (TheZone));
  
  SET_MEM_ERR (noErr);
  return freespace;
}

Size
_MaxMem_flags (Size *growp, boolean_t sys_p)
{
  block_header_t *b;
  THz save_zone;
  THz current_zone;
  uint32 biggestfree;
  uint32 sizesofar;
  block_header_t *startb;
  Size grow;
  enum { SEARCHING, COUNTING } state;
  
  MM_SLAM ("entry");
  
  sizesofar = 0;
  startb = 0;
  
  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  
  current_zone = MR (TheZone);

  /* Purge everyone */
  for (b = ZONE_HEAP_DATA (current_zone);
       b != ZONE_BK_LIM (current_zone);
       b = BLOCK_NEXT (b))
    {
      Handle h;

      if (PSIZE (b) < MIN_BLOCK_SIZE)
	HEAP_DEATH ();

      h = BLOCK_TO_HANDLE (current_zone, b);
      if (USE (b) == REL
	  && (HANDLE_STATE (h, b) & (LOCKBIT | PURGEBIT)) == PURGEBIT)
	EmptyHandle (h);
    }

  /* Compact the whole thing */
  CompactMem (0x7FFFFFFF);

  /* Compress the free blocks */
  state = SEARCHING;
  biggestfree = 0;

  for (b = ZONE_HEAP_DATA (current_zone);
       b != ZONE_BK_LIM (current_zone);
       b = BLOCK_NEXT (b))
    {
      if (PSIZE (b) < MIN_BLOCK_SIZE)
	HEAP_DEATH ();

      if (state == SEARCHING)
	{
	  if (USE (b) == FREE)
	    {
	      startb = b;
	      sizesofar = PSIZE (b);
	      state = COUNTING;
	    }
	}
      else
	{
	  if (USE (b) == FREE)
	    sizesofar += PSIZE (b);
	  else
	    {
	      SETPSIZE (startb, sizesofar);
	      if (ZONE_ALLOC_PTR (current_zone) > startb
		  && ((char *) ZONE_ALLOC_PTR (current_zone)
		      < (char *) startb + sizesofar))
		ZONE_ALLOC_PTR_X (current_zone) = RM ((Ptr) startb);
	      if (sizesofar > biggestfree)
		biggestfree = sizesofar;
	      state = SEARCHING;
	    }
	}
    }
  if (state == COUNTING)
    {
      SETPSIZE (startb, sizesofar);
      if (ZONE_ALLOC_PTR (current_zone) > startb
	  && ((char *) ZONE_ALLOC_PTR (current_zone)
	      < (char *) startb + sizesofar))
	ZONE_ALLOC_PTR_X (current_zone) = RM ((Ptr) startb);

      if (sizesofar > biggestfree)
	biggestfree = sizesofar;
    }

  if (TheZone == ApplZone)
    grow = (uint32) MR (ApplLimit) - (uint32) HEAPEND;
  else
    grow = 0;

  TheZone = save_zone;

  *growp = grow;
  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
  return biggestfree;
}


Size
_CompactMem_flags (Size sizeneeded, boolean_t sys_p)
{
  int32 amtfree;
  block_header_t *src, *target, *ap;
  THz save_zone;
  THz current_zone;
  boolean_t startfront_p;
  
  MM_SLAM ("entry");
  
  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  current_zone = MR (TheZone);


  /* We've seen HyperCard load allocPtr with -8 before ... yahoo.
     Specifically, HC 2.1 would do this after you use the Chart making
     stack to make a chart and then you try to "Go Home" via the menu */

  ap = ZONE_ALLOC_PTR (current_zone);
  if (ap >= ZONE_HEAP_DATA (current_zone)
      && ap <= ZONE_BK_LIM (current_zone))
    src = target = ap;
  else
    src = target = ZONE_HEAP_DATA (current_zone);
  startfront_p = (src == ZONE_HEAP_DATA (current_zone));

 repeat:
  ZONE_ALLOC_PTR_X (current_zone) = CLC_NULL;
  amtfree = 0;

  while (src != ZONE_BK_LIM (current_zone)
	 && amtfree < sizeneeded)
    {
      if (USE (src) == REL && !ROMlib_locked (src))
	{
	  if (src == target)
	    src = target = BLOCK_NEXT (target);
	  else if (src < target)
	    gui_abort ();
	  else
	    {
	      *target = *src;
	      
	      SETMASTER (BLOCK_TO_HANDLE (current_zone, src),
			 BLOCK_DATA (target));
	      
	      BlockMove (BLOCK_DATA (src), BLOCK_DATA (target), LSIZE (src));
	      
	      src = (block_header_t *) ((char *) src + PSIZE (target));
	      target = BLOCK_NEXT (target);
	    }
	}
      else if (USE (src) == FREE)
	src = BLOCK_NEXT (src);
      else
	{
	  if (src > target)
	    {
	      int32 src_target_diff = (char *) src - (char *) target;

	      mm_set_block_fields_offset (target,
				   FREE_BLOCK_STATE, FREE, 0,
				   src_target_diff, 0);
	      
	      if (src_target_diff > amtfree)
		{
		  amtfree = src_target_diff;
		  if (!ZONE_ALLOC_PTR_X (current_zone))
		    ZONE_ALLOC_PTR_X (current_zone) = (Ptr) RM (target);
		}
	    }
	  src = target = BLOCK_NEXT (src);
	}
    }

  /* If we got out because we hit the end, do the last case above for 
     the final free block. */
  if (src == ZONE_BK_LIM (current_zone) && src > target)
    {
      int32 src_target_diff = (char *) src - (char *) target;

      mm_set_block_fields_offset (target,
			   FREE_BLOCK_STATE, FREE, 0,
			   src_target_diff, 0);
      
      if (src_target_diff > amtfree)
	{
	  amtfree = src_target_diff;
	  if (!ZONE_ALLOC_PTR_X (current_zone))
	    ZONE_ALLOC_PTR_X (current_zone) = RM ((Ptr) target);
	}
    }
  
  if (amtfree < sizeneeded && !startfront_p)
    {
      startfront_p = TRUE;
      src = target = ZONE_HEAP_DATA (current_zone);
      goto repeat;
    }
  
  TheZone = save_zone;
  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
  return amtfree;
}

void
_ResrvMem_flags (Size needed, boolean_t sys_p)
{
  THz save_zone;
  THz current_zone;
  block_header_t *b;
  Size free;
  long avail;
  boolean_t already_maxed_p;
  
  MM_SLAM ("entry");
  
  if (needed <= 0)
    {
      SET_MEM_ERR (noErr);
      return;
    }

  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  current_zone = MR (TheZone);
  already_maxed_p = FALSE;

 again:
  for (b = ZONE_HEAP_DATA (current_zone);
       b != ZONE_BK_LIM (current_zone);
       b = BLOCK_NEXT (b))
    {
      if (PSIZE (b) < MIN_BLOCK_SIZE)
	HEAP_DEATH ();

      if (ROMlib_makespace (&b, needed))
	{
	  TheZone = save_zone;
	  MM_SLAM ("exit");
	  SET_MEM_ERR (noErr);
	  return;
	}
    }

  avail = MaxMem (&free);
  if (avail >= needed && !already_maxed_p)
    {
      already_maxed_p = TRUE;
      goto again;
    }
  if (free >= needed)
    {
      /* relalloc will do the actual extension. */
      TheZone = save_zone;
      MM_SLAM ("exit");
      SET_MEM_ERR (noErr);
      return;
    }

  TheZone = save_zone;
  MM_SLAM ("exit");
  SET_MEM_ERR (memFullErr);
}

void
_PurgeMem_flags (Size sizeneeded, boolean_t sys_p)
{
  long amount_free, max_free;
  block_header_t *b;
  THz save_zone;
  THz current_zone;
  
  MM_SLAM ("entry");
  
  amount_free = 0;

  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  current_zone = MR (TheZone);

  max_free = 0;
  for (b = ZONE_HEAP_DATA (current_zone);
       b != ZONE_BK_LIM (current_zone);
       b = BLOCK_NEXT (b))
    {
      Handle h;
      
      if (PSIZE (b) < MIN_BLOCK_SIZE)
	HEAP_DEATH ();

      h = BLOCK_TO_HANDLE (current_zone, b);
      
      if (USE (b) == REL
	  && (HANDLE_STATE (h, b) & (LOCKBIT | PURGEBIT)) == PURGEBIT)
	EmptyHandle (h);
      
      amount_free = ROMlib_amtfree (b);
      if (amount_free >= max_free)
	max_free = amount_free;
      if (amount_free >= sizeneeded)
	break;
    }

  TheZone = save_zone;
  
  if (amount_free < sizeneeded)
    SET_MEM_ERR (memFullErr);
  else
    SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
}


PRIVATE void
BlockMove_and_possibly_flush_cache (Ptr src, Ptr dst, Size cnt,
				    boolean_t flush_p)
{
  if (cnt > 0)
    {
      /* ugly, but probably better than crashing when we try to
	 dereference 0 */

      if (!dst)
	dst = (Ptr) SYN68K_TO_US (dst);

      if (!src)
	src = (Ptr) SYN68K_TO_US (src);

      memmove_transfer (dst, src, cnt);
      if (flush_p)
	ROMlib_destroy_blocks ((syn68k_addr_t) US_TO_SYN68K(dst), cnt, TRUE);
    }

  /* don't use `SET_MEM_ERR' since that will do a heap slam and we
     will lose */
  MemErr = CWC (noErr);
}

void
BlockMove (Ptr src, Ptr dst, Size cnt)
{
  BlockMove_and_possibly_flush_cache (src, dst, cnt, TRUE);
}

void
BlockMoveData (Ptr src, Ptr dst, Size cnt)
{
  BlockMove_and_possibly_flush_cache (src, dst, cnt, FALSE);
}

void
BlockMove_the_trap (Ptr src, Ptr dst, Size cnt, boolean_t flush_p)
{
  MM_SLAM ("entry");
  BlockMove_and_possibly_flush_cache (src, dst, cnt, flush_p);
  MM_SLAM ("exit");
}

void
MaxApplZone (void)
{
  MM_SLAM ("entry");
  
/* #warning MaxApplZone does not do anything -- we start out with max */
  SET_MEM_ERR (noErr);
}

void
MoveHHi (Handle h)
{
  MM_SLAM ("entry");

  /* Oh No! More Lemmings appears to assume that MoveHHi will
   * flush the cache.  This is not an unreasonable assumption, since
   * large BlockMove's are guaranteed to flush the cache, and
   * MoveHHi of a large piece of memory would typically involve a
   * big BlockMove.  Since MoveHHi isn't called very often,
   * and is expected to be fairly slow, we use this opportunity
   * to flush the cache.  We only nuke code whose checksums
   * have changed, for speed.
   *
   * We make an exception for Microsoft Word, because the spelling checker
   * will be real slow if we don't.
   */

  if (ROMlib_creator != TICK ("MSWD") && ROMlib_creator != TICK ("ddOr"))
    ROMlib_destroy_blocks (0, ~0, TRUE);
  
  /* #### there used to be a lot of code here; but it was unused and
     returned noErr #if !MOVEHIWORKS -- see the rcsfile for details */
/* #warning MoveHHi not implemented */
  /* #### just emits too many unecessary warnings
     warning_unimplemented (""); */
  SET_MEM_ERR (noErr);
}

int32
_MaxBlock_flags (boolean_t sys_p)
{
  THz save_zone;
  THz current_zone;
  int32 max_free;
  int32 total_free;
  block_header_t *b;
  
  MM_SLAM ("entry");
  
  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  current_zone = MR (TheZone);
  
  max_free = total_free = 0;
  
  for (b = ZONE_HEAP_DATA (current_zone);
       b != ZONE_BK_LIM (current_zone);
       b = BLOCK_NEXT (b))
    {
      if (PSIZE (b) < MIN_BLOCK_SIZE)
	HEAP_DEATH ();

      if (USE (b) == FREE)
	total_free += PSIZE (b);
      else if (USE (b) == NREL || ROMlib_locked (b))
	{
	  if (total_free > max_free)
	    max_free = total_free;
	  total_free = 0;
	}
    }

  TheZone = save_zone;
  SET_MEM_ERR (noErr);
  MM_SLAM ("exit");
  return MAX (total_free, max_free) - HDRSIZE;
}

void
_PurgeSpace_flags (Size *total_out, Size *contig_out, boolean_t sys_p)
{
  THz save_zone, current_zone;
  int32 total_free;
  int32 this_contig;
  int32 max_contig;
  block_header_t *b;
  
  MM_SLAM ("entry");
  
  save_zone = TheZone;
  if (sys_p)
    TheZone = SysZone;
  current_zone = MR (TheZone);
  
  total_free = this_contig = max_contig = 0;
  for (b = ZONE_HEAP_DATA (current_zone);
       b != ZONE_BK_LIM (current_zone);
       b = BLOCK_NEXT (b))
    {
      Handle h;

      if (PSIZE (b) < MIN_BLOCK_SIZE)
	HEAP_DEATH ();

      h = BLOCK_TO_HANDLE (current_zone, b);
      if (USE (b) == FREE
	  || (USE (b) == REL
	      && (HANDLE_STATE (h, b) & (LOCKBIT | PURGEBIT)) == PURGEBIT))
	{
	  this_contig += PSIZE (b);
	  total_free += PSIZE (b);
	}
      else if (USE (b) == NREL || ROMlib_locked (b))
	{
	  if (this_contig > max_contig)
	    max_contig = this_contig;
	  this_contig = 0;
	}
    }
  
  TheZone = save_zone;
  
  SET_MEM_ERR (noErr);
  *total_out = total_free - HDRSIZE;
  *contig_out = MAX (this_contig, max_contig) - HDRSIZE;
  MM_SLAM ("exit");
}

Size
StackSpace (void)
{
  int32 fp;

  MM_SLAM ("entry");
  
  fp = (int32) SYN68K_TO_US (EM_A7);

  /* Stack pointer on return is fp + 8 (4 for old fp, 4 for return
     address) */

  SET_MEM_ERR (noErr);
  return (fp + 8) - (int32) HEAPEND;
}

void
SetApplLimit (Ptr new_limit)
{
  /* NOTE TO CLIFF: 
     We can't do any sanity checks here (not even a brk()), since
     the ApplLimit might be directly changed by programs, and we have
     to deal with that.  */
  /* Making the ApplLimit too small has no effect (IMII-30), and making
     it too big shouldn't cause a problem until the excess memory starts
     being used (by incrementation of HEAPEND). */
  
  MM_SLAM ("entry");
  
  ApplLimit = RM (new_limit);
  HeapEnd = RM (new_limit - MIN_BLOCK_SIZE);

  SET_MEM_ERR (noErr);
}

void
SetGrowZone (ProcPtr newgz)
{
  MM_SLAM ("entry");
  
  ZONE_GZ_PROC_X (MR (TheZone)) = RM (newgz);
  SET_MEM_ERR (noErr);
}

void
EmptyHandle (Handle h)
{
  THz save_zone, current_zone;
  block_header_t *b;
  
  MM_SLAM ("entry");
  
  b = HANDLE_TO_BLOCK (h);
  if (b == NULL)
    {
      SET_MEM_ERR (noErr);
      return;
    }

  save_zone = TheZone;
  current_zone = HandleZone (h);
  TheZone = RM (current_zone);

  if (ROMlib_locked (b))
    {
      TheZone = save_zone;
      SET_MEM_ERR (memPurErr);
      return;
    }
  if (USE (b) == FREE)
    {
      TheZone = save_zone;
      SET_MEM_ERR (memWZErr);
      return;
    }

  if (ZONE_PURGE_PROC_X (current_zone))
    {
      uint32 saved0, saved1, saved2, savea0, savea1;
      ROMlib_hook (memory_purgeprocnumber);
      
      saved0 = EM_D0;
      saved1 = EM_D1;
      saved2 = EM_D2;
      savea0 = EM_A0;
      savea1 = EM_A1;
      PUSHADDR ((long) US_TO_SYN68K (h));
      CALL_EMULATOR ((syn68k_addr_t)
		     US_TO_SYN68K (ZONE_PURGE_PROC (current_zone)));
      EM_D0 = saved0;
      EM_D1 = saved1;
      EM_D2 = saved2;
      EM_A0 = savea0;
      EM_A1 = savea1;
    }

  ROMlib_freeblock (b);
  SETMASTER (h, NULL);

  TheZone = save_zone;
  MM_SLAM ("exit");
  SET_MEM_ERR (noErr);
}

/* Fluff for Cliff */
void
ROMlib_installhandle (Handle sh, Handle dh)
{
  THz save_zone;
  
  MM_SLAM ("entry");
  save_zone = TheZone;
  TheZone = RM (HandleZone (dh));

  if (TRUE
      || ROMlib_locked (HANDLE_TO_BLOCK (dh))
      || HandleZone (sh) != MR (TheZone))
    {
      Size size;

      size = GetHandleSize (sh);
      SetHandleSize (dh, size);
      if (MemErr == CWC (noErr))
	BlockMove (STARH (sh), STARH (dh), size);
      DisposHandle (sh);
    }
  else
    {
      block_header_t *db = HANDLE_TO_BLOCK (dh);
      block_header_t *sb = HANDLE_TO_BLOCK (sh);
      ROMlib_freeblock (db);
      SETMASTER (dh, STARH (sh));
      BLOCK_LOCATION_OFFSET_X (sb) = CL ((uint32) dh - (uint32) MR (TheZone));
      sh->p = (Ptr) ZONE_HFST_FREE_X (MR (TheZone));
      ZONE_HFST_FREE_X (MR (TheZone)) = RM ((Ptr) sh);
    }
  TheZone = save_zone;
  MM_SLAM ("exit");
}

OSErr
MemError (void)
{
  MM_SLAM ("entry");
  
  return CW (MemErr);
}

THz
SystemZone (void)
{
  MM_SLAM ("entry");
  
  return MR (SysZone);
}

THz
ApplicZone (void)
{
  MM_SLAM ("entry");
  
  return MR (ApplZone);
}

/* Like NewHandle, but fills in the newly allocated memory by copying
 * data from the supplied pointer.  Use the NewHandle_copy_ptr and
 * NewHandleSys_copy_ptr macros to access this function.
 */
Handle
_NewHandle_copy_ptr_flags (Size size, const void *data_to_copy,
			   boolean_t sys_p)
{
  Handle h;

  h = _NewHandle_flags (size, sys_p, FALSE);
  if (MemErr == CWC (noErr))
    memcpy (STARH (h), data_to_copy, size);
  return h;
}

/* Like NewHandle, but fills in the newly allocated memory by copying
 * data from the supplied Handle.  Use the NewHandle_copy_handle and
 * NewHandleSys_copy_handle macros to access this function.
 */
Handle
_NewHandle_copy_handle_flags (Size size, Handle data_to_copy, boolean_t sys_p)
{
  Handle h;

  if (GetHandleSize (data_to_copy) < size)
    warning_unexpected ("Not enough bytes to copy!");
  h = _NewHandle_flags (size, sys_p, FALSE);
  if (MemErr == CWC (noErr))
    memcpy (STARH (h), STARH (data_to_copy), size);
  return h;
}

/* Like NewPtr, but fills in the newly allocated memory by copying
 * data from the supplied pointer.  Use the NewPtr_copy_ptr and
 * NewPtrSys_copy_ptr macros to access this function.
 */
Ptr
_NewPtr_copy_ptr_flags (Size size, const void *data_to_copy,
			boolean_t sys_p)
{
  Ptr p;

  p = _NewPtr_flags (size, sys_p, FALSE);
  if (MemErr == CWC (noErr))
    memcpy (p, data_to_copy, size);
  return p;
}

/* Like NewPtr, but fills in the newly allocated memory by copying
 * data from the supplied Handle.  Use the NewPtr_copy_handle and
 * NewPtrSys_copy_handle macros to access this function.
 */
Ptr
_NewPtr_copy_handle_flags (Size size, Handle data_to_copy, boolean_t sys_p)
{
  Ptr p;

  if (GetHandleSize (data_to_copy) < size)
    warning_unexpected ("Not enough bytes to copy!");
  p = _NewPtr_flags (size, sys_p, FALSE);
  if (MemErr == CWC (noErr))
    memcpy (p, STARH (data_to_copy), size);
  return p;
}
