/*
 * jvirtmem.c
 *
 * Copyright (C) 1991, Thomas G. Lane.
 * This file is part of the Independent JPEG Group's software.
 * For conditions of distribution and use, see the accompanying README file.
 *
 * This file provides the system-dependent memory allocation routines
 * for the case where we can rely on virtual memory to handle large arrays.
 *
 * This includes some MS-DOS code just for trial purposes; "big" arrays will
 * have to be handled with temp files on MS-DOS, so a real implementation of
 * a DOS memory manager will probably be a separate file.  (See additional
 * comments about big arrays, below.)
 * 
 * NB: allocation routines never return NULL.
 * They should exit to error_exit if unsuccessful.
 */

#include "jinclude.h"

#ifdef __STDC__
#include <stdlib.h>		/* to declare malloc(), free() */
#else
extern void * malloc PP((size_t size));
extern void free PP((void *ptr));
#endif


/* Insert system-specific definitions of far_malloc, far_free here. */

#ifndef NEED_FAR_POINTERS	/* Generic for non-braindamaged CPUs */

#define far_malloc(x)	malloc(x)
#define far_free(x)	free(x)

#else /* NEED_FAR_POINTERS */

#ifdef __TURBOC__
/* These definitions work for Turbo C */
#include <alloc.h>		/* need farmalloc(), farfree() */
#define far_malloc(x)	farmalloc(x)
#define far_free(x)	farfree(x)
#else
#ifdef MSDOS
/* These definitions work for Microsoft C and compatible compilers */
#include <malloc.h>		/* need _fmalloc(), _ffree() */
#define far_malloc(x)	_fmalloc(x)
#define far_free(x)	_ffree(x)
#endif
#endif

#endif /* NEED_FAR_POINTERS */


/*
 * Some important notes:
 *   The array alloc/dealloc routines are not merely a convenience;
 *   on 80x86 machines the bottom-level pointers in an array are FAR
 *   and thus may not be allocatable by alloc_small.
 *
 *   Also, it's not a good idea to try to merge the sarray and barray
 *   routines, even though they are textually almost the same, because
 *   samples are usually stored as bytes while coefficients are shorts.
 *   Thus, in machines where byte pointers have a different representation
 *   from word pointers, the resulting machine code could not be the same.
 */


static external_methods_ptr methods; /* saved for access to error_exit */


#ifdef MEM_STATS		/* optional extra stuff for statistics */

#define MALLOC_OVERHEAD  (SIZEOF(char *)) /* assumed overhead per request */
#define MALLOC_FAR_OVERHEAD  (SIZEOF(char FAR *)) /* for "far" storage */

static long total_num_small = 0;	/* total # of small objects alloced */
static long total_bytes_small = 0;	/* total bytes requested */
static long cur_num_small = 0;		/* # currently alloced */
static long max_num_small = 0;		/* max simultaneously alloced */

#ifdef NEED_FAR_POINTERS
static long total_num_medium = 0;	/* total # of medium objects alloced */
static long total_bytes_medium = 0;	/* total bytes requested */
static long cur_num_medium = 0;		/* # currently alloced */
static long max_num_medium = 0;		/* max simultaneously alloced */
#endif

static long total_num_sarray = 0;	/* total # of sarray objects alloced */
static long total_bytes_sarray = 0;	/* total bytes requested */
static long cur_num_sarray = 0;		/* # currently alloced */
static long max_num_sarray = 0;		/* max simultaneously alloced */

static long total_num_barray = 0;	/* total # of barray objects alloced */
static long total_bytes_barray = 0;	/* total bytes requested */
static long cur_num_barray = 0;		/* # currently alloced */
static long max_num_barray = 0;		/* max simultaneously alloced */


