/******************************************************************************
 *		           FREXX PROGRAMMING LANGUAGE    		      *
 ******************************************************************************

 memory.c

 Memory functions handling all allocating and freeing.

 *****************************************************************************/

/************************************************************************
 *                                                                      *
 * fpl.library - A run time library interpreting script langauge.       *
 * Copyright (C) 1992, 1993 FrexxWare                                   *
 * Author: Daniel Stenberg                                              *
 *                                                                      *
 * 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, or (at your option)  *
 * any later version.                                                   *
 *                                                                      *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                      *
 * Daniel Stenberg                                                      *
 * Birger Jarlsgatan 93b 3tr                                            *
 * 113 56 Stockholm                                                     *
 * Sweden                                                               *
 *                                                                      *
 * FidoNet 2:201/328                                                    *
 *                                                                      *
 ************************************************************************/
#ifdef UNIX
#include <sys/types.h>
#elif defined(AMIGA)
#include <exec/types.h>
#include <exec/memory.h>
#include <proto/exec.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include "script.h"
#include "memory.h"

static void LinkMemory(struct Data *, struct MemInfo *, char);
static void UnLinkMemory(struct Data *, struct MemInfo *, char);

#ifdef DEBUG
extern long mem;
extern long maxmem;
long malloc_count=0;
long Mmalloc_count=0;
long free_mem=0;
long max_free_mem=0;
#endif

/**********************************************************************
 *
 * MallocCycle();
 *
 * Get memory to a MALLOC_DYNAMIC alloc. The memory can be taken from the
 * memory cache if any is available.
 *
 ******/

void *MallocCycle(struct Data *scr, long size DEBUGPARAMETERS)
{
  if(size<256) {
    register struct FreeBlock *pnt;
    size>>=4;
    if(pnt=scr->blox[size]) {
      scr->blox[size]=pnt->next;
      scr->blockcount[size]--;
#ifdef DEBUG
#ifdef DEBUGPARAMETERS
      pnt->mem.line=line;
      pnt->mem.source=source; /* the two debugparameters */
#endif
      Mmalloc_count++;
      free_mem-=(((struct MemInfo *)pnt)->size&SIZEBITS)+
	sizeof(struct MemInfo);
      CheckMem(scr, (char *)pnt+sizeof(struct MemInfo));
#endif
      return (void *)((char *)pnt+sizeof(struct MemInfo));
    } else
      size=(size<<4)+15;
  }

#ifdef DEBUG
#ifdef DEBUGPARAMETERS
  return Malloc(scr, size, MALLOC_DYNAMIC, source, line);
#endif
#else
  return Malloc(scr, size, MALLOC_DYNAMIC);
#endif
}

/**********************************************************************
 *
 * DefaultAlloc();
 *
 * THis function allocates memory from the system. Replaceable with the
 * tag FPLTAG_INTERNAL_ALLOC.
 *
 ******/

void ASM *DefaultAlloc(REG(d0) long size)
{
#ifdef AMIGA
  return (void *)AllocMem(size, MEMF_ANY);
#elif defined(UNIX)
  return (void *)malloc(size);
#endif
}

/**********************************************************************
 *
 * DefaultDealloc();
 *
 * This functions does nothing but returns allocated memory to the system.
 * It can be replaced by a user function specified with the tag
 * FPLTAG_INTERNAL_DEALLOC.
 *
 ******/
 
void ASM DefaultDealloc(REG(a1) void *point, REG(d0) long size)
{
#ifdef AMIGA  
  FreeMem(point, size);
#elif UNIX
  free(point);
#endif
}

/**********************************************************************
 *
 * Malloc();
 *
 * Allocate memory for real.
 *
 ******/

