/* piw-malloc.c - Instrumented versions of the standard malloc functions.
 *
 * author(s): Chris Kingsley, Tom Lord
 ****************************************************************
 * Copyright (C) 1998 UUNET Technologies, Inc.
 * Copyright (c) 1983, 1993 The Regents of the University of California.
 * 			    All rights reserved.
 *
 * See the file "COPYING.PIW" for further information
 * about the copyright status of this work.
 */


#include "hackerlab/machine/alignment.h"
#include "hackerlab/os/unistd.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/piw/gdb.h"
#include "hackerlab/piw-malloc/logging.h"
#include "hackerlab/piw-malloc/piw-malloc.h"


/* automatically generated __STDC__ prototypes for static functions */
static __inline__ unsigned long combine (unsigned long a, unsigned long b, unsigned long c, unsigned long d);
static __inline__ unsigned long combine2 (unsigned long a, unsigned long b, unsigned long c, unsigned long d);
static __inline__ unsigned long scramble (unsigned long addr);
static __inline__ unsigned long scramble2 (unsigned long addr);
static __inline__ unsigned long block_hash (long n_bytes, void * block_addr);
static __inline__ unsigned long block_hash2 (long n_bytes, void * block_addr);
static __inline__ long block_unhash (void * block_addr, unsigned long block_hash);
static __inline__ long block_unhash2 (void * block_addr, unsigned long block_hash);
static void init_malloc ();
static int set_malloc_padding (int * errn, int n);
static __inline__ int piw_malloc_heap_tag_index (void * addr);
static long pages_ceiling (long amt);
static long pages_floor (long amt);
static void * sbrk_for_malloc (long amt, void ** foreign_start, void ** foreign_end);
static void * page_aligned_sbrk (long * actual_amount,
				 void * foreign_start[2],
				 void * foreign_end[2],
				 long requested_amt);
static __inline__ t_uchar get_byte (unsigned long word, int byte_number);
static __inline__ t_uchar padding_fill_for_location (void * loc);
static void fill_in_padding (t_uchar * padding);
static int check_padding (void * block, t_uchar * padding);
static __inline__ t_uchar fill_for_location (void * loc);
static void fill_in_region (t_uchar * region, int length);
static int check_filled_region (void * block, t_uchar * region, int length);
static int which_bucket (long * block_size_p, long n_bytes);


/************************************************************************
 *(h1 "PIW Malloc Debugging Features Functions"
 *    :includes ("hackerlab/piw/piw.h"))
 * 
 * This section describes the debugging features of PIW malloc.
 */
/*(menu)
 */

/************************************************************************
 *(h2 "An Overview of PIW Malloc Features")
 *
 * PIW malloc is capable of a variety of correctness checks and
 * logging actions.  The variables that control this behavior are
 * described in detail in the next section and at the end of the
 * manual.  Here is a brief overview:
 */
 
/*(h3 "Logging" :need 0)
 * 
 * If the flag `piw_malloc_log' is set, every call to `malloc',
 * `realloc' and `free' is logged on entry and return.  Errors
 * detected by these functions are also logged.  See xref:"PIW Log
 * Entries".
 * 
 * Malloc logging can be turned off and on as your program runs, to
 * reduce the final size of the log or the amount of time spent
 * generating log entries.  There is a trade-off, though: if logging
 * is disabled for part of a program, any report generated by
 * examining the log will be based on incomplete data.
 */

/*(h3 "Breakpoints" :need 0)
 * 
 * When the PIW malloc functions detect an error, two actions are
 * taken.  If logging is enabled (by the variable `piw_malloc_log'), a
 * log entry is generated.  Second, a "breakpoint function" is called
 * (regardless of whether or not logging is enabled).  See xref:"Using
 * GDB With PIW".
 */

/*(h3 "Padding" :need 0)
 * 
 * Allocated blocks can optionally be surrounded by extra space
 * (*padding*) by setting the variable `piw_malloc_block_pad'.  That
 * variable is the number of bytes of padding to add before and after
 * each allocated block.
 * 
 * Padding can be used to inexpensively test the hypothesis that stray
 * writes near allocated regions are causing your program to fail.  If
 * padding eliminates the problem, that strongly suggests the
 * hypothesis is correct.  Padding has other uses which are described
 * below.
 * 
 * The amount of padding must be set once, during the initialization
 * of the PIW run-time system, and can not be changed thereafter.  The
 * PIW implementation of `malloc' assumes that all allocated blocks
 * have the same amount of padding -- no provision is made for padding
 * some blocks of memory but not others or for padding different
 * blocks by different amounts.
 */

/*(h3 "Filling")
 * 
 * Setting the variable `piw_malloc_block_fill' causes newly allocated
 * or freed memory to be filled with location-dependent values.
 * Setting `piw_malloc_padding_fill' causes newly allocated padding
 * areas to be filled.  These are comparatively expensive operations.
 * 
 * Filling can be used to test the hypothesis that a program is
 * working in spite of reads from uninitialized memory, stray reads
 * from freed blocks of memory, or reads from areas just outside of
 * allocated blocks.  Such reads may ordinarily return 0 or
 * stale-but-usable data, thus disguising bugs in your program.
 * Actively filling regions where such reads are likely to take place
 * increases the chance that a bug will clearly reveal itself.
 * 
 * Filling may be selectively turned on and off as your program runs.
 * This can be used to reduce the expense of filling.
 * 
 * Filling has another use which is explained next.
 */

/*(h3 "Overwrite Checking")
 * 
 * Setting the variable `piw_malloc_check_filled' causes
 * malloc-related functions to examine filled-in padding areas and
 * blocks of freed memory for signs of errant overwrites.  For
 * `piw_malloc_check_filled' to be useful, one or both of the
 * variables `piw_malloc_padding_fill' and `piw_malloc_block_fill'
 * must be set.
 * 
 * The values in filled areas are opportunisticly compared to the
 * location-dependent values PIW stores there and a difference
 * indicates that a stray write has occured.
 * 
 * Overwrites discovered this way are discovered "late" (possibly well
 * after they occur).  When they are discovered, they are immediately
 * logged and a breakpoint function is called.
 * 
 * Overwrite checking may be selectively turned on and off as your
 * program runs, to reduce the cost of using it, however, there is an
 * important restriction.  If your program *ever* uses overwrite
 * checking then it must *always* use one or both kinds of filling.
 * There is no provision for having some areas filled and some not.
 */

/*(h3 "Internal Data Structures Validation")
 * 
 * Setting the variable `piw_malloc_internals_check' to a positive
 * integer causes malloc-related functions to periodically examine the
 * allocator's internal data structures for consistency.  If problems
 * are found, an error is logged and a breakpoint function is called.
 * 
 * Setting `piw_malloc_internals_check' to 1 causes checks to be
 * performed as often as possible.  Setting it to `N' causes checks to
 * be performed every `N'th opportunity (so, the higher `N', the less
 * frequently checks are made).
 */

/*(h3 "Malloc Heap Tag Bits")
 * 
 * Setting the variable `piw_keep_tag_bits' causes malloc to maintain
 * a bitmap that describes which regions of the malloc heap are part
 * of allocated blocks, and which are not.  In combination with *write
 * barriers*, this can be used to detect stray writes before they
 * occur.  (See xref:"Using PIW Malloc With Write Barriers".)
 * 
 * Tag bits must be enabled once during the initialization of the PIW
 * run-time system, and can not be disabled thereafter.  PIW malloc
 * assumes that the tag bits describes all allocated regions -- there
 * is no provision for tagging only part of the malloc arena.
 */

/****************************************************************
 * malloc, realloc, and free.
 *
 * 	*  The format of memory returned to the program is:
 * 
 * 	    |__block header__|__padding__|__user data__|__padding__|
 * 		    ^			 ^
 * 		    |			 |
 * 	 (struct malloc_header)link;	 Value returned from malloc.
 * 
 * 
 * 	    Padding is optional. The amount of padding is determined
 * 	    at the time of the first call to malloc.
 * 
 * 	*  The format of a block header is:
 */

struct malloc_header
{
  union
    {
      void * link;
      unsigned long hash;
    } h;
  union
    {
      void * link;
      unsigned long hash;
    } xtra;
};

#define SIZEOF_MALLOC_HEADER  (((sizeof (struct malloc_header) + MACHINE_ALIGNMENT - 1) / MACHINE_ALIGNMENT) * MACHINE_ALIGNMENT)

/* 
 * 	   While the block is on a free-list, `h.link' points to the 
 * 	   next, word-aligned block.  While the block is allocated,
 * 	   `h.hash' holds a hash value derived from the address and size 
 *	   of the block.  It is possible to recover the size of the block 
 *	   given that hash value and the block address.
 * 
 *	   While a block is allocated, `xtra.hash' also holds a hash value
 *	   derived from the address and size of the block.  This information
 *	   is redundant and is used to help detect bogus arguments to 
 *	   `realloc' or `free'.
 */