GLOBAL void
j_mem_stats (void)
{
  /* since this is only a debugging stub, we can cheat a little on the
   * trace message mechanism... helps 'cuz trace can't handle longs.
   */
  fprintf(stderr, "total_num_small = %ld\n", total_num_small);
  fprintf(stderr, "total_bytes_small = %ld\n", total_bytes_small);
  if (cur_num_small)
    fprintf(stderr, "CUR_NUM_SMALL = %ld\n", cur_num_small);
  fprintf(stderr, "max_num_small = %ld\n", max_num_small);
  
#ifdef NEED_FAR_POINTERS
  fprintf(stderr, "total_num_medium = %ld\n", total_num_medium);
  fprintf(stderr, "total_bytes_medium = %ld\n", total_bytes_medium);
  if (cur_num_medium)
    fprintf(stderr, "CUR_NUM_MEDIUM = %ld\n", cur_num_medium);
  fprintf(stderr, "max_num_medium = %ld\n", max_num_medium);
#endif
  
  fprintf(stderr, "total_num_sarray = %ld\n", total_num_sarray);
  fprintf(stderr, "total_bytes_sarray = %ld\n", total_bytes_sarray);
  if (cur_num_sarray)
    fprintf(stderr, "CUR_NUM_SARRAY = %ld\n", cur_num_sarray);
  fprintf(stderr, "max_num_sarray = %ld\n", max_num_sarray);
  
  fprintf(stderr, "total_num_barray = %ld\n", total_num_barray);
  fprintf(stderr, "total_bytes_barray = %ld\n", total_bytes_barray);
  if (cur_num_barray)
    fprintf(stderr, "CUR_NUM_BARRAY = %ld\n", cur_num_barray);
  fprintf(stderr, "max_num_barray = %ld\n", max_num_barray);
}

#endif /* MEM_STATS */


LOCAL void
out_of_memory (int which)
/* Report an out-of-memory error and stop execution */
/* If we compiled MEM_STATS support, report alloc requests before dying */
{
#ifdef MEM_STATS
  j_mem_stats();
#endif
  ERREXIT1(methods, "Insufficient memory (case %d)", which);
}



METHODDEF void *
alloc_small (size_t sizeofobject)
/* Allocate a "small" (all-in-memory) object */
{
  void * result;

#ifdef MEM_STATS
  total_num_small++;
  total_bytes_small += sizeofobject + MALLOC_OVERHEAD;
  cur_num_small++;
  if (cur_num_small > max_num_small) max_num_small = cur_num_small;
#endif

  result = malloc(sizeofobject);
  if (result == NULL)
    out_of_memory(1);
  return result;
}


METHODDEF void
free_small (void *ptr)
/* Free a "small" (all-in-memory) object */
{
  free(ptr);

#ifdef MEM_STATS
  cur_num_small--;
#endif
}


#ifdef NEED_FAR_POINTERS

METHODDEF void FAR *
alloc_medium (size_t sizeofobject)
/* Allocate a "medium" (all in memory, but in far heap) object */
{
  void FAR * result;

#ifdef MEM_STATS
  total_num_medium++;
  total_bytes_medium += sizeofobject + MALLOC_FAR_OVERHEAD;
  cur_num_medium++;
  if (cur_num_medium > max_num_medium) max_num_medium = cur_num_medium;
#endif

  result = far_malloc(sizeofobject);
  if (result == NULL)
    out_of_memory(2);
  return result;
}


METHODDEF void
free_medium (void FAR *ptr)
/* Free a "medium" (all in memory, but in far heap) object */
{
  far_free(ptr);

#ifdef MEM_STATS
  cur_num_medium--;
#endif
}

#endif /* NEED_FAR_POINTERS */