void ASM *Malloc(REG(a0) struct Data *scr, REG(d0) long size, REG(d1) char type
		 DEBUGPARAMETERS)
{
  char *alloc;

#ifdef DEBUG
  struct MemInfo *meminfo;

  size+=MEMORY_COOKIE; /* add extra bytes after the block! */
#endif

  alloc=(char *)scr->Alloc(size+sizeof(struct MemInfo));

  if(!alloc) {
    /* If the first alloc failed, flush caches and retry! */
    FlushFree(scr);
    alloc=(char *)scr->Alloc(size+sizeof(struct MemInfo));
    if(!alloc)
      return(NULL);
  }

  ((struct MemInfo *)alloc)->size=size | (type * ALLOCBIT);
  /* size = size | (type * 1<<31) */

  LinkMemory(scr, (struct MemInfo *)alloc, type);

#ifdef DEBUG
  meminfo=(struct MemInfo *)alloc;

#if PRE_COOKIE>0
  /* Fill the pre_cookie with junk too! */
  memset((void *)meminfo->dummy, 0xbb,
	 sizeof(struct MemInfo)-offsetof(struct MemInfo, dummy));
#endif

#if MEMORY_COOKIE>0
  memset((char *)meminfo + sizeof(struct MemInfo) + size-MEMORY_COOKIE,
	 0xbb, MEMORY_COOKIE);
#endif

#ifdef DEBUGPARAMETERS
  meminfo->line=line;
  meminfo->source=source; /* the two debugparameters */
#endif

  Mmalloc_count++;
  malloc_count++;
  mem+=size+sizeof(struct MemInfo);
  if(mem>maxmem)
    maxmem=mem;
#endif
  return (void *) ((char *)alloc+sizeof(struct MemInfo));
}

/**********************************************************************
 *
 * LinkMemory();
 *
 * Adds the specifed memory area to the certain memory type list.
 *
 *****/

static void LinkMemory(struct Data *scr, struct MemInfo *point, char type)
{
  point->prev=scr->MallocKey[type];	 /* previous */
  point->next=NULL;		 /* next */
  if(scr->MallocKey[type])
    point->prev->next=point;
  scr->MallocKey[type] = (void *)point;
}


/**********************************************************************
 *
 * UnLinkMemory();
 *
 * Deletes the specifed memory area from the certain memory type list.
 *
 *****/

static void UnLinkMemory(struct Data *scr, struct MemInfo *point, char type)
{
  if(scr->MallocKey[type]==point) {
    /* if this is the last Malloc, set `last' to `prev' */
    scr->MallocKey[type]=point->prev;
    if(scr->MallocKey[type])
      scr->MallocKey[type]->next=NULL;
  } else {
    /* point the previous' `next' to our `next', and our next `previous'
       to our `previous'. Unlink us from the chain */
    if(point->prev)
      /* only if we aren't the _first_ Malloc() ! */
      point->prev->next=point->next;
    if(point->next)
      /* only if there is a next! */
      point->next->prev=point->prev;
  }
}


/**********************************************************************
 *
 * FreeCycle();
 *
 * Free the MALLOC_DYNAMIC allocated memory to the memory cache, or if
 * that is full, free for real.
 *
 * In this routine we do not have to bitwise and SIZEBITS since the ALLOCBIT
 * is never set when this is called!
 *
 ******/

void FreeCycle(struct Data *scr, void *ptr)
{
  struct MemInfo *point=(struct MemInfo *)((char *)ptr-sizeof(struct MemInfo));
  /* `point' points to the MemInfo structure */
#if defined(DEBUG) && defined(UNIX)
  if(point->size&ALLOCBIT) {
    fprintf(stderr, "*WARNING* Illegal free() of %d bytes!\n",
	    point->size&SIZEBITS);
  }
#endif

#ifdef DEBUG
  CheckMem(scr, ptr);
#endif

#if MEMORY_QUEUE>0
  if(point->size<256+MEMORY_COOKIE) {
    register long size=point->size-MEMORY_COOKIE>>4;
    if(scr->blockcount[size]<MEMORY_QUEUE_SIZE) {
#ifdef DEBUG
      memset(ptr, 0xaa, point->size-MEMORY_COOKIE);

      free_mem+=point->size+sizeof(struct MemInfo);
      if(free_mem>max_free_mem)
	max_free_mem=free_mem;
#endif
      ((struct FreeBlock *)point)->next=scr->blox[size];
      scr->blox[size]=(struct FreeBlock *)point;
      scr->blockcount[size]++;
      return;
    }
  }
#endif
  Free(scr, ptr, MALLOC_DYNAMIC);
}