/************************************************************************
 *(h2 "Allocation Function Control Variables")
 * 
 * 
 * PIW's implementation of `malloc' is controlled by the boolean flags
 * and integer variables documented below.  See xref:"Control
 * Variables".
 * 
 * Some variables may be useful to modify at run-time by setting the
 * global variable of the same name.  Other variables (as noted in the
 * documentation) must be set only before the first call to a
 * malloc-related function -- it is best to set them by means of
 * `PIWFLAGS'.
 */

/*(c piw_malloc_log)
 * extern int piw_malloc_log;
 *
 * If true, log calls to `malloc' and errors detected by `malloc'.
 */
extern int piw_malloc_log;


/*(c piw_malloc_block_pad)
 * extern int piw_malloc_block_pad;
 *
 * The number of bytes of padding before and after each allocated
 * block of memory.
 *
 * This may only be changed before the first call to `malloc'.
 */
extern int piw_malloc_block_pad;


/*(c piw_keep_tag_bits)
 * int piw_keep_tag_bits;
 *
 * If true, tag bits are kept that describe the heap as known
 * to malloc.
 *
 * This may only be changed before the first call to `malloc'.
 * See xref:"Using PIW Malloc With Write Barriers".
 */
extern int piw_keep_tag_bits;


/*(c piw_tag_map_range)
 * int piw_tag_map_range;
 *
 * Space for tag bits is allocated once, when `malloc' initializes its
 * internal data structures.  `tag_map_range' is the maximum size of a
 * malloc heap for which tag bits are allocated.  This value is
 * expressed in megabytes.  The default value is 32.
 *
 * This may only be changed before the first call to `malloc'.
 * 
 * If the heap expands beyond the size indicated in `piw_tag_map_range',
 * tag bits are not kept for the overflow portion of the heap.
 */
extern int piw_tag_map_range;


/*(c piw_malloc_block_fill)
 * int piw_malloc_block_fill;
 *
 * If not 0, `malloc'-related functions fill in newly allocated and
 * freed memory with location-dependent values.  This helps to detect
 * stray writes and to cause stray reads to appear as bugs.
 */
extern int piw_malloc_block_fill;


/*(c piw_malloc_padding_fill)
 * int piw_malloc_padding_fill;
 *
 * If not 0, malloc-related functions fill in newly allocated padding
 * areas with location-dependent values.  This helps to detect stray
 * writes and to cause stray reads to appear as bugs.
 */
extern int piw_malloc_padding_fill;


/*(c piw_malloc_check_filled)
 * int piw_malloc_check_filled;
 *
 * If not 0, and if `piw_malloc_padding_fill' or
 * `piw_malloc_block_fill' is also not 0, then malloc-related
 * functions examine padding areas or (and) freed memory for signs of
 * stray writes by comparing the values stored there to those filled
 * in by the malloc library itself.
 */
extern int piw_malloc_check_filled;


/*(c piw_malloc_internals_check)
 * int piw_malloc_internals_check;
 *
 * If set to `N', where `N != 0', then perform a sanity check of PIW's
 * data structures at every `N'th opportunity to do so.  Such checks
 * are expensive, so setting `piw_malloc_internals_check' to 1 can
 * slow down a program considerably.
 */
extern int piw_malloc_internals_check;


/****************************************************************
 * (h2 "Publicly Visable Allocator State")
 * 
 */

/*(c piw_malloc_heap_start)
 * void * piw_malloc_heap_start;
 * 
 * The lowest address of the heap as seen by malloc-related functions.
 */
void * piw_malloc_heap_start = 0;

/*(c piw_malloc_heap_end)
 * void * piw_malloc_heap_end;
 * 
 * One byte beyond the highest address of the heap as seen by
 * malloc-related functions.
 */
void * piw_malloc_heap_end = 0;


/****************************************************************
 * Private State
 */

static int sbrk_direction = 0;				/* does the heap grow up (1) or down (-1)? */
static void * last_malloc_brk = 0;			/* the break where malloc left it */
static int internals_check_phase = 0;			/* counter for timing internal checks */

static long page_size = 0;				/* getpagesize(), memoized */
static int page_size_log2 = 0;				/* log2(page_size), memoized */
static int page_bucket = 0;				/* Which bucket holds blocks >= page_size? */
static long padding_size = 0;				/* How many padding bytes in each direction? */

enum malloc_constants
{
  /* Bucket N is for blocks of size 2**(4+N).
   * 
   * On a K bit machine, there are buckets for blocks
   * of size:
   *
   *	2**8 .. 2**(K-1)
   */
  n_bucket_sizes = sizeof (long) * 8 - 5,
};

static void * free_lists[n_bucket_sizes] = {0,};
static int n_blocks_allocated_for_free_list[n_bucket_sizes] = {0,};	/* Number of allocated blocks per-freelist */
static int n_blocks_overall_for_freelist[n_bucket_sizes] = {0,};	/* Total number of blocks per-freelist */
static int n_blocks_allocated = 0;					/* Number of allocated blocks of any size. */
static int n_blocks_overall = 0;					/* Total number of blocks of any size. */

bitset piw_malloc_arena_tags = 0;
void * piw_tagged_heap_start = 0;
void * piw_tagged_heap_end = 0;


/****************************************************************
 * Block Headers
 *
 * While a block is allocated, its link holds a hash value
 * derived from the address and size of the block. 
 * It is possible to recover the size of the block given that
 * hash value and the block address.
 *
 *	While allocated:
 *
 *	  block.h.hash = n_bytes ^ scramble((unsigned long)&block)
 *	  block.xtra.hash = n_bytes ^ scramble2((unsigned long)&block)
 *
 *	where `n_bytes' is the amount of memory requested by the
 *	caller of malloc.
 *
 * So:
 *
 *		n_bytes == (block.h.hash ^ scramble ((unsigned long)&block))
 *		        == (block.xtra.hash ^ scramble2 ((unsigned long)&block))
 *
 */

#ifndef __GCC__
#define __inline__
#endif

static __inline__ unsigned long
combine (unsigned long a, unsigned long b, unsigned long c, unsigned long d)
{
  return (  ((((a << 6) ^ (b >> 2) ^ (c << 2) ^ (d >> 4)) & 0xff) << 24)
	  | ((((a >> 7) ^ (b << 3) ^ (c >> 7) ^ (d >> 3)) & 0xff) << 16)
	  | ((((a << 2) ^ (b >> 4) ^ (c << 1) ^ (d >> 1)) & 0xff) << 8)
	  | (((a >> 2) ^ (b << 7) ^ (c >> 7) ^ (d << 2)) & 0xff));
}


static __inline__ unsigned long
combine2 (unsigned long a, unsigned long b, unsigned long c, unsigned long d)
{
  return (  ((((a << 7) ^ (b >> 2) ^ (c << 2) ^ (d >> 7)) & 0xff) << 24)
	  | ((((a >> 1) ^ (b << 1) ^ (c >> 2) ^ (d >> 4)) & 0xff) << 16)
	  | ((((a << 7) ^ (b >> 3) ^ (c << 7) ^ (d >> 3)) & 0xff) << 8)
	  | (((a >> 2) ^ (b << 4) ^ (c >> 6) ^ (d << 2)) & 0xff));
}


static __inline__ unsigned long
scramble (unsigned long addr)
{
  return combine(((addr)>>24) & 0xff, ((addr)>>16) & 0xff, ((addr)>>8) & 0xff, (addr) & 0xff);
}


static __inline__ unsigned long
scramble2 (unsigned long addr)
{
  return combine2(((addr)>>24) & 0xff, ((addr)>>16) & 0xff, ((addr)>>8) & 0xff, (addr) & 0xff);
}


static __inline__ unsigned long
block_hash (long n_bytes, void * block_addr)
{
  return  (unsigned long)n_bytes ^ scramble ((unsigned long)block_addr);
}


static __inline__ unsigned long
block_hash2 (long n_bytes, void * block_addr)
{
  return  (unsigned long)n_bytes ^ scramble2 ((unsigned long)block_addr);
}


static __inline__ long
block_unhash (void * block_addr, unsigned long block_hash)
{
  return (long)(scramble ((unsigned long)block_addr) ^ (unsigned long)block_hash);
}


static __inline__ long
block_unhash2 (void * block_addr, unsigned long block_hash)
{
  return (long)(scramble2 ((unsigned long)block_addr) ^ (unsigned long)block_hash);
}


/****************************************************************
 * Initialization
 */