METHODDEF JSAMPARRAY
alloc_small_sarray (long samplesperrow, long numrows)
/* Allocate a "small" (all-in-memory) 2-D sample array */
{
  JSAMPARRAY result;
  long i;

#ifdef MEM_STATS
  total_num_sarray++;
  total_bytes_sarray += (samplesperrow * SIZEOF(JSAMPLE) + MALLOC_FAR_OVERHEAD)
			* numrows;
  cur_num_sarray++;
  if (cur_num_sarray > max_num_sarray) max_num_sarray = cur_num_sarray;
#endif

  /* Get space for row pointers; this is always "near" on 80x86 */
  result = (JSAMPARRAY) alloc_small((size_t) (numrows * SIZEOF(JSAMPROW)));

  /* Get the rows themselves; on 80x86 these are "far" */
  for (i = 0; i < numrows; i++) {
    result[i] = (JSAMPROW) far_malloc((size_t) (samplesperrow * SIZEOF(JSAMPLE)));
    if (result[i] == NULL)
      out_of_memory(3);
  }

  return result;
}


METHODDEF void
free_small_sarray (JSAMPARRAY ptr, long numrows)
/* Free a "small" (all-in-memory) 2-D sample array */
{
  long i;

  /* Free the rows themselves; on 80x86 these are "far" */
  for (i = 0; i < numrows; i++) {
    far_free((void FAR *) ptr[i]);
  }

  /* Free space for row pointers; this is always "near" on 80x86 */
  free_small((void *) ptr);

#ifdef MEM_STATS
  cur_num_sarray--;
#endif
}


METHODDEF JBLOCKARRAY
alloc_small_barray (long blocksperrow, long numrows)
/* Allocate a "small" (all-in-memory) 2-D coefficient-block array */
{
  JBLOCKARRAY result;
  long i;

#ifdef MEM_STATS
  total_num_barray++;
  total_bytes_barray += (blocksperrow * SIZEOF(JBLOCK) + MALLOC_FAR_OVERHEAD)
			* numrows;
  cur_num_barray++;
  if (cur_num_barray > max_num_barray) max_num_barray = cur_num_barray;
#endif

  /* Get space for row pointers; this is always "near" on 80x86 */
  result = (JBLOCKARRAY) alloc_small((size_t) (numrows * SIZEOF(JBLOCKROW)));

  /* Get the rows themselves; on 80x86 these are "far" */
  for (i = 0; i < numrows; i++) {
    result[i] = (JBLOCKROW) far_malloc((size_t) (blocksperrow * SIZEOF(JBLOCK)));
    if (result[i] == NULL)
      out_of_memory(4);
  }

  return result;
}


METHODDEF void
free_small_barray (JBLOCKARRAY ptr, long numrows)
/* Free a "small" (all-in-memory) 2-D coefficient-block array */
{
  long i;

  /* Free the rows themselves; on 80x86 these are "far" */
  for (i = 0; i < numrows; i++) {
    far_free((void FAR *) ptr[i]);
  }

  /* Free space for row pointers; this is always "near" on 80x86 */
  free_small((void *) ptr);

#ifdef MEM_STATS
  cur_num_barray--;
#endif
}



