/**********************************************************************
 * File:        memry.c  (Formerly memory.c)
 * Description: Memory allocation with builtin safety checks.
 * Author:					Ray Smith
 * Created:					Wed Jan 22 09:43:33 GMT 1992
 *
 * (C) Copyright 1992, Hewlett-Packard Ltd.
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 ** http://www.apache.org/licenses/LICENSE-2.0
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 *
 **********************************************************************/

#include          "mfcpch.h"
#include          <stdlib.h>
#ifdef __UNIX__
#include          <assert.h>
#endif
#include          "stderr.h"
#include          "tprintf.h"
#include          "memblk.h"
#include          "memry.h"

//#define COUNTING_CLASS_STRUCTURES

/**********************************************************************
 * new
 *
 * Replace global new to get at memory leaks etc.
 **********************************************************************/
/*
void*						operator new(				//allocate memory
size_t						size						//amount to allocate
)
{
  if (size==0)
  {
    err.log(RESULT_LOGICAL_ERROR,E_LOC,ERR_PRIMITIVES,
      ERR_SCROLLING,ERR_CONTINUE,ERR_ERROR,
      "Zero requested of new");
    size=1;
  }
  return alloc_big_mem(size);
}

void						operator delete(			//free memory
void*						addr						//mem to free
)
{
  free_big_mem(addr);
}*/

/**********************************************************************
 * check_mem
 *
 * Check consistency of all memory controlled by alloc_mem.
 **********************************************************************/

DLLSYM void check_mem(                     //check consistency
                      const char *string,  //context message
                      inT8 level           //level of check
                     ) {
  big_mem.check (string, level);
  main_mem.check (string, level);
  check_structs(level);
}


/**********************************************************************
 * alloc_string
 *
 * Allocate space for a string.  The space can only be used for chars as
 * it is not aligned.  Allocation is guaranteed to be fast and not cause
 * fragmentation for small strings (upto 10*worst alignment).  Calls for
 * larger strings will be satisfied with alloc_mem.
 * Use free_string to free the space from alloc_string.
 **********************************************************************/

DLLSYM char *alloc_string(             //allocate string
                          inT32 count  //no of chars required
                         ) {
#ifdef RAYS_MALLOC
  char *string;                  //allocated string

  if (count < 1 || count > MAX_CHUNK) {
    tprintf ("Invalid size %d requested of alloc_string", count);
    return NULL;
  }

  count++;                       //add size byte
  if (count <= MAX_STRUCTS * sizeof (MEMUNION)) {
    string = (char *) alloc_struct (count, "alloc_string");
    //get a fast structure
    if (string == NULL) {
      tprintf ("No memory for alloc_string");
      return NULL;
    }
    string[0] = (inT8) count;    //save its length
  }
  else {
                                 //get a big block
    string = (char *) alloc_mem (count);
    if (string == NULL) {
      tprintf ("No memory for alloc_string");
      return NULL;
    }
    string[0] = 0;               //mark its id
  }
  return &string[1];             //string for user
#else
  // Round up the amount allocated to a multiple of 4
  return static_cast<char*>(malloc((count + 3) & ~3));
#endif
}


/**********************************************************************
 * free_string
 *
 * Free a string allocated by alloc_string.
 **********************************************************************/

DLLSYM void free_string(              //free a string
                        char *string  //string to free
                       ) {
#ifdef RAYS_MALLOC
  if (((ptrdiff_t) string & 3) == 1) { //one over word
    string--;                    //get id marker
    if (*string == 0) {
      free_mem(string);  //generally free it
      return;
    }
    else if (*string <= MAX_STRUCTS * sizeof (MEMUNION)) {
                                 //free structure
      free_struct (string, *string, "alloc_string");
      return;
    }
  }
  tprintf ("Non-string given to free_string");
#else
  free(string);
#endif
}