/**********************************************************************
 *
 * Free();
 *
 * Free a allocated memory area of a certain type. The freed memory
 * area gets freed `for real'.
 *
 *****/

void ASM Free(REG(a0) struct Data *scr, REG(a1) void *ptr, REG(d0) char type)
{
  struct MemInfo *point=(struct MemInfo *)((char *)ptr-sizeof(struct MemInfo));
  /* `point' points to the MemInfo structure: */
#ifdef DEBUG
  CheckMem(scr, ptr);
#endif
  UnLinkMemory(scr, point, type);
#ifdef DEBUG
  mem-=(point->size&SIZEBITS)+sizeof(struct MemInfo);
#endif
  scr->Dealloc(point, (point->size&SIZEBITS)+sizeof(struct MemInfo));
}

#ifdef DEBUG
/**********************************************************************
 *
 * CheckMem();
 *
 * Check a special malloc to see that it hasn't overwritten it's boundaries.
 *
 ******/

ReturnCode CheckMem(struct Data *scr, void *ptr)
{
  long i;
  long b, c;
  unsigned char *data;
  struct MemInfo *point;
  /* `point' points to the MemInfo structure: */
  point=(struct MemInfo *)((char *)ptr-sizeof(struct MemInfo));

#if MEMORY_COOKIE>0
  for(i=b=0;i<MEMORY_COOKIE; i++) {
    data=(char *)point+ sizeof(struct MemInfo)+i+(point->size&SIZEBITS)-MEMORY_COOKIE;
    if(*data!= 0xbb)
      b++;
  }

  if(b) {
#ifdef AMIGA
    /* ERROR */
    point=NULL;
#else
    fprintf(stderr, "Memory violation: malloc(%d) was abused %d bytes after block!\n",
	    (point->size&SIZEBITS), b);
#endif
  }
#endif

#if PRE_COOKIE>0
  for(c=i=0;i<sizeof(struct MemInfo)-offsetof(struct MemInfo, dummy); i++)
    if(point->dummy[i]!= 0xbb)
      c++;

  if(c) {
#ifdef AMIGA
    /* ERROR */
    point=NULL;
#else
    fprintf(stderr, "Memory violation: malloc(%d) was abused %d bytes before block!\n",
	    (point->size&SIZEBITS), c);
#endif
  }
#endif

  return(b+c?FPL_OUT_OF_MEMORY:FPL_OK);
}
#endif

/**********************************************************************
 *
 * FreeAll();
 *
 * Free all memory allocated with specified type!
 *
 ******/

void FreeAll(struct Data *scr, char type)
{
  struct MemInfo *point;
  void *prev;
#ifdef DEBUG
  long size;
#endif
  if(!scr->MallocKey[type])
    return;
  do {
    point=scr->MallocKey[type];
    /* `point' points to the MemInfo structure! */

    prev=(void *)point->prev;

#ifdef DEBUG
    mem-=(point->size&SIZEBITS)+sizeof(struct MemInfo);
    size=(point->size&SIZEBITS);

    if(type == MALLOC_DYNAMIC)
      free_mem=0; /* no free mem left in memory queuing */
#endif
    scr->Dealloc(scr->MallocKey[type], (point->size&SIZEBITS)+sizeof(struct MemInfo));
  } while(scr->MallocKey[type]=prev);
  if(type==MALLOC_DYNAMIC)
    InitFree(scr); /* init the memory cache tables to prevent accidents with
		      FPLSEND_FLUSHCACHE */
}

#if 0
if(type) {
  /* MALLOC_STATIC */
  FREEA((char *)point+sizeof(struct MemInfo));
} else {
  /* MALLOC DYNAMIC */
  FREE((char *)point+sizeof(struct MemInfo));
}
#endif

/**********************************************************************
 *
 * InitFree()
 *
 * Initialize the memory cache/queue.
 *
 ******/