static void
init_malloc ()
{
  long blk_size;
  int bucket;
  int errn;

  /* Get parameters from the environment.
   */
  piw_init_flags_and_variables ();

  if (0 > set_malloc_padding (&errn, piw_malloc_block_pad))
    panic ("unable to initialize libc-malloc.c");

  if (piw_keep_tag_bits)
    {
      unsigned long n_bits;
      long bitmap_size;
      long allocation_size;
      char * mem;
      char * brk0;

      if (!piw_tag_map_range)
	piw_tag_map_range = 32;
      n_bits = piw_tag_map_range * 1024 * 1024;
      n_bits /= piw_bytes_per_tag_bit;
      bitmap_size = sizeof_bitset (n_bits);
      allocation_size = sizeof (bitset_subset) + bitmap_size;
      brk0 = sbrk (0);
      mem = sbrk (allocation_size);
      if (mem == (char *)-1)
	panic ("unable to allocate PIW tag bits");
      if (mem < brk0)
	sbrk_direction = -1;
      else
	sbrk_direction = 1;
      /* align mem on a subset boundry */
      mem += sizeof (bitset_subset) - ((unsigned long)mem & (sizeof (bitset_subset) - 1));
      piw_malloc_arena_tags = (bitset)mem;
      bitset_clear (n_bits, piw_malloc_arena_tags);
    }      

  /* memoize getpagesize
   */
  page_size = getpagesize();

  /* memoize log2 (page_size)
   */
  {
    long x;
    x = 1;
    page_size_log2 = 0;
    while (x < page_size)
      {
	x <<= 1;
	++page_size_log2;
      }
    if (x != page_size)
      panic ("page size is not a power of 2");
  }

  /* Which free-list has the smallest blocks >= than page_size?
   *
   * When searching for a free-list to fit a particular size,
   * this list is the starting point for sizes >= one page.
   */
  blk_size = 8;
  bucket = 0;
  while (blk_size < page_size)
    {
      blk_size <<= 1;
      bucket++;
    }
  page_bucket = bucket;

  /* Find out which direction the break moves.
   */
  {
    long unusable;
    void * a;
    void * b;

    a = sbrk (0);
    unusable = page_size - ((unsigned long)a & (page_size - 1));
    a = sbrk (unusable);				/* leak up to one page */
    b = sbrk (page_size);				/* leak one page */
    if (sbrk_direction > 0)
      last_malloc_brk = (void *)((char *)b + page_size);
    else
      last_malloc_brk = b;
    piw_malloc_heap_start = last_malloc_brk;
    piw_malloc_heap_end = last_malloc_brk;
  }

  if (piw_keep_tag_bits)
    {
      if (sbrk_direction < 0)
	{
	  piw_tagged_heap_end = last_malloc_brk;
	  piw_tagged_heap_start = (void *)((char *)last_malloc_brk - piw_tag_map_range * 1024 * 1024);
	}
      else
	{
	  piw_tagged_heap_end = (void *)((char *)last_malloc_brk + piw_tag_map_range * 1024 * 1024);
	  piw_tagged_heap_start = last_malloc_brk;
	}
    }
}


/************************************************************************
 *h2 "Allocation Functions")
 * 
 * The functions in this section do the work of `malloc', `realloc', and
 * `free'.   In addition, they perform various kinds of logging and error
 * checking.  
 *
 * The functions in this section return error codes when an error
 * occurs.  For a list of error codes, see xref:"PIW Allocation
 * Function Error Codes".
 */

/*c set_malloc_padding
 * int set_malloc_padding (int * errn, int n);
 * 
 * Set the amount of padding surrounding each block of allocated 
 * memory.  This is only permitted prior to the first `malloc'.
 *
 * The amount of padding is rounded up to a multiple of
 * `MACHINE_ALIGNMENT' (defined in `"hackerlab/machine/alignment.h"').
 *
 * Return 0 on success, -1 on error.
 * 
 */
static int
set_malloc_padding (int * errn, int n)
{
  if (page_size)
    {
      *errn = piw_etoolate;
      return -1;
    }
  padding_size = ((n + MACHINE_ALIGNMENT - 1) / MACHINE_ALIGNMENT);
  padding_size *= MACHINE_ALIGNMENT;
  return 0;
}


/*c piw_malloc)
 * void * piw_malloc (int * errn, long n_bytes);
 * 
 * Allocate at least `n_bytes' of storage and return a pointer to the
 * allocated memory.  Return 0 if the allocation request can not be
 * satisfied.
 *
 * All allocated regions are word-aligned.  Page-sized and larger
 * regions are page-aligned.
 * 
 * This function performs various kinds of error checking and logging:
 * 
 * If `piw_malloc_log' is not 0, log entries are generated on entry
 * and exit from this function.
 *
 * If the variable `piw_malloc_internals_check' is `N' (and `N' is not
 * 0) then on every `N'th call to `piw_malloc', `piw_realloc',
 * `piw_free', or `piw_allocation_size', scan the heap for signs of
 * stray writes.
 *
 * If `piw_malloc_log' is set and the free list containing
 * appropriately sized blocks appears to be corrupt, a log entry is
 * generated.  Regardless of `piw_malloc_log', if a free list is
 * corrupt, `piw_sanity_check_breakpoint' is called.
 *
 * If `piw_malloc_block_fill' and `piw_malloc_check_filled' are both
 * not 0, then this function expects the newly allocated region to
 * have been previously filled with a distinctive bit pattern.  The
 * region is checked for that pattern and if it is not found, an error
 * is logged and `piw_corrupted_fill_breakpoint' is called.
 *
 * If the padding size is not 0 and the variables
 * `piw_malloc_padding_fill' and `piw_malloc_check_filled' are set,
 * then this function expects the padding area surrounding the newly
 * allocated block to be filled with a distinctive bit patern.  The
 * padding areas are also checked for that pattern and if it is not
 * found, an error is logged and `piw_corrupted_padding_breakpoint' is
 * called.
 * 
 * If the padding size is not 0 and the variable
 * `piw_malloc_padding_fill' is set, the padding areas around the
 * newly allocated block are filled with a distinctive bit pattern.
 * 
 * If the variable `piw_keep_tag_bits' is set, the newly allocated
 * region is marked writable in a bitmap of the heap maintained for
 * use with write barriers.  (*XREF!*).
 *
 * Before returning, the function `piw_malloc_returns' is called with
 * the address of the newly allocated region as its argument (or 0 if
 * the allocation failed).
 */