/*
 * About "big" array management:
 *
 * To allow machines with limited memory to handle large images,
 * all processing in the JPEG system is done a few pixel or block rows
 * at a time.  The above "small" array routines are only used to allocate
 * strip buffers (as wide as the image, but just a few rows high).
 * In some cases multiple passes must be made over the data.  In these
 * cases the "big" array routines are used.  The array is still accessed
 * a strip at a time, but the memory manager must save the whole array
 * for repeated accesses.  The intended implementation is that there is
 * a strip buffer in memory (as high as is possible given the desired memory
 * limit), plus a backing file that holds the rest of the array.
 *
 * The request_big_array routines are told the total size of the image (in case
 * it is useful to know the total file size that will be needed).  They are
 * also given the unit height, which is the number of rows that will be
 * accessed at once; the in-memory buffer should usually be made a multiple of
 * this height for best efficiency.
 *
 * The request routines create control blocks (and may open backing files),
 * but they don't create the in-memory buffers.  This is postponed until
 * alloc_big_arrays is called.  At that time the total amount of space needed
 * is known (approximately, anyway), so free memory can be divided up fairly.
 *
 * The access_big_array routines are responsible for making a specific strip
 * area accessible (after reading or writing the backing file, if necessary).
 * Note that the access routines are told whether the caller intends to modify
 * the accessed strip; during a read-only pass this saves having to rewrite
 * data to disk.
 *
 * The typical access pattern is one top-to-bottom pass to write the data,
 * followed by one or more read-only top-to-bottom passes.  However, other
 * access patterns may occur while reading.  For example, translation of image
 * formats that use bottom-to-top scan order will require bottom-to-top read
 * passes.  The memory manager need not support multiple write passes nor
 * funny write orders (meaning that rearranging rows must be handled while
 * reading data out of the big array, not while putting it in).
 *
 * In current usage, the access requests are always for nonoverlapping strips;
 * that is, successive access start_row numbers always differ by exactly the
 * unitheight.  This allows fairly simple buffer dump/reload logic if the
 * in-memory buffer is made a multiple of the unitheight.  It would be
 * possible to keep subsampled rather than fullsize data in the "big" arrays,
 * thus reducing temp file size, if we supported overlapping strip access
 * (access requests differing by less than the unitheight).  At the moment
 * I don't believe this is worth the extra complexity.
 *
 * This particular implementation doesn't use temp files; the whole of a big
 * array is allocated in (virtual) memory, and any swapping is done behind the
 * scenes by the operating system.
 */



/* The control blocks for virtual arrays.
 * These are pretty minimal in this implementation.
 * Note: in this implementation we could realize big arrays
 * at request time and make alloc_big_arrays a no-op;
 * however, doing it separately keeps callers honest.
 */

struct big_sarray_control {
	JSAMPARRAY mem_buffer;	/* memory buffer (the whole thing, here) */
	long rows_in_mem;	/* Height of memory buffer */
	long samplesperrow;	/* Width of memory buffer */
	long unitheight;	/* # of rows accessed by access_big_sarray() */
	big_sarray_ptr next;	/* list link for unrealized arrays */
};

struct big_barray_control {
	JBLOCKARRAY mem_buffer;	/* memory buffer (the whole thing, here) */
	long rows_in_mem;	/* Height of memory buffer */
	long blocksperrow;	/* Width of memory buffer */
	long unitheight;	/* # of rows accessed by access_big_barray() */
	big_barray_ptr next;	/* list link for unrealized arrays */
};


/* Headers of lists of control blocks for unrealized big arrays */
static big_sarray_ptr unalloced_sarrays;
static big_barray_ptr unalloced_barrays;


METHODDEF big_sarray_ptr
request_big_sarray (long samplesperrow, long numrows, long unitheight)
/* Request a "big" (virtual-memory) 2-D sample array */
{
  big_sarray_ptr result;

  /* get control block */
  result = (big_sarray_ptr) alloc_small(SIZEOF(struct big_sarray_control));

  result->mem_buffer = NULL;	/* lets access routine spot premature access */
  result->rows_in_mem = numrows;
  result->samplesperrow = samplesperrow;
  result->unitheight = unitheight;
  result->next = unalloced_sarrays; /* add to list of unallocated arrays */
  unalloced_sarrays = result;

  return result;
}


METHODDEF big_barray_ptr
request_big_barray (long blocksperrow, long numrows, long unitheight)
/* Request a "big" (virtual-memory) 2-D coefficient-block array */
{
  big_barray_ptr result;

  /* get control block */
  result = (big_barray_ptr) alloc_small(SIZEOF(struct big_barray_control));

  result->mem_buffer = NULL;	/* lets access routine spot premature access */
  result->rows_in_mem = numrows;
  result->blocksperrow = blocksperrow;
  result->unitheight = unitheight;
  result->next = unalloced_barrays; /* add to list of unallocated arrays */
  unalloced_barrays = result;

  return result;
}


METHODDEF void
alloc_big_arrays (long extra_small_samples, long extra_small_blocks,
		  long extra_medium_space)
