/* memory.c */


/*
 * Vis5D system for visualizing five dimensional gridded data sets.
 * Copyright (C) 1990 - 2000 Bill Hibbard, Johan Kellum, Brian Paul,
 * Dave Santek, and Andre Battaiola.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * As a special exception to the terms of the GNU General Public
 * License, you are permitted to link Vis5D with (and distribute the
 * resulting source and executables) the LUI library (copyright by
 * Stellar Computer Inc. and licensed for distribution with Vis5D),
 * the McIDAS library, and/or the NetCDF library, where those
 * libraries are governed by the terms of their own licenses.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "../config.h"


#include <assert.h>
#ifdef stellar
#  include <malloc.h>
#  include <memory.h>
#else
#  include <stdlib.h>
#  include <string.h>
#endif
#include <stdio.h>
#include "globals.h"
#include "memory.h"
#include "misc.h"
#include "sync.h"

struct mem {
   int        size;
   struct mem *prev;
   struct mem *next;
   short int  free, magic;
#ifdef DEBUG_MEM
   int type;
#endif
};


#define MEMSIZ sizeof(struct mem)
#define MAGIC 0x1234

static void check_memory( Context ctx );





/********************************************/
/****   Private functions                 ***/
/********************************************/


/*
 * Allocate a block of memory.
 * Input:  b - number of bytes to allocate
 *         permanent - if non-zero do a permanent allocation
 *         type - type of block (see list in memory.h)
 * Return:  address of block or NULL if unable to make allocation
 */
static void *alloc( Context ctx, int b, int permanent, int type )
{
   int bytes;
   struct mem *pos, *new;

#ifdef DEBUG_MEM
   printf("Allocate( %d )\n", b);
#endif

   /* round up bytes to multiple of sizeof(struct mem) */
   if (b<MEMSIZ) {
      bytes = MEMSIZ;
   }
   else {
      bytes = ( (b+MEMSIZ-1) / MEMSIZ ) * MEMSIZ;
   }
   /*
    * If we want to make a permanent allocation, try to do it at tail
    * of memory list.
    */
   if (permanent) {
      if (ctx->tail->size >= bytes) {
#ifdef DEBUG_MEM
         printf("permanent allocation of %d bytes.  old tail->size=%d",
                 bytes, ctx->tail->size );
#endif
         ctx->tail->size -= bytes;
         ctx->memory_used += bytes;
#ifdef DEBUG_MEM
         printf(".  new tail->size=%d\n", ctx->tail->size );
#endif
         return (char *) ctx->tail + MEMSIZ + ctx->tail->size;
      }
      /* couldn't allocate from tail; just do a normal allocation */
   }


   /*
    * Find a block of memory large enough to make the allocation from.
    */

   pos = NULL;
   if (ctx->guess) {
      /* Try a guess */
#ifdef DEBUG_MEM
      assert( ctx->guess->magic==MAGIC );
      assert( ctx->guess->free==1);
#endif
      if (ctx->guess->free && ctx->guess->size >= bytes + MEMSIZ) {
         /* a good guess! */
#ifdef DEBUG_MEM
         printf("Good guess!\n");
#endif
         pos = ctx->guess;
         ctx->guess = NULL;
      }
   }

   if (pos==NULL) {
      /* Find first fit */
#ifdef DEBUG_MEM
      int tries = 0;
#endif
      for (pos=ctx->head; pos; pos=pos->next) {
         if (pos->free && pos->size == bytes) {
            /* found exact fit! */
            break;
         }
         else if (pos->free && pos->size >= bytes + MEMSIZ) {
            /* found a block to split */
            break;
         }
#ifdef DEBUG_MEM
         tries++;
#endif
      }
#ifdef DEBUG_MEM
      printf("%d tries\n", tries);
#endif
   }


   if (!pos) {
      /* couldn't find block large enough, return NULL */
      ctx->guess = NULL;
      return NULL;
   }

   if (pos->size == bytes) {
      /* found a block of exact size! */
      pos->free = 0;
      ctx->memory_used += bytes;
      if (ctx->guess==pos)
        ctx->guess = NULL;
#ifdef DEBUG_MEM
      pos->type = type;
      printf("exact fit 0x%x 0x%x\n", (int)pos, (int)(pos+1));
      check_memory( ctx );
#endif
      return (void *) (pos+1);
   }
   else {
      /*
       * 'pos' points to a block of sufficient size.  Split it into two
       * pieces, and return the address of the first part
       */
      new = (struct mem *) ( (char *) pos + MEMSIZ + bytes );
      /* init new fragment node */
      new->size = pos->size - bytes - MEMSIZ;
      new->prev = pos;
      new->next = pos->next;
      new->free = 1;
      new->magic = MAGIC;
      /* tail pointer */
      if (pos->next)
        pos->next->prev = new;
      else
        ctx->tail = new;
      /* links to pos */
      pos->next = new;
      pos->size = bytes;
      pos->free = 0;
      ctx->memory_used += bytes + MEMSIZ;
      /* reset guess */
      if (!ctx->guess) {
         ctx->guess = new;
      }
#ifdef DEBUG_MEM
      pos->type = type;
      printf("big fit 0x%x 0x%x\n", (int)pos, (int)(pos+1));
      check_memory( ctx );
#endif
      return (void *) (pos+1);
   }
}