void *
piw_malloc (int * errn, long n_bytes)
{
  long block_size;
  int bucket;
  void * block;
  void * foreign_heap_start[2];
  void * foreign_heap_end[2];

  if (page_size == 0)
    init_malloc ();

  if (piw_malloc_log)
    piw_log_malloc_call (n_bytes);

  if (piw_malloc_internals_check)
    {
      ++internals_check_phase;
      if (internals_check_phase >= piw_malloc_internals_check)
	{
	  internals_check_phase = 0;
	  piw_malloc_sanity_check ();
	}
    }

  bucket = which_bucket (&block_size, n_bytes);
  if (bucket == -1)
    {
      if (piw_malloc_log)
	piw_log_malloc (n_bytes, 0);
      *errn = piw_esize;
      return piw_malloc_returns (0);
    }

  block = free_lists[bucket];
  foreign_heap_start[0] = 0;
  foreign_heap_end[0] = 0;
  foreign_heap_start[1] = 0;
  foreign_heap_end[1] = 0;

  /* Conditions:
   *
   * bucket 		the freelist containing blocks sufficient for n_bytes
   * block_size 	total number of bytes available in blocks from that freelist
   * block		== free_lists[bucket] (possibly 0).
   * foreign_heap_start	== {0,0}; start of heap areas that malloc lost track of.
   * foreign_heap_end 	== {0,0}; end of heap areas that malloc lost track of.
   *
   * If that freelist is empty, refill it:
   */
  if (block == 0)
    {
      long requested_amt;
      long actual_amt;
      int n_blocks;
      void * core;
      int sbrk_pad;

      /* To be computed:
       *
       * requested_amt	the argument to sbrk.
       *		For a small block_size, we sbrk several blocks at once.
       *		For a large block size, we try to sbrk one block at a time
       *		  but might wind up with more.
       */
      if (block_size < page_size)
	requested_amt = 2 * page_size;
      else
	{
	  /* Pad the amount to sbrk so that the user area
	   * of the block can be page aligned.
	   */
	  sbrk_pad = page_size - ((SIZEOF_MALLOC_HEADER + padding_size) & (page_size - 1));
	  if (sbrk_pad == page_size)
	    sbrk_pad = 0;
	  requested_amt = sbrk_pad + block_size;
	}

      core = page_aligned_sbrk (&actual_amt, foreign_heap_start, foreign_heap_end, requested_amt);

      if (core == (void *)-1)
	{
	  if (piw_malloc_log)
	    piw_log_malloc (n_bytes, 0);
	  *errn = piw_espace;
	  return piw_malloc_returns (0);
	}

      /* Conditions:
       *
       * core			the memory returned by sbrk.
       * actual_amt		the size of that memory
       * bucket			the free list bucket on which to store that memory
       * free_lists[bucket]	0
       * foreign_heap_start[]	the beginning of heap spaces not owned by malloc
       * foreign_heap_end[]	the end of heap spaces not owned by malloc
       * bucket			the free list bucket on which to store that memory
       * block_size 		total number of bytes available per block from that free list bucket
       * sbrk_pad		if block_size > page_size, the offset from the start of an
       *			  aligned page to the malloc header to page-align the user
       *			  area.
       *
       * To be computed:
       *
       * free_lists[bucket]			a linked list of the new blocks.
       * n_blocks_overall_for_freelist[bucket]	update to count the new blocks
       * n_blocks_overall			update to count the new blocks
       * block					the first element of that list (== free_lists[bucket])
       *
       * Also, if memory tags are being kept, regions of the heap that malloc lost
       * at the last sbrk must be marked as writable.
       */

      if (piw_malloc_block_fill)
	{
	  /* If free blocks are supposed to be filled, fill the new
	   * heap space.
	   */
	  fill_in_region ((t_uchar *)core, actual_amt);
	}

      /* Build a free list from the new memory.
       */
      if (block_size < page_size)
	{
	  n_blocks = actual_amt / block_size;
	  n_blocks_overall_for_freelist[bucket] += n_blocks;
	  n_blocks_overall += n_blocks;
	  free_lists[bucket] = core;
	  while (--n_blocks)
	    {
	      void * next_block;
	      next_block = (void *)((char *)core + block_size);
	      ((struct malloc_header *)core)->h.link = next_block;
	      ((struct malloc_header *)core)->xtra.link = next_block;
	      core = next_block;
	    }
	}
      else
	{
	  core = (char *)core + sbrk_pad;
	  actual_amt -= sbrk_pad;
	  free_lists[bucket] = core;
	  n_blocks_overall_for_freelist[bucket] += 1;
	  while (1)
	    {
	      int bytes_after_header_and_padding;
	      int bytes_in_last_page;
	      int last_page_overlaps;
	      int next_core_offset;
	      int amt_in_next_core;
	      void * next_block;

	      bytes_after_header_and_padding = block_size - (SIZEOF_MALLOC_HEADER + padding_size);
	      
	      bytes_in_last_page = bytes_after_header_and_padding & (page_size - 1);

	      last_page_overlaps = (bytes_in_last_page <= sbrk_pad);

	      next_core_offset = (  SIZEOF_MALLOC_HEADER
				  + padding_size
				  + (last_page_overlaps
				     ? pages_floor (bytes_after_header_and_padding)
				     : pages_ceiling (bytes_after_header_and_padding)));

	      amt_in_next_core = actual_amt - next_core_offset;

	      if (amt_in_next_core < requested_amt)
		break;

	      next_block = (void *)((char *)core + next_core_offset + sbrk_pad);
	      ((struct malloc_header *)core)->h.link = next_block;
	      ((struct malloc_header *)core)->xtra.link = next_block;
	      core = next_block;
	      actual_amt -= (next_core_offset + sbrk_pad);
	      ++n_blocks_overall_for_freelist[bucket];
	    }
	}
      ((struct malloc_header *)core)->h.link = 0;
      ((struct malloc_header *)core)->xtra.link = 0;
      block = free_lists[bucket];
    }

  /* Free storage exists of the requested size.
   *
   * bucket 			the freelist containing the requested block
   * block_size 		total number of bytes available in blocks from that freelist
   * block 			== free_lists[bucket]
   * foreign_heap_start[]	the beginning of heap spaces not owned by malloc (or 0)
   * foreign_heap_end[]		the end of heap spaces not owned by malloc (or 0)
   */
  if (piw_malloc_block_fill && piw_malloc_check_filled)
    {
      t_uchar * pos;
      pos = (t_uchar *)block + SIZEOF_MALLOC_HEADER;
      check_filled_region (block, pos,  block_size - SIZEOF_MALLOC_HEADER);
    }

  /* Remove block from the freelist.   Record the size
   * of the allocated region.
   */
  if (((struct malloc_header *)block)->h.link != ((struct malloc_header *)block)->xtra.link)
    {
      if (piw_malloc_log)
	piw_log_bogus_malloc_meta_data (block);
      piw_sanity_check_breakpoint ("corrupt malloc header detected in piw_malloc");
    }
  free_lists[bucket] = ((struct malloc_header *)block)->h.link;
  ((struct malloc_header *)block)->h.hash = block_hash (n_bytes, block);
  ((struct malloc_header *)block)->xtra.hash = block_hash2 (n_bytes, block);

  /* Fill in padding areas, if requested:
   */
  if (padding_size && piw_malloc_padding_fill)
    {
      fill_in_padding ((t_uchar *)block + SIZEOF_MALLOC_HEADER);
      fill_in_padding ((t_uchar *)block + SIZEOF_MALLOC_HEADER + padding_size + n_bytes);
    }

  /* Update the count of the number of blocks allocated.
   */
  n_blocks_allocated_for_free_list[bucket] += 1;			/* Of this particular size. */
  ++n_blocks_allocated;							/* And overall. */

  /* Return a pointer to the user-data area within the block.
   */
  {
    void * answer;
    answer = (void *)((char *)block + SIZEOF_MALLOC_HEADER + padding_size);

    if (piw_malloc_log)
      piw_log_malloc (n_bytes, answer);

    /* Mark this region, and any regions that someone other than malloc got from sbrk,
     * as writable.
     */
    if (piw_keep_tag_bits)
      {
	piw_mark_writable (answer, n_bytes);
	if (foreign_heap_start[0])
	  {
	    piw_mark_writable (foreign_heap_start[0], (unsigned long)foreign_heap_end[0] - (unsigned long)foreign_heap_start[0]);
	    if (foreign_heap_start[1])
	      piw_mark_writable (foreign_heap_start[1], (unsigned long)foreign_heap_end[1] - (unsigned long)foreign_heap_start[1]);
	  }
      }
    return piw_malloc_returns (answer);
  }
}


/*c piw_realloc)
 * void * piw_realloc (int * errn, void * ublock, long n_bytes);
 * 
 * Allocate at least `n_bytes' of storage and return a pointer to the
 * allocated memory.  `block' may be 0 or a previously allocated block
 * of memory.  If `block' already points to at least `n_bytes' of
 * allocated storage, `block' may be returned with no new allocation
 * taking place.  Otherwise, if allocation succeeds, data is copied
 * from `block' to the new region and `block' is freed.  If allocation
 * fails, 0 is returned and `block' is unchanged.
 * 
 * This function performs various kinds of error checking and logging:
 * All of the checks performed by `piw_malloc' are also performed by
 * this function.  In addition:
 * 
 * If `piw_malloc_log' is not 0, log entries are generated on entry to
 * and exit from this function.
 *
 * If `block' is neither 0 nor a previously allocated region, an error
 * is logged and `piw_bogus_realloc_breakpoint' is called.  0 is returned
 * from this function.
 * 
 * Before returning, the function `piw_realloc_returns' is called with
 * the address of the newly allocated region as its argument (or 0 if
 * the allocation failed).
 */