/**********************************************************************
 * alloc_struct
 *
 * Allocate space for a structure.  This function is designed to be
 * fast and fragmentation free for arbitrary combinations of small
 * objects. (Upto 40 bytes in length.)
 * It can be used for any size of object up to 512K, but you must use
 * free_struct to release the memory it gives.  alloc_mem is better
 * for arbitrary data blocks of large size (>40 bytes.)
 * alloc_struct always aborts if the allocation fails.
 **********************************************************************/

DLLSYM void *
alloc_struct (                   //allocate memory
inT32 count,                     //no of chars required
#if defined COUNTING_CLASS_STRUCTURES
const char *name                 //name of type
#else
const char *                     //name of type
#endif
) {
#ifdef RAYS_MALLOC
  MEMUNION *element;             //current element
  MEMUNION *returnelement;       //return value
  inT32 struct_count;            //no of required structs
  inT32 blocksize;               //no of structs in block
  inT32 index;                   //index to structure

  if (count < 1 || count > MAX_CHUNK) {
    tprintf ("Invalid size %d requested of alloc_struct", count);
    return NULL;
  }

  //      tprintf("Allocating structure of size %d\n",count);
                                 //no of MEMUNIONS-1
  struct_count = (count - 1) / sizeof (MEMUNION);
  if (struct_count < MAX_STRUCTS) {
                                 //can do fixed sizes
    #ifdef COUNTING_CLASS_STRUCTURES
    if (name != NULL) {
      index = identify_struct_owner (struct_count, name);
      if (index < MAX_CLASSES)
        owner_counts[struct_count][index]++;
    }
    #endif
                                 //head of freelist
    returnelement = free_structs[struct_count];
    if (returnelement == NULL) {
                                 //need a new block
                                 //get one
      element = (MEMUNION *) new_struct_block ();
      if (element == NULL) {
        tprintf ("No memory to satisfy request for %d", (int) count);
        return NULL;
      }
                                 //add to block list
      element->ptr = struct_blocks[struct_count];
      struct_blocks[struct_count] = element;
      blocks_in_use[struct_count]++;
      element++;                 //free cell
      returnelement = element;   //going to return 1st
      blocksize = STRUCT_BLOCK_SIZE / (struct_count + 1) - 1;

      for (index = 1; index < blocksize; index++) {
                                 //make links
        element->ptr = element + struct_count + 1;
        element += struct_count + 1;
      }
      element->ptr = NULL;       //end of freelist
    }
                                 //new free one
    free_structs[struct_count] = returnelement->ptr;
                                 //count number issued
    structs_in_use[struct_count]++;
  }
  else {
                                 //just get some
    returnelement = (MEMUNION *) alloc_mem (count);
    if (returnelement == NULL) {
      tprintf ("No memory to satisfy request for %d", (int) count);
      return NULL;
    }
  }
  return returnelement;          //free cell
#else
  return malloc(count);
#endif
}


/**********************************************************************
 * free_struct
 *
 * Free memory allocated by alloc_struct.  The size must be supplied.
 **********************************************************************/