/*
 * Deallocate a block of memory.
 * Input:  addr - the address of the block to deallocate
 *         b - number of bytes in the block or -1 if unknown
 */
static void dealloc( Context ctx, void *addr, int b )
{
   int bytes;
   struct mem *pos, *pred, *succ;

#ifdef DEBUG_MEM
   printf("Deallocate( 0x%x, %d )\n", (int)addr, b );
#endif
   if (addr==NULL) {
      printf("Warning:  deallocate(NULL)\n");
      return;
   }

   pos = (struct mem *) ( (char *) addr - MEMSIZ );

#ifdef DEBUG_MEM
   /* Sanity Checks: */
   assert( pos->magic==MAGIC );
   assert( pos->free==0 );
#endif

   if (b<0) {
      bytes = pos->size;
   }
   else if (b<MEMSIZ) {
      bytes = MEMSIZ;
   }
   else {
      /* round up bytes to multiple of sizeof(struct mem) */
      bytes = ( (b+MEMSIZ-1) / MEMSIZ ) * MEMSIZ;
      if (pos->size!=bytes) {
         printf("Warning:  wrong number of bytes in deallocate() %d vs %d\n",
                pos->size, bytes );
      }
   }

   /* mark as free */
   pos->free = 1;
   ctx->memory_used -= bytes;

   /* try to merge this block with successor */
   if (pos->next && pos->next->free==1) {
#ifdef DEBUG_MEM
      printf("Merge with successor\n");
#endif
      succ = pos->next;
      pos->size += MEMSIZ + succ->size;
      pos->next = succ->next;
      pos->free = 1;
      if (succ->next)
         succ->next->prev = pos;
      else
         ctx->tail = pos;
      /* update guess if necessary */
      if (succ==ctx->guess) {
         ctx->guess = pos;
      }
      ctx->memory_used -= MEMSIZ;
   }

   /* try to merge this block with predecessor */
   if (pos->prev && pos->prev->free==1) {
#ifdef DEBUG_MEM
      printf("Merge with predecessor\n");
#endif
      pred = pos->prev;
      pred->size += MEMSIZ + pos->size;
      pred->next = pos->next;
      if (pos->next)
         pos->next->prev = pred;
      else
         ctx->tail = pred;
      /* update guess if necessary */
      if (pos==ctx->guess) {
         ctx->guess = pred;
      }
      /* update pos */
      pos = pred;
      ctx->memory_used -= MEMSIZ;
   }

   /* reset guess */
   ctx->guess = pos;
#ifdef DEBUG_MEM
   check_memory(ctx);
#endif
}




/********************************************/
/***         DEBUGGING FUNCTIONS          ***/
/********************************************/



static void check_memory( Context ctx )
{
   struct mem *pos, *pred;

   pred = NULL;
   pos = ctx->head;
   while (pos) {
      if (pos->free!=1 && pos->free!=0) {
         printf("bad pos->free %d\n", pos->free);
      }
      if (pos->magic!=MAGIC) {
         printf("bad magic number in node 0x%x\n", (int)pos );
      }
      if (pos->prev != pred) {
         printf("bad pred pointer 0x%x should be 0x%x\n", 
		(int)pos->prev, (int)pred );
      }
      if (pos->next==NULL && ctx->tail!=pos) {
         die("bad tail\n");
      }
      if (pred && pred->free==1 && pos->free==1) {
         die("adjacent free blocks");
      }
      if (pred) {
         if ((char *) pred + pred->size + MEMSIZ != (char *) pos) {
            die("Bad size");
         }
      }

      pred = pos;
      pos = pos->next;
   }

   assert( ctx->tail->free==1 );

   if (ctx->guess)
     assert( ctx->guess->free == 1 );

}