void *
piw_realloc (int * errn, void * ublock, long n_bytes)
{
  void * block;
  long old_n_bytes;
  long old_block_size;
  int old_bucket;
  long block_size;
  int ideal_bucket;
  void * answer;

  if (page_size == 0)
    init_malloc ();

  if (piw_malloc_log)
    piw_log_realloc_call (ublock, n_bytes);

  if (piw_malloc_internals_check)
    {
      ++internals_check_phase;
      if (internals_check_phase >= piw_malloc_internals_check)
	{
	  internals_check_phase = 0;
	  piw_malloc_sanity_check ();
	}
    }

  if (!ublock)
    {
      void * answer;
      answer = piw_malloc (errn, n_bytes);
      if (piw_malloc_log)
	piw_log_realloc (ublock, n_bytes, answer);
      return piw_realloc_returns (answer);
    }

  if (  ((unsigned long)ublock & (sizeof (long) - 1))
      || (ublock < piw_malloc_heap_start)
      || (ublock >= piw_malloc_heap_end))
    goto bad_block;

  block = (void *)((char *)ublock - padding_size - SIZEOF_MALLOC_HEADER);
  old_n_bytes = block_unhash (block, ((struct malloc_header *)block)->h.hash);
  old_bucket = which_bucket (&old_block_size, old_n_bytes);
  if (   (old_n_bytes != block_unhash2 (block, ((struct malloc_header *)block)->xtra.hash))
      || (old_bucket == -1)
      || !n_blocks_allocated_for_free_list[old_bucket])
    {
    bad_block:
      if (piw_malloc_log)
	piw_log_bogus_realloc (ublock);
      piw_bogus_realloc_breakpoint (ublock);
      if (piw_malloc_log)
	piw_log_realloc (ublock, n_bytes, 0);
      *errn = piw_emalloc_header;
      return piw_realloc_returns (0);
    }

  if (padding_size && piw_malloc_padding_fill && piw_malloc_check_filled)
    {
      check_padding (ublock, (t_uchar *)block + SIZEOF_MALLOC_HEADER);
      check_padding (ublock,
		     (t_uchar *)block + SIZEOF_MALLOC_HEADER + padding_size + old_n_bytes);
    }

  ideal_bucket = which_bucket (&block_size, n_bytes);

  if (ideal_bucket == old_bucket)
    {
      answer = ublock;
      if (piw_malloc_padding_fill)
	{
	  if (padding_size)
	    fill_in_padding ((t_uchar *)answer + n_bytes);
	}
      if (piw_keep_tag_bits)
	{
	  if (old_n_bytes < n_bytes)
	    piw_mark_writable ((void *)((char *)ublock + old_n_bytes), n_bytes - old_n_bytes);
	  else if (n_bytes < old_n_bytes)
	    {
	      int writable_start;
	      int writable_len;

	      writable_start = n_bytes - piw_bytes_per_tag_bit;
	      if (writable_start >= 0)
		{
		  writable_len = piw_bytes_per_tag_bit;
		}
	      else
		{
		  writable_start = 0;
		  writable_len = n_bytes;
		}
	      /* In addition to marking part of the block unreadable, the end of the
	       * block must be re-marked writable, because at the granularity of the
	       * the tag map, the unreadable region might overlap with the part of the
	       * block that is still writable.
	       */
	      piw_mark_unreadable ((void *)((char *)ublock + n_bytes), old_n_bytes - n_bytes);
	      piw_mark_writable ((void *)((char *)ublock + writable_start), writable_len);
	    }
	}
    }
  else
    {
      answer = piw_malloc (errn, n_bytes);
      if (!answer)
	{
	  if (piw_malloc_log)
	    piw_log_realloc (ublock, n_bytes, 0);
	  return piw_realloc_returns (0);
	}
      mem_move (answer, ublock, (n_bytes < old_n_bytes) ? n_bytes : old_n_bytes);
      if (0 > piw_free (errn, ublock))
	{
	  int x;
	  piw_free (&x, answer);
	  if (piw_malloc_log)
	    piw_log_realloc (ublock, n_bytes, 0);
	  return piw_realloc_returns (0);
	}
    }

  {
    void * new_block;
    new_block = (void *)(answer - padding_size - SIZEOF_MALLOC_HEADER);
    ((struct malloc_header *)new_block)->h.hash = block_hash (n_bytes, new_block);
    ((struct malloc_header *)new_block)->xtra.hash = block_hash2 (n_bytes, new_block);
  }

  if (piw_malloc_log)
    piw_log_realloc (ublock, n_bytes, answer);
  return piw_realloc_returns (answer);
}



/*c piw_free)
 * int piw_free (int * errn, void * ublock);
 * 
 * Free `block', a previously allocated block of memory.
 * 
 * This function performs various kinds of error checking and logging:
 * 
 * If `piw_malloc_log' is not 0, log entries are generated on entry to
 * and exit from this function.
 * 
 * If the variable `piw_malloc_internals_check' is `N' (and `N' is not
 * 0) then on every `N'th call to `piw_malloc', `piw_realloc',
 * `piw_free', or `piw_allocation_size', scan the heap for signs of
 * stray writes.
 *
 * If the padding size is not 0 and the variables
 * `piw_malloc_padding_fill' and `piw_malloc_check_filled' are set,
 * then this function expects the padding area surrounding the newly
 * freed block to be filled with a distinctive bit patern.  The
 * padding areas are checked for that pattern and if it is not found,
 * an error is logged and `piw_corrupted_padding_breakpoint' is
 * called.
 * 
 * If the variable `piw_keep_tag_bits' is set, the newly freed region
 * is marked unwritable in a bitmap of the heap maintained for use
 * with write barriers. (*XREF!*).
 * 
 * If `block' is neither 0 nor a previously allocated region, an error
 * is logged and `piw_bogus_free_breakpoint' is called.  0 is returned
 * from this function.
 */
int
piw_free (int * errn, void * ublock)
{
  void * block;
  long n_bytes;
  long block_size;
  int bucket;

  if (page_size == 0)
    init_malloc ();

  if (piw_malloc_log)
    piw_log_free_call (ublock);

  if (piw_malloc_internals_check)
    {
      ++internals_check_phase;
      if (internals_check_phase >= piw_malloc_internals_check)
	{
	  internals_check_phase = 0;
	  piw_malloc_sanity_check ();
	}
    }

  if (ublock == 0)
    {
      if (piw_malloc_log)
	piw_log_free (ublock);
      return 0;
    }

  if (  ((unsigned long)ublock & (sizeof (long) - 1))
      || (ublock < piw_malloc_heap_start)
      || (ublock >= piw_malloc_heap_end))
    goto bad_block;

  block = (void *)((char *)ublock - padding_size - SIZEOF_MALLOC_HEADER);
  n_bytes = block_unhash (block, ((struct malloc_header *)block)->h.hash);
  bucket = which_bucket (&block_size, n_bytes);
  if (   (n_bytes != block_unhash2 (block, ((struct malloc_header *)block)->xtra.hash))
      || (bucket == -1)
      || !n_blocks_allocated_for_free_list[bucket])
    {
    bad_block:
      if (piw_malloc_log)
	piw_log_bogus_free (ublock);
      piw_bogus_free_breakpoint (ublock);
      if (piw_malloc_log)
	piw_log_free (ublock);
      *errn = piw_emalloc_header;
      return -1;
    }

  if (padding_size && piw_malloc_padding_fill && piw_malloc_check_filled)
    {
      check_padding (ublock, (t_uchar *)block + SIZEOF_MALLOC_HEADER);
      check_padding (ublock, (t_uchar *)block + SIZEOF_MALLOC_HEADER + padding_size + n_bytes);
    }

  if (piw_keep_tag_bits)
    piw_mark_unreadable (ublock, n_bytes);

  ((struct malloc_header *)block)->h.link = free_lists[bucket];
  ((struct malloc_header *)block)->xtra.link = free_lists[bucket];
  free_lists[bucket] = block;
  n_blocks_allocated_for_free_list[bucket] -= 1;
  --n_blocks_allocated;
  if (piw_malloc_block_fill)
    {
      t_uchar * pos;
      pos = (t_uchar *)block + SIZEOF_MALLOC_HEADER;
      fill_in_region (pos, block_size - SIZEOF_MALLOC_HEADER);
    }
  if (piw_malloc_log)
    piw_log_free (ublock);
  return 0;
}



/*c piw_allocation_size)
 * long piw_allocation_size (int * errn, void * ublock);
 * 
 * Return the number of bytes allocated for `ublock' which should be
 * either 0 or a region previously returned from `piw_malloc' or
 * `piw_realloc'.
 * 
 * If the header which contains the size of the block appears to have
 * been overwritten, an error is logged, `piw_sanity_check_breakpoint'
 * is called, and this function returns -1.
 *
 * If the variable `piw_malloc_internals_check' is `N' (and `N' is not
 * 0) then on every `N'th call to `piw_malloc', `piw_realloc',
 * `piw_free', or `piw_allocation_size', scan the heap for signs of
 * stray writes.
 * 
 */
long
piw_allocation_size (int * errn, void * ublock)
{
  void * block;
  long n_bytes;

  if (page_size == 0)
    init_malloc ();

  if (piw_malloc_internals_check)
    {
      ++internals_check_phase;
      if (internals_check_phase >= piw_malloc_internals_check)
	{
	  internals_check_phase = 0;
	  piw_malloc_sanity_check ();
	}
    }

  if (ublock == 0)
    return 0;

  block = (void *)((char *)ublock - padding_size - SIZEOF_MALLOC_HEADER);
  n_bytes = block_unhash (block, ((struct malloc_header *)block)->h.hash);
  if (n_bytes != block_unhash2 (block, ((struct malloc_header *)block)->xtra.hash))
    {
      if (piw_malloc_log)
	piw_log_bogus_malloc_meta_data (ublock);
      piw_sanity_check_breakpoint ("corrupt block header in piw_allocation_size");
      *errn = piw_emalloc_header;
      return -1;
    }
  return n_bytes;
}


/************************************************************************
 *(h2 "Allocation Function Return Breakpoints")
 * 
 * The functions in this section are called after allocation and
 * before returning to the calling function.  They are useful with an
 * interactive debugger for setting breakpoints that are reached
 * whenever an allocation occurs, or when the allocation of a specific
 * region occurs.
 */

/*(c piw_malloc_returns)
 * void * piw_malloc_returns (void * block);
 * 
 * This function is called before returning from `malloc'.
 * `block' is the value that will be returned from `malloc'.
 */
void *
piw_malloc_returns (void * block)
{
  /*c
   * void * piw_malloc_returns (void * block)
   *
   * Return `block'.
   *
   * This function is called when `piw_malloc'
   * is about to return `block'.
   */
  return block;
}


/*(c piw_realloc_returns)
 * void * piw_realloc_returns (void * block);
 * 
 * This function is called before returning from `realloc'.
 * `block' is the value that will be returned from `piwrealloc'.
 */