/* Allocate the in-memory buffers for any unrealized "big" arrays */
/* 'extra' values are upper bounds for total future small-array requests */
/* and far-heap requests */
{
  /* In this implementation we just malloc the whole arrays */
  /* and expect the system's virtual memory to worry about swapping them */
  big_sarray_ptr sptr;
  big_barray_ptr bptr;

  for (sptr = unalloced_sarrays; sptr != NULL; sptr = sptr->next) {
    sptr->mem_buffer = alloc_small_sarray(sptr->samplesperrow,
					  sptr->rows_in_mem);
  }

  for (bptr = unalloced_barrays; bptr != NULL; bptr = bptr->next) {
    bptr->mem_buffer = alloc_small_barray(bptr->blocksperrow,
					  bptr->rows_in_mem);
  }

  unalloced_sarrays = NULL;	/* reset for possible future cycles */
  unalloced_barrays = NULL;
}


METHODDEF JSAMPARRAY
access_big_sarray (big_sarray_ptr ptr, long start_row, boolean writable)
/* Access the part of a "big" sample array starting at start_row */
/* and extending for ptr->unitheight rows.  writable is true if  */
/* caller intends to modify the accessed area. */
{
  /* debugging check */
  if (start_row < 0 || start_row+ptr->unitheight > ptr->rows_in_mem ||
      ptr->mem_buffer == NULL)
    ERREXIT(methods, "Bogus access_big_sarray request");

  return ptr->mem_buffer + start_row;
}


METHODDEF JBLOCKARRAY
access_big_barray (big_barray_ptr ptr, long start_row, boolean writable)
/* Access the part of a "big" coefficient-block array starting at start_row */
/* and extending for ptr->unitheight rows.  writable is true if  */
/* caller intends to modify the accessed area. */
{
  /* debugging check */
  if (start_row < 0 || start_row+ptr->unitheight > ptr->rows_in_mem ||
      ptr->mem_buffer == NULL)
    ERREXIT(methods, "Bogus access_big_barray request");

  return ptr->mem_buffer + start_row;
}


METHODDEF void
free_big_sarray (big_sarray_ptr ptr)
/* Free a "big" (virtual-memory) 2-D sample array */
{
  free_small_sarray(ptr->mem_buffer, ptr->rows_in_mem);
  free_small((void *) ptr);	/* free the control block too */
}


METHODDEF void
free_big_barray (big_barray_ptr ptr)
/* Free a "big" (virtual-memory) 2-D coefficient-block array */
{
  free_small_barray(ptr->mem_buffer, ptr->rows_in_mem);
  free_small((void *) ptr);	/* free the control block too */
}



/*
 * The method selection routine for virtual memory systems.
 * The system-dependent setup routine should call this routine
 * to install the necessary method pointers in the supplied struct.
 */

GLOBAL void
jselvirtmem (external_methods_ptr emethods)
{
  methods = emethods;		/* save struct addr for error exit access */

  emethods->alloc_small = alloc_small;
  emethods->free_small = free_small;
#ifdef NEED_FAR_POINTERS
  emethods->alloc_medium = alloc_medium;
  emethods->free_medium = free_medium;
#endif
  emethods->alloc_small_sarray = alloc_small_sarray;
  emethods->free_small_sarray = free_small_sarray;
  emethods->alloc_small_barray = alloc_small_barray;
  emethods->free_small_barray = free_small_barray;
  emethods->request_big_sarray = request_big_sarray;
  emethods->request_big_barray = request_big_barray;
  emethods->alloc_big_arrays = alloc_big_arrays;
  emethods->access_big_sarray = access_big_sarray;
  emethods->access_big_barray = access_big_barray;
  emethods->free_big_sarray = free_big_sarray;
  emethods->free_big_barray = free_big_barray;

  unalloced_sarrays = NULL;	/* make sure list headers are empty */
  unalloced_barrays = NULL;
}