void InitFree(struct Data *scr)
{
  memset(scr->blox, 0, sizeof(struct FreeBlock *)*BLOCK_ENTRIES);
  memset(scr->blockcount, 0, sizeof(long)*BLOCK_ENTRIES);
}

/**********************************************************************
 *
 * FlushFree();
 *
 * Flush the memory chache.
 *
 *******/

void FlushFree(struct Data *scr)
{
  register long i;
  for(i=0; i<BLOCK_ENTRIES;i++) {
    register struct FreeBlock *pnt;
    while(pnt=scr->blox[i]) {
      scr->blox[i]=pnt->next; /* delete block from chain */
      scr->blockcount[i]--;   /* count down counter */
      /* free block for real */
      Free(scr, (char *)pnt+sizeof(struct MemInfo), MALLOC_DYNAMIC);
    }
  }
}

/**********************************************************************
 *
 * SwapMem();
 *
 * If type is MALLOC_STATIC or MALLOC_DYNAMIC, this function will secure that
 * the memory area will be of that kind!
 *
 * THE AREA MUST BE ALLOCATED WHEN THIS CONVERTION HAPPENS!!! IT MUST NOT
 * BE IN THE FREE MEMORY CACHE/QUEUE LIST.
 *
 * This function is mainly used when storing global data. Since all
 * MALLOC_DYNAMIC data gets freed at the end of a FPL program run, we must
 * convert the mallocs to be able to keep them for next execution!
 *
 *****/

void SwapMem(struct Data *scr, void *ptr, char type)
{
  struct MemInfo *point=(struct MemInfo *)((char *)ptr-sizeof(struct MemInfo));
  /* `point' points to the MemInfo structure: */
  if(point->size&ALLOCBIT && type==MALLOC_DYNAMIC) {
    /* This is a MALLOC_STATIC alloc */
    UnLinkMemory(scr, point, MALLOC_STATIC); /* take away from static list */
    point->size&=SIZEBITS;
    LinkMemory(scr, point, MALLOC_DYNAMIC);  /* insert in dynamic list */
  } else if(!(point->size&ALLOCBIT) && type==MALLOC_STATIC) {
    /* This is a MALLOC_DYNAMIC alloc */
    UnLinkMemory(scr, point, MALLOC_DYNAMIC); /* take away from dynamic list */
    point->size|=ALLOCBIT;
    LinkMemory(scr, point, MALLOC_STATIC);    /* insert in static list */
  }
  /* If no if() statement was reached, the memory area type was corect! */
}

/**********************************************************************
 *
 * fplAlloc()
 *
 * fplAlloc() allocates memory and assign it to the fpl internal list of
 * used memory. If fplFree() is called, this memory area will be free'd if
 * not earlier. Free this memory using fplDealloc().
 *
 ******/

void PREFIX *fplAlloc(REG(a0) struct Data *scr, REG(d0) long size)
{
  if(!scr)
    return(NULL);
  return MALLOCA(size);
}

/**********************************************************************
 *
 * fplDealloc()
 *
 * This function frees memory previously allocated with fplAlloc();
 *
 *****/

void PREFIX fplDealloc(REG(a0) struct Data *scr, REG(a1) void *ptr)
{
  if(!scr)
    return;
  FREEA(ptr);
}

/**********************************************************************
 *
 * fplAlloca()
 *
 * fplAlloca() allocates memory and assign it to the fpl internal list of
 * used memory. The memory will only exist while executing the current file or
 * until next program has been executed. If fplFree() is called, this memory
 * area will also be free'd. Free this memory using fplDealloca(). This function
 * will use the FPL internal memory caching system.
 *
 ******/

void PREFIX *fplAlloca(REG(a0) struct Data *scr, REG(d0) long size)
{
  if(!scr)
    return(NULL);
  return MALLOC(size);
}

/**********************************************************************
 *
 * fplDealloca()
 *
 * This function frees memory previously allocated with fplAlloca();
 *
 *****/

void PREFIX fplDealloca(REG(a0) struct Data *scr, REG(a1) void *ptr)
{
  if(!scr)
    return;
  FREE(ptr);
}