void *
piw_realloc_returns (void * block)
{
  /*c
   * void * piw_realloc_returns (void * block)
   *
   * Return `block'.
   *
   * This function is called when `piw_realloc'
   * is about to return `block'.
   */
  return block;
}

/************************************************************************
 *(h2 "Allocation Error Breakpoints")
 * 
 * The functions in this section are called when an error is detected
 * in one of the PIW allocation functions.  They are useful with an
 * interactive debugger for setting breakpoints that are reached
 * whenever an allocation error occurs.
 */


/*(c piw_bogus_free_breakpoint)
 * void piw_bogus_free_breakpoint (void * block);
 * 
 * This function is called whenever PIW detects an attempt to free a
 * block of memory that does not appear to have been returned by a
 * previous allocation.  Either the block was not previously
 * allocated, or was corrupted by a stray write, or malloc's
 * internal data structures were corrupted.
 * 
 * `block' is the region of memory that was passed to `piw_free'.
 * 
 * This function calls `piw_msg_breakpoint'.  See xref:"Using GDB With
 * PIW".
 */
void
piw_bogus_free_breakpoint (void * block)
{
  /*c
   * void piw_bogus_free_breakpoint (void * block)
   *
   * This breakpoint is reached if `free' is passed a block
   * of memory that does not seem to have been allocated by
   * `malloc' or `realloc'.
   *
   * The bogus block is passed as the parameter `block'.
   */
  piw_msg_breakpoint ("attempt to free non-allocated block");
}


/*(c piw_bogus_realloc_breakpoint)
 * void piw_bogus_realloc_breakpoint (void * block);
 * 
 * This function is called whenever PIW detects an attempt to
 * reallocate a block of memory that does not appear to have been
 * returned by a previous allocation.  The block was not previously
 * allocated, or the block was corrupted by a stray write, or malloc's
 * internal data structures were corrupted.
 * 
 * `block' is the region of memory that was passed to `realloc'.
 * 
 * This function calls `piw_msg_breakpoint'.  See xref:"Using GDB With
 * PIW".
 */
void
piw_bogus_realloc_breakpoint (void * block)
{
  /*c
   * void piw_bogus_realloc_breakpoint (void * block)
   *
   * This breakpoint is reached if `realloc' is passed a block
   * of memory that does not seem to have been allocated by
   * `malloc' or `realloc'.
   *
   * The bogus block is passed as the parameter `block'.
   */
  piw_msg_breakpoint ("attempt to realloc non-allocated block");
}


/*(c piw_corrupted_padding_breakpoint)
 * void piw_corrupted_padding_breakpoint (void * block,
 *                                        t_uchar * location);
 * 
 * This function is called whenever PIW detects an overwrite
 * to a padding area surrounding an allocated or freed block
 * of memory.
 * 
 * `block' is the region of memory and `location' the first
 * overwritten address of the padding.
 * 
 * This function calls `piw_msg_breakpoint'.  See xref:"Using GDB With
 * PIW".
 */
void
piw_corrupted_padding_breakpoint (void * block,
				  t_uchar * location)
{
  /*c
   * piw_corrupted_padding_breakpoint (void * block, 
   *				       t_uchar * location)
   *
   * This breakpoint is reached if a stray write is detected
   * by examining the padding surrounding a region allocated
   * by `malloc' or `realloc'.
   *
   * The allocated block associated with this stray write is
   * `block'.  The exact location of the corrupted padding is 
   * `location'.
   */
  piw_msg_breakpoint ("overwrite of malloc padding detected");
}


/*(c piw_corrupted_fill_breakpoint)
 * void piw_corrupted_fill_breakpoint (void * block,
 *                                     t_uchar * location);
 * 
 * This function is called whenever PIW detects an overwrite
 * to a previously freed block of memory.
 * 
 * `block' is the region of memory and `location' the first
 * overwritten address of the block.
 * 
 * This function calls `piw_msg_breakpoint'.  See xref:"Using GDB With
 * PIW".
 */
void
piw_corrupted_fill_breakpoint (void * block,
			       t_uchar * location)
{
  /*c
   * piw_corrupted_fill_breakpoint (void * block, 
   *				    t_uchar * location)
   *
   * This breakpoint is reached if a stray write is detected
   * by examining a free region filled by `malloc', `realloc',
   * or `free'.
   *
   * The allocated block associated with this stray write is
   * `block'.  The exact location of the corrupted filling is 
   * `location'.
   */
  piw_msg_breakpoint ("overwrite of freed region detected");
}



/*(c piw_sanity_check_breakpoint)
 * void piw_sanity_check_breakpoint (char * msg);
 * 
 * This breakpoint is reached if a malloc-related function discovers
 * corruption of the allocator's internal data structures.
 *
 * The parameter `msg' briefly describes the corruption.
 * 
 * This function calls `piw_msg_breakpoint'.  See xref:"Using GDB With
 * PIW".
 */
void
piw_sanity_check_breakpoint (char * msg)
{
  /*c
   * void piw_sanity_check_breakpoint (char * msg)
   *
   * This breakpoint is reached if `piw_malloc_sanity_check' discovers
   * corruption of malloc's internal data structures.
   *
   * The parameter `msg' briefly describes the corruption.
   * 
   * This function is called from `piw_malloc_sanity_check', so 
   * examining the caller's frame from a symbolic debugger may be
   * informative.
   */
  panic_msg (msg);
  piw_msg_breakpoint ("corruption of malloc internals detected");
}


/************************************************************************
 *h2 "Heap Map Tag Bits")
 * 
 * 
 */

/* static __inline__ int
 * piw_malloc_heap_tag_index (void * addr);
 *
 * Return the PIW tag bitmap address (bit number) of `addr'.
 */
static __inline__ int
piw_malloc_heap_tag_index (void * addr)
{
  return (((unsigned long)addr - (unsigned long)piw_tagged_heap_start) / piw_bytes_per_tag_bit);
}

static void
piw_tags_map_overflow ()
{
  static int overflow = 0;
  if (!overflow)
    {
      piw_log_malloc_tags_map_overflow ();
      overflow = 1;
    }
}

/*c piw_mark_writable)
 * void piw_mark_writable (void * memory, long extent);
 * 
 * Mark the `extent'-byte region beginning at `memory' as writable in
 * bitmap of heap tags.  (See xref:"piw_keep_tag_bits".)  Allocated 
 * regions are automatically marked writable.
 */
void
piw_mark_writable (void * memory, long extent)
{
  if (   (memory >= piw_tagged_heap_end)
      || (((char *)memory + extent) < (char *)piw_tagged_heap_start))
    {
      piw_tags_map_overflow ();
      return;
    }
  if (memory < piw_tagged_heap_start)
    {
      piw_tags_map_overflow();
      memory = piw_tagged_heap_start;
    }
  if (((char *)memory + extent) >= (char *)piw_tagged_heap_end)
    {
      piw_tags_map_overflow();
      extent = ((char *)piw_tagged_heap_end - (char *)memory) - 1;
    }
  bitset_fill_range (piw_malloc_arena_tags,
		     piw_malloc_heap_tag_index(memory),
		     piw_malloc_heap_tag_index((char *)memory + extent - 1) + 1);
}




/*c piw_mark_unreadable)
 * void piw_mark_unreadable (void * memory, long extent);
 * 
 * Mark the `extent'-byte region beginning at `memory' as unreadable
 * (and unwritable) in bitmap of heap tags.  (See
 * xref:"piw_keep_tag_bits".)  Freed regions are automatically marked
 * unreadable.
 */
void
piw_mark_unreadable (void * memory, long extent)
{
  if (   (memory >= piw_tagged_heap_end)
      || (((char *)memory + extent) < (char *)piw_tagged_heap_start))
    {
      piw_tags_map_overflow ();
      return;
    }
  if (memory < piw_tagged_heap_start)
    {
      piw_tags_map_overflow();
      memory = piw_tagged_heap_start;
    }
  if (((char *)memory + extent) >= (char *)piw_tagged_heap_end)
    {
      piw_tags_map_overflow();
      extent = ((char *)piw_tagged_heap_end - (char *)memory) - 1;
    }
  bitset_clear_range (piw_malloc_arena_tags,
		      piw_malloc_heap_tag_index(memory),
		      piw_malloc_heap_tag_index((char *)memory + extent - 1) + 1);
}



/************************************************************************
 *h2 "Allocation Heap Sanity Checks")
 * 
 * 
 * 
 */

/*c piw_malloc_sanity_check)
 * int piw_malloc_sanity_check ();
 * 
 * Examine the malloc heap for signs of corruption by stray writes.
 * If corruption is found, an error is logged and
 * `piw_sanity_check_breakpoint' is called.
 */