DLLSYM void
free_struct (                    //free a structure
void *deadstruct,                //structure to free
inT32 count,                     //no of bytes
#if defined COUNTING_CLASS_STRUCTURES
const char *name                 //name of type
#else
const char *                     //name of type
#endif
) {
#ifdef RAYS_MALLOC
  MEMUNION *end_element;         //current element
  MEMUNION *element;             //current element
  MEMUNION *prev_element;        //previous element
  MEMUNION *prev_block;          //previous element
  MEMUNION *nextblock;           //next block in list
  MEMUNION *block;               //next block in list
  inT32 struct_count;            //no of required structs
  inT32 index;                   //to structure counts

  if (count < 1 || count > MAX_CHUNK) {
    tprintf ("Invalid size %d requested of free_struct", count);
    return;
  }

  //      tprintf("Freeing structure of size %d\n",count);
                                 //no of MEMUNIONS-1
  struct_count = (count - 1) / sizeof (MEMUNION);

  if (deadstruct == NULL) {
                                 //not really legal
    check_struct(MEMCHECKS, count);
  }
  else {
    if (struct_count < MAX_STRUCTS) {
                                 //can do fixed sizes
      #ifdef COUNTING_CLASS_STRUCTURES
      if (name != NULL) {
        index = identify_struct_owner (struct_count, name);
        if (index < MAX_CLASSES) {
          owner_counts[struct_count][index]--;
          ASSERT_HOST (owner_counts[struct_count][index] >= 0);
        }
      }
      #endif
      element = (MEMUNION *) deadstruct;
                                 //add to freelist
      element->ptr = free_structs[struct_count];
      free_structs[struct_count] = element;
                                 //one less in use
      structs_in_use[struct_count]--;
      if (structs_in_use[struct_count] == 0) {
        index = 0;
        for (element = struct_blocks[struct_count];
        element != NULL; element = nextblock) {
                                 //traverse and destroy
          nextblock = element->ptr;
                                 //free all the blocks
          old_struct_block(element);
          index++;
        }
                                 //none left any more
        struct_blocks[struct_count] = NULL;
                                 //no free structs
        free_structs[struct_count] = NULL;
        blocks_in_use[struct_count] = 0;
      }
      else if (structs_in_use[struct_count] < 0) {
        tprintf ("Negative number of structs of size %d in use",
          (int) count);
      }
      else if (structs_in_use[struct_count] < blocks_in_use[struct_count]) {
        prev_block = NULL;
        for (block = struct_blocks[struct_count];
        block != NULL; block = nextblock) {
          nextblock = block;
          index = STRUCT_BLOCK_SIZE / (struct_count + 1) - 1;
          end_element = block + STRUCT_BLOCK_SIZE;
          for (element = free_structs[struct_count];
          element != NULL; element = element->ptr) {
            if (element > nextblock && element < end_element) {
              index--;
              if (index == 0)
                break;
            }
          }
          if (index == 0) {
            index = STRUCT_BLOCK_SIZE / (struct_count + 1) - 1;
            for (element =
              free_structs[struct_count], prev_element = NULL;
            element != NULL; element = element->ptr) {
              if (element > nextblock && element < end_element) {
                index--;
                if (prev_element != NULL)
                  prev_element->ptr = element->ptr;
                else
                  free_structs[struct_count] = element->ptr;
                if (index == 0)
                  break;
              }
              else
                prev_element = element;
            }
            if (prev_block != NULL)
              prev_block->ptr = block->ptr;
            else
              struct_blocks[struct_count] = block->ptr;
            nextblock = block->ptr;
            blocks_in_use[struct_count]--;
                                 //free all the blocks
            old_struct_block(block);
          }
          else {
            prev_block = block;
                                 //traverse and destroy
            nextblock = block->ptr;
          }
        }
      }
    }
    else
      free_mem(deadstruct);  //free directly
  }
#else
  free(deadstruct);
#endif
}


/**********************************************************************
 * alloc_mem_p
 *
 * Allocate permanent space which will never be returned.
 * This space is allocated from the top end of a memory block to
 * avoid the fragmentation which would result from alternate use
 * of alloc_mem for permanent and temporary blocks.
 **********************************************************************/

//#ifdef __UNIX__
//#pragma OPT_LEVEL 0
//#endif
DLLSYM void *alloc_mem_p(             //allocate permanent space
                         inT32 count  //block size to allocate
                        ) {
  #ifdef RAYS_MALLOC
  #ifdef TESTING_BIGSTUFF
  if (main_mem.biggestblock == 0)
    main_mem.init (alloc_big_mem, free_big_mem,
      FIRSTSIZE, LASTSIZE, MAX_CHUNK);
  #else
  if (main_mem.biggestblock == 0)
    main_mem.init ((void *(*)(inT32)) malloc, free,
      FIRSTSIZE, LASTSIZE, MAX_CHUNK);
  #endif
  if (mem_mallocdepth > 0)
    return main_mem.alloc_p (count, trace_caller (mem_mallocdepth));
  else
    return main_mem.alloc_p (count, NULL);
  #else
  return malloc ((size_t) count);
  #endif
}