static void dump_memory( Context ctx )
{
   struct mem *pos;

   pos = ctx->head;
   while (pos) {
      printf("node: 0x%x\n", (int)pos );
      printf("  size: %d", pos->size );
      printf("  prev: 0x%x", (int)pos->prev );
      printf("  next: 0x%x", (int)pos->next );
#ifdef DEBUG_MEM
      printf("  type: %d", pos->type );
#endif
      printf("  free: %d\n", pos->free );
      pos = pos->next;
   }
   printf("tail = 0x%x\n", (int)ctx->tail );
   printf("memory used: %d\n", ctx->memory_used);
}



/**********************************************************************/
/****                         PUBLIC FUNCTIONS                     ****/
/**********************************************************************/



/*
 * Initialize the memory management for a context.
 * Input:  ctx - the vis5d context
 *         bytes - size of the memory pool.
 * Return:  1 = success, 0 = error
 */
int init_memory( Context ctx, int bytes )
{
   struct mem *m;

   assert( bytes==0 || bytes>=1024*1024 );

   /*printf("init_memory( %d ) ctx = %d\n", bytes, ctx->context_index);*/

   ctx->memory_limit = bytes;

   if (bytes) {
      m = (struct mem *) malloc( bytes );
      if (!m) {
         printf("Error: unable to allocate %d bytes of memory.\n", bytes);
         printf("Either change MBS in vis5d.h or use -mbs option.\n");
         return 0;
      }

      m->size = bytes - sizeof(struct mem);
      m->prev = NULL;
      m->next = NULL;
      m->free = 1;
      m->magic = MAGIC;

      ctx->mempool = m;
      /* MJK 12.15.98 */
      ctx->head = ctx->tail = ctx->guess = m;
/*
      ctx->head = ctx->tail = m;
*/
      ctx->memory_used = MEMSIZ;
   }
   else {
      ctx->mempool = 0;
      ctx->memory_used = 0;
   }

   ALLOC_LOCK( ctx->memlock );
   ALLOC_LOCK( ctx->lrulock );

   ctx->meminited = 1;

   return 1;
}




/*
 * Define a shared memory area to use as the memory pool for a context.
 * Input:  ctx - the vis5d context
 *         bytes - size of the memory pool.
 * Return:  1 = success, 0 = error
 */
int init_shared_memory( Context ctx, void *start, int bytes )
{
   struct mem *m;

   ctx->memory_limit = bytes;

   m = start;
   m->size = bytes - sizeof(struct mem);
   m->prev = NULL;
   m->next = NULL;
   m->free = 1;
   m->magic = MAGIC;

   ctx->mempool = start;
   /* MJK 12.15.98 */
   ctx->head = ctx->tail = ctx->guess = m;
/*
   ctx->head = ctx->tail = m;
*/
   ctx->memory_used = MEMSIZ;

   ALLOC_LOCK( ctx->memlock );
   ALLOC_LOCK( ctx->lrulock );

   return 1;
}





/*
 * Reinitialize a memory pool so that it is completely unallocated.
 */
int reinit_memory( Context ctx )
{
   struct mem *m;

   if (ctx->memory_limit) {
      m = ctx->head;

      m->size = ctx->memory_limit - sizeof(struct mem);
      m->prev = NULL;
      m->next = NULL;
      m->free = 1;
      m->magic = MAGIC;

      ctx->head = ctx->tail = m;
      ctx->memory_used = MEMSIZ;
   }
   else {
      /* How do we free() all the malloc()s ?? - in case
         init_memory given bytes = 0 to flag use malloc */
      ctx->memory_used = 0;
   }

   return 1;
}



/*
 * Return the amount of available memory.
 * Input:  ctx - the vis5d context
 */
int mem_available( Context ctx )
{
   if (ctx->memory_limit==0)
      return 1024*1024*1024;  /* a Gig ought to be enough */
   else
      return ctx->memory_limit - ctx->memory_used;
}




/*
 * Allocate a block of memory.  If there is not enough memory to satisfy
 * the request, the least recently used graphics will be deallocated.
 * Input:  ctx - the vis5d context
 *         bytes - how many bytes to allocate
 * Return:  address of memory block or NULL if out of memory.
 */
#ifndef allocate
void *allocate( Context ctx, int bytes )
{
  
  assert( bytes>=0 );

  if (ctx->memory_limit==0) {
	 /* just malloc */
#ifdef DEBUG_MEM
	 void *tmp;
	 tmp = (void *) malloc( bytes );
	 printf("malloc from allocate 0x%x %d\n",tmp,bytes);
	 return tmp;
#else
	 return (void *) malloc( bytes );
#endif
  }else {
	 void *addr;
	 int ma, d;

	 do {
		LOCK_ON( ctx->memlock );
		addr = alloc( ctx, bytes, 0, NULL_TYPE );
		LOCK_OFF( ctx->memlock );
		if (addr) {
		  /* all done, return */
#ifdef DEBUG_MEM
		  printf("allocate 0x%x %d\n",addr,bytes);
#endif
		  return addr;
		}
		/* We didn't find a free block large enough, */
		/* try deallocating some graphics */
		ma = mem_available(ctx);
		LOCK_ON( ctx->lrulock );
		if (ma==mem_available(ctx)) {
		  d = deallocate_lru(ctx);
		}
		LOCK_OFF( ctx->lrulock );
	 } while (d>0);
	 /* Couldn't deallocate anything, we're REALLY out of memory */
#ifdef DEBUG_MEM
	 printf("Allocate %d failed\n", bytes );
	 dump_memory(ctx);
#endif
	 return NULL;
  }
}