int
piw_malloc_sanity_check ()
{
  int n_blocks;
  int x;

  n_blocks = 0;
  for (x = 0; x < n_bucket_sizes; ++x)
    {
      struct malloc_header * freelist;
      struct malloc_header ** location;
      int expected_in_freelist;
      int found_in_freelist;

      n_blocks += n_blocks_allocated_for_free_list[x];
      freelist = (struct malloc_header *)free_lists[x];
      location = (struct malloc_header **)&free_lists[x];
      expected_in_freelist = n_blocks_overall_for_freelist[x] - n_blocks_allocated_for_free_list[x];
      if (expected_in_freelist < 0)
	{
	  piw_malloc_internals_check = 0;
	  if (piw_malloc_log)
	    piw_log_bogus_malloc_meta_data (0);
	  piw_sanity_check_breakpoint ("statistics corrupt for one of the free lists");
	  return -1;
	}
      found_in_freelist = 0;

      while (freelist)
	{
	  if (   ((long)freelist & (sizeof (long) - 1))
	      || (freelist->h.link != freelist->xtra.link))
	    {
	      piw_malloc_internals_check = 0;
	      if (piw_malloc_log)
		piw_log_bogus_malloc_meta_data ((void *)location);
	      piw_sanity_check_breakpoint ("link pointer corrupt for a block on one of the free lists");
	      return -1;
	    }
	  ++found_in_freelist;
	  location = (struct malloc_header **)&freelist->h.link;
	  freelist = freelist->h.link;
	}
      if (found_in_freelist != expected_in_freelist)
	{
	  piw_malloc_internals_check = 0;
	  if (piw_malloc_log)
	    piw_log_bogus_malloc_meta_data (0);
	  piw_sanity_check_breakpoint ("number of blocks found on one of the free lists does not agree with allocation statistics");
	  return -1;
	}
    }

  if (n_blocks != n_blocks_allocated)
    {
      piw_malloc_internals_check = 0;
      if (piw_malloc_log)
	piw_log_bogus_malloc_meta_data (0);
      piw_sanity_check_breakpoint ("number of blocks found on all free lists does not agree with allocation statistics");
      return -1;
    }
  return 0;
}



/****************************************************************
 * Page-aligned sbrk
 */

/* long pages_ceiling (long amt)
 *
 * Return `amt' rounded up to the nearest multiple of page_size.
 */
static long
pages_ceiling (long amt)
{
  return (amt + page_size - 1) & ~(page_size - 1);
}

/* long pages_floor (long amt)
 *
 * Return `amt' rounded down to the nearest multiple of page_size.
 */
static long
pages_floor (long amt)
{
  return amt & ~(page_size - 1);
}


/* void * sbrk_for_malloc (long amt)
 *
 * Like sbrk, except that it also updates `piw_malloc_heap_start',
 * and `piw_malloc_heap_end'.
 */
static void *
sbrk_for_malloc (long amt, void ** foreign_start, void ** foreign_end)
{
  void * answer;
  void * inferred_last_brk;
  void * new_brk;
  

  answer = sbrk (amt);
  if (answer == (void *)-1)
    return answer;
  if (sbrk_direction < 0)
    piw_malloc_heap_start = answer;
  else
    piw_malloc_heap_end = (void *)((char *)answer + amt);
  
  /* Compute
   *
   * inferred_last_brk		Where the break must have been before we called sbrk.
   * new_brk			Where the break is after we called sbrk.
   */
  if (sbrk_direction > 0)
    {
      inferred_last_brk = answer;
      new_brk = (void *)((char *)answer + amt);
    }
  else
    {
      inferred_last_brk = (void *)((char *)answer + amt);
      new_brk = answer;
    }
  
  /* If someone else called sbrk besides malloc,
   * or if we lost memory while getting an aligned
   * block, we can detect that here and note what part of 
   * the heap malloc lost.
   */
  if (inferred_last_brk != last_malloc_brk)
    {
      if (sbrk_direction > 0)
	{
	  *foreign_start = last_malloc_brk;
	  *foreign_end = inferred_last_brk;
	}
      else
	{
	  *foreign_start = inferred_last_brk;
	  *foreign_end = last_malloc_brk;
	}
    }
  else
    {
      *foreign_start = 0;
      *foreign_end = 0;
    }

  last_malloc_brk = new_brk;

  return answer;
}


/* void * page_aligned_sbrk (long * actual_amount,
 *			     void * foreign_start[2],
 *			     void * foreign_end[2],
 * 			     long requested_amt)
 *
 * Perform an sbrk, but be sure to return page aligned memory.
 *
 * Also return the actual amount allocated, which may be larger than 
 * requested_amt.  The actual amount allocated may or may not be a multiple
 * of page_size.
 *
 * Also return up to two regions of the heap that malloc lost
 * because some other function (possibly a signal handler) called
 * sbrk.   These regions must be marked as writable if malloc is
 * keeping tag bits for the region.
 *
 * Like sbrk, return the allocated heap or ((void*)-1).
 */
static void *
page_aligned_sbrk (long * actual_amount,
		   void * foreign_start[2],
		   void * foreign_end[2],
		   long requested_amt)
{
  void * core;
  long real_amt;
  long unusable;

  real_amt = pages_ceiling (requested_amt);
  core = sbrk_for_malloc (real_amt, &foreign_start[0], &foreign_end[0]);

  if (core == (void *)-1)
    return core;

  /* Conditions:
   *
   * real_amt	How much new heap was allocated.
   * core	low address of that memory
   *
   * How much of that memory is unusable because it is
   * not page-aligned?
   */
  unusable = page_size - ((long)core & (page_size - 1));
  if (unusable == page_size)
    unusable = 0;
  
  /* If core is not page aligned, some memory will be
   * discarded.  This conditional does that and computes
   * new values for real_amt and core.  It may be necessary
   * to call sbrk a second time.
   */
  if (unusable)
    {
      if ((real_amt - unusable) < requested_amt)
	{
	  long additional_amt;
	  void * new_core;

	  /* After aligning core we won't have enough memory 
	   * to satisfy the request for requested_amt.
	   *
	   * We have to sbrk more.  Hopefully the second sbrk will
	   * return something that is contiguous with the first sbrk,
	   * but we don't count on it.
	   *
	   * additional_amt, the amount of the next sbrk, is enough to
	   * satisfy requested_amt no matter what sbrk returns (except
	   * -1).   If sbrk happens to return something contiguous to
	   * the previous sbrk, we combine the regions.  Otherwise,
	   * throw the first region away.
	   */
	  additional_amt = page_size + real_amt;
	  if (!foreign_start[0])
	    new_core = sbrk_for_malloc (additional_amt, &foreign_start[0], &foreign_end[0]);
	  else
	    new_core = sbrk_for_malloc (additional_amt, &foreign_start[1], &foreign_end[1]);
	  if (new_core == (void *)-1)
	    return new_core;

	  if (   ((sbrk_direction > 0) && (((char *)core + real_amt) == (char *)new_core))
	      || ((sbrk_direction < 0) && (((char *)new_core + additional_amt) == (char *)core)))
	    {
	      /* The first and second sbrk returned contiguous regions.
	       *
	       * Ensure that `core' points to the low address of the combined regions
	       * and `real_amt' contains the combined size.
	       */
	      if (sbrk_direction < 0)
		core = new_core;
	      real_amt += additional_amt;
	    }
	  else
	    {
	      /* The first and second sbrk returned non-contiguous regions.
	       * The second region is certain to be large enough.
	       * Simply discard the first region and act as though we'd
	       * asked for the second region in the first place.
	       */
	      real_amt = additional_amt;
	      core = new_core;
	      unusable = page_size - ((long)core & (page_size - 1));
	      if (unusable == page_size)
		unusable = 0;
	    }
	}

      /* Even though we lost some memory to alignment,
       * we now have enough to satisfy the request.
       * Make it look like someone else called sbrk
       * and got the memory we have to throw away to 
       * be page aligned.
       */

      /* Conditions:
       *
       * core		address of sbrk memory (not page aligned).
       * real_amt	size of sbrk memory
       * unusable	how many bytes past core is the next page boundry?
       *
       * real_amt - unusable >= requested_amt
       */
      core = (void *)((char *)core + unusable);
      real_amt -= unusable;
    }

  /* Conditions:
   *
   * real_amt	How much new heap was allocated.
   * core	low address of that memory
   *
   * real_amt >= requested_amt
   * core is page aligned
   *
   * Tell the caller how much memory we are returning.
   */

  *actual_amount = real_amt;
  return core;
}


/****************************************************************
 * Padding
 *
 * When blocks are padded, padding areas are filled with hash
 * values derived from their location.  This is how those
 * values are computed.  The definition is biased towards 32 bit
 * machines, but will work on other types.
 *
 */

static __inline__ t_uchar
get_byte (unsigned long word, int byte_number)
{
  return (word >> (byte_number * 8)) & 0xff;
}