/**********************************************************************
 * alloc_mem
 *
 * Return a pointer to a buffer of count bytes aligned for any type.
 **********************************************************************/

DLLSYM void *alloc_mem(             //get some memory
                       inT32 count  //no of bytes to get
                      ) {
  #ifdef RAYS_MALLOC
  #ifdef TESTING_BIGSTUFF
  if (main_mem.biggestblock == 0)
    main_mem.init (alloc_big_mem, free_big_mem,
      FIRSTSIZE, LASTSIZE, MAX_CHUNK);
  #else
  if (main_mem.biggestblock == 0)
    main_mem.init ((void *(*)(inT32)) malloc, free,
      FIRSTSIZE, LASTSIZE, MAX_CHUNK);
  #endif
  if (mem_mallocdepth > 0)
    return main_mem.alloc (count, trace_caller (mem_mallocdepth));
  else
    return main_mem.alloc (count, NULL);
  #else
  return malloc ((size_t) count);
  #endif
}


/**********************************************************************
 * alloc_big_mem
 *
 * Return a pointer to a buffer of count bytes aligned for any type.
 **********************************************************************/

DLLSYM void *alloc_big_mem(             //get some memory
                           inT32 count  //no of bytes to get
                          ) {
  #ifdef TESTING_BIGSTUFF
  if (big_mem.biggestblock == 0)
    big_mem.init ((void *(*)(inT32)) malloc, free,
      BIGSIZE, BIGSIZE, MAX_BIGCHUNK);
  if (mem_mallocdepth > 0)
    return big_mem.alloc (count, trace_caller (mem_mallocdepth));
  else
    return big_mem.alloc (count, NULL);
  #else
  return malloc ((size_t) count);
  #endif
}


/**********************************************************************
 * alloc_big_zeros
 *
 * Return a pointer to a buffer of count bytes aligned for any type.
 **********************************************************************/

DLLSYM void *alloc_big_zeros(             //get some memory
                             inT32 count  //no of bytes to get
                            ) {
  #ifdef TESTING_BIGSTUFF
  if (big_mem.biggestblock == 0)
    big_mem.init ((void *(*)(inT32)) malloc, free,
      BIGSIZE, BIGSIZE, MAX_BIGCHUNK);
  void *buf;                     //return value

  if (mem_mallocdepth > 0)
    buf = big_mem.alloc (count, trace_caller (mem_mallocdepth));
  else
    buf = big_mem.alloc (count, NULL);
  memset (buf, 0, count);
  return buf;
  #else
  return calloc ((size_t) count, 1);
  #endif
}


/**********************************************************************
 * free_mem
 *
 * Free a block allocated by alloc_mem (or alloc_mem_p).
 * It checks that the pointer is legal and maintains counts of the
 * amount of free memory above and below the current free pointer.
 **********************************************************************/

DLLSYM void free_mem(                //free mem from alloc_mem
                     void *oldchunk  //chunk to free
                    ) {
  #ifdef RAYS_MALLOC
  if (mem_freedepth > 0 && main_mem.callers != NULL)
    main_mem.dealloc (oldchunk, trace_caller (mem_freedepth));
  else
    main_mem.dealloc (oldchunk, NULL);
  #else
  free(oldchunk);
  #endif
}


/**********************************************************************
 * free_big_mem
 *
 * Free a block allocated by alloc_big_mem.
 * It checks that the pointer is legal and maintains counts of the
 * amount of free memory above and below the current free pointer.
 **********************************************************************/

DLLSYM void free_big_mem(                //free mem from alloc_mem
                         void *oldchunk  //chunk to free
                        ) {
  #ifdef TESTING_BIGSTUFF
  if (mem_freedepth > 0 && main_mem.callers != NULL)
    big_mem.dealloc (oldchunk, trace_caller (mem_freedepth));
  else
    big_mem.dealloc (oldchunk, NULL);
  #else
  free(oldchunk);
  #endif
}