#endif

/*
 * Allocate a block of memory.  If there is not enough memory to satisfy
 * the request, the least recently used graphics will be deallocated.
 * Input:  ctx - the vis5d context
 *         bytes - how many bytes to allocate
 *         type - type of block (see list in memory.h)
 * Return:  address of memory block or NULL if out of memory.
 */
void *allocate_type( Context ctx, int bytes, int type )
{
   assert( bytes>=0 );

   if (ctx->memory_limit==0) {
      /* just malloc */
      return (void *) malloc( bytes );
   }
   else {
      void *addr;
      int ma, d;

      do {
         LOCK_ON( ctx->memlock );
         addr = alloc( ctx, bytes, 0, type );
         LOCK_OFF( ctx->memlock );
         if (addr) {
            /* all done, return */
            return addr;
         }
         /* We didn't find a free block large enough, */
         /* try deallocating some graphics */
         ma = mem_available(ctx);
         LOCK_ON( ctx->lrulock );
         if (ma==mem_available(ctx)) {
            d = deallocate_lru(ctx);
         }
         LOCK_OFF( ctx->lrulock );
      } while (d>0);
      /* Couldn't deallocate anything, we're REALLY out of memory */
#ifdef DEBUG_MEM
      printf("Allocate %d failed\n", bytes );
      dump_memory(ctx);
#endif
      return NULL;
   }
}



/*
 * Permanent allocate.  Same as allocate, above, but used to allocate
 * memory which will NEVER be deallocated.
 * Input:  ctx - the vis5d context
 *         bytes - number of bytes to allocate
 */
void *pallocate( Context ctx, int bytes )
{
   if (ctx->memory_limit==0) {
      /* just malloc */
      return (void *) malloc( bytes );
   }
   else {
      void *addr;
      int ma, d;

      do {
         LOCK_ON( ctx->memlock );
         addr = alloc( ctx, bytes, 1, NULL_TYPE );
         LOCK_OFF( ctx->memlock );
         if (addr) {
            /* all done, return */
            return addr;
         }
         /* We didn't find a free block large enough, */
         /* try deallocating some graphics */
         ma = mem_available(ctx);
         LOCK_ON( ctx->lrulock );
         if (ma==mem_available(ctx)) {
            d = deallocate_lru(ctx);
         }
         LOCK_OFF( ctx->lrulock );
      } while (d>0);
      /* Couldn't deallocate anything, we're REALLY out of memory */
#ifdef DEBUG_MEM
      printf("Allocate %d failed\n", bytes );
      dump_memory(ctx);
#endif
      return NULL;
   }
}




/*
 * Deallocate a block of memory.
 * Input:  ctx - the vis5d context
 *         addr - address of block (if NULL, nothing happens)
 *         bytes - size of block (if <= zero, bytes is ignored)
 */
void deallocate( Context ctx, void *addr, int bytes )
{
   LOCK_ON( ctx->memlock );
   if (addr) {
      if (ctx->memory_limit==0) {
         free( addr );
      }
      else {
         dealloc( ctx, addr, bytes );
      }
   }
   LOCK_OFF( ctx->memlock );
}




/*
 * Return the amount of memory used in a context.
 */
int mem_used( Display_Context dtx )
{
   int m;
   int yo;

   m = 0;
   for (yo = 0; yo < dtx->numofctxs; yo++){
      if (dtx->ctxpointerarray[yo]->memory_limit != 0){
         m += dtx->ctxpointerarray[yo]->memory_used;
      }
   }      
   return m;
}


void *MALLOC( size_t size )
{
   void *p;
   p = malloc( size );
/*
   if (size == 0) {
     printf("MALLOC: size = 0\n");
   }
   printf("MALLOC(%d) = 0x%x\n", size, p );
*/
   return p;
}


void FREE( void *ptr, int id )
{
/*   printf("FREE(0x%x) id=%d\n", ptr, id );*/
   free( ptr );
}


syntax highlighted by Code2HTML, v. 0.9.1