static __inline__ t_uchar
padding_fill_for_location (void * loc)
{
  unsigned long x;
  x = (unsigned long)loc;
  switch (x & 3)
    {
    case 0:	return get_byte (x, 1) ^ 0xc0;
    case 1:	return get_byte (x, 3) ^ 0xd1;
    case 2:	return get_byte (x, 0) ^ 0xf1;
    case 3:	return get_byte (x, 2) ^ 0xed;
    }
  while (1)
    panic ("compiler error in padding_fill_for_location"); /* shut up -Wall */
}


static void
fill_in_padding (t_uchar * padding)
{
  t_uchar * padding_bound;

  padding_bound = padding + padding_size;
  while (padding < padding_bound)
    {
      *padding = padding_fill_for_location (padding);
      ++padding;
    }
}


static int
check_padding (void * block, t_uchar * padding)
{
  t_uchar * padding_bound;

  padding_bound = padding + padding_size;
  while (padding < padding_bound)
    {
      if (*padding != padding_fill_for_location (padding))
	{
	  if (piw_malloc_log)
	    piw_log_bogus_malloc_padding (block, (void *)padding);
	  piw_corrupted_padding_breakpoint (block, (void *)padding);
	  return -1;
	}
      ++padding;
    }
  return 0;
}


static __inline__ t_uchar
fill_for_location (void * loc)
{
  unsigned long x;
  x = (unsigned long)loc;
  switch (x & 3)
    {
    case 0:	return get_byte (x, 1) ^ 0xd0;
    case 1:	return get_byte (x, 2) ^ 0xac;
    case 2:	return get_byte (x, 3) ^ 0x1d;
    case 3:	return get_byte (x, 0) ^ 0xad;
    }
  while (1)
    panic ("compiler error in padding_fill_for_location"); /* shut up -Wall */
}


static void
fill_in_region (t_uchar * region, int length)
{
  t_uchar * region_bound;

  region_bound = region + length;
  while (region < region_bound)
    {
      *region = fill_for_location (region);
      ++region;
    }
}


static int
check_filled_region (void * block, t_uchar * region, int length)
{
  t_uchar * region_bound;

  region_bound = region + length;
  while (region < region_bound)
    {
      if (*region != fill_for_location (region))
	{
	  if (piw_malloc_log)
	    piw_log_bogus_malloc_fill (block, (void *)region);
	  piw_corrupted_fill_breakpoint (block, (void *)region);
	  return -1;
	}
      ++region;
    }
  return 0;
}



/****************************************************************
 * Request Sizes -> Bucket Numbers
 */

/* which_bucket
 *
 * Compute which free-list to use for a given request size.
 *
 * Returns an index into free_lists and the size of blocks
 * in that list.  The "block_size" returned is the number of
 * bytes that follow the address pointed to by the freelist.
 * These bytes will eventually hold the block header, padding,
 * and the caller's part of the block.
 *
 * Freelists are kept for each block size in:
 *
 * 		2**4 .. 2**(sizeof (long) * 8 - 1)
 *
 * For small blocks, on free-list N where 2**(4+N) < page_size,
 * the actual size of each block is exactly 2**(4+N).  That means
 * that if the user asks for a block of exactly 2**(4+N) bytes,
 * it will come from a freelist holding blocks with 2**(5+N) bytes
 * or larger.  The extra space is used for the link pointer and
 * padding areas.
 *
 * For large blocks, on free-list N where 2**(4+N) >= page_size,
 * the actual size of each block is  
 *
 * 	(2**(4+N) + 2 * padding_size + SIZEOF_MALLOC_HEADER)
 *
 * The extra room for large blocks means that if someone asks to
 * allocate 2**(4+N) bytes (where 2**(4+N) >= page_size), they are
 * certain to get a block from the 2**(4+N) free-list instead of from
 * the 2**(5+N) free-list.
 *
 */
static int
which_bucket (long * block_size_p, long n_bytes)
{
  long block_size_req;
  long block_size;
  long extra;
  int bucket;

  /* block_size_req is the number of bytes needed to hold
   * n_bytes of caller data, plus the link pointer and padding.
   */
  block_size_req = SIZEOF_MALLOC_HEADER + n_bytes + 2 * padding_size;

  if (block_size_req < page_size)
    {
      block_size = 16;
      bucket = 0;
      extra = 0;
    }
  else
    {
      block_size = page_size;
      extra = SIZEOF_MALLOC_HEADER + 2 * padding_size;
      bucket = page_bucket;
    }

  /* Conditions
   *
   * bucket		an index into the array free_lists.
   *			If bucket B holds blocks large enough
   *			for an allocation request of `n_bytes',
   *			then B >= bucket.
   *
   * block_size 	is 2**(4+bucket) -- the size of blocks 
   *			on free_list[bucket], not counting
   *			the extra space given to large blocks:
   *
   * extra 		is:
   *			   0 if 2**(4+bucket) < page_size, 
   *	   		and
   *			  SIZEOF_MALLOC_HEADER+2*padding_size 
   *			    if 2**(4+bucket) >= page_size
   *
   * The actual size of a block in the free_lists[bucket] is:
   *
   *		block_size + extra
   *
   * Find out exactly which free-list to use:
   */
  while (block_size_req > (block_size + extra))
    {
      ++bucket;
      block_size <<= 1;
      if (bucket > n_bucket_sizes)
	return -1;
      if (bucket == page_bucket)
	extra = SIZEOF_MALLOC_HEADER + 2 * padding_size;
      /* Conditions
       *
       * Same as on entry to the loop.
       */
    }
  
  *block_size_p = block_size + extra;
  return bucket;
}


/*include-documentation "errno.c"
 */



/************************************************************************
 *(h2 "Malloc Internals")
 * 
 * This section describes the PIW implementation of the functions
 * `malloc', `realloc', and `free'.  The information here may be
 * helpful in understanding the source code or how this implementation
 * interacts with your programs and operating system.
 * 
 * `malloc' is derived from an implementation that is part of
 * FreeBSD 2.1 (and presumably some other versions of BSD).  The
 * implementation operates as follows:
 * 
 * The internal unit of allocation is a "block" -- a power-of-two
 * sized chunk of memory.  Every user allocation request is satisfied
 * by a block of the smallest suitable size.  Large blocks are handled
 * specially to avoid a near-100% space inefficiency when programs ask
 * to allocate a power-of-two sized chunk of memory; this is explained
 * below.
 * 
 * The format of a block of memory is:
 * 
 *	|__block header__|__padding__|__user data__|__padding__|
 * 		^                    ^
 * 		|                    |
 * 	(struct malloc_header)link;    Value returned from malloc.
 * 
 * Padding is an optional debugging aid. The amount of padding is
 * determined at the time of the first call to `malloc' by the value
 * of `piw_malloc_block_pad'.
 * 
 * The "link" holds a free-list pointer when the block is unallocated.
 * When the block is allocated, it holds a hash value from which the
 * size of the block may be recovered.  In fact, the "link" holds two
 * redundant free-list pointers or hash values -- the redundant values
 * are compared to check for signs of heap corruption.
 * 
 * Freelists are kept for each block size (from `2**4' to `2**(sizeof
 * (size_t) * 8 - 1))'.
 * 
 * A block must hold a user's data as well as malloc's internal data
 * structures.  Thus, a user request to allocate `N' bytes must be
 * satisfied using a block which is larger than `N' bytes.
 * 
 * For small blocks, on free-list `N' where `2**(4+N) < page_size',
 * the actual size of each block is exactly `2**(4+N)' bytes.  If you
 * allocate a block of size `2**(4+N)', rather than coming from
 * free-list `N', it will come from free-list `N+1' or greater.
 * 
 * For large blocks, on free-list `N' where `2**(4+N) >= page_size',
 * the actual size of each block is
 * 
 * 	(2**(4+N) + 2 * padding_size + SIZEOF_MALLOC_HEADER)
 * 
 * bytes.  The amount of memory requested from `sbrk' for such a block
 * is rounded up to a multiple of the page size in order to enforce
 * alignment requirements.  The `user data' of large blocks (>=
 * page_size) is always page-aligned.  (The user data is the address
 * returned by `malloc').  The malloc header and optional padding are
 * stored immediately below that address, on the previous page.
 * 
 * The extra space allocated for large blocks ensures that if you
 * allocate a block of size `2**(4+N)' (or slightly less) it will come
 * from free-list `N' and less than two pages of memory will be
 * wasted.  
 * 
 * An attempt to allocate memory begins by examining the relevant free
 * list.  If the free list is not empty, its first element is used to
 * satisfy the request.  If the free list is empty, new heap space is
 * acquired using "sbrk" and the empty free list is refilled from that
 * new space.
 * 
 * Each new page acquired from `sbrk' is allocated for a specific
 * block size.  When a page is allocated, it is divided up into blocks
 * that are added to a freelist.  When freed, blocks are returned to
 * the head of the free-list from which they came; There is no
 * provision for combining blocks to form larger blocks or for
 * returning memory to the operating system.
 * 
 */
