Updated copyright years.
[bpt/emacs.git] / src / ralloc.c
index aceac44..e5860a1 100644 (file)
@@ -1,5 +1,5 @@
 /* Block-relocating memory allocator. 
-   Copyright (C) 1992 Free Software Foundation, Inc.
+   Copyright (C) 1993 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -23,19 +23,67 @@ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
    rather than all of them.  This means allowing for a possible
    hole between the first bloc and the end of malloc storage. */
 
+#ifdef emacs
+
 #include "config.h"
 #include "lisp.h"              /* Needed for VALBITS.  */
+
 #undef NULL
-#include "mem_limits.h"
+
+/* The important properties of this type are that 1) it's a pointer, and
+   2) arithmetic on it should work as if the size of the object pointed
+   to has a size of 1.  */
+#ifdef __STDC__
+typedef void *POINTER;
+#else
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+typedef char *POINTER;
+
+#endif
+
+typedef unsigned long SIZE;
+
+/* Declared in dispnew.c, this version doesn't screw up if regions
+   overlap.  */
+extern void safe_bcopy ();
+
 #include "getpagesize.h"
 
+#else  /* Not emacs.  */
+
+#include <stddef.h>
+
+typedef size_t SIZE;
+typedef void *POINTER;
+
+#include <unistd.h>
+#include <malloc.h>
+#include <string.h>
+
+#define safe_bcopy(x, y, z) memmove (y, x, z)
+
+#endif /* emacs.  */
+
 #define NIL ((POINTER) 0)
 
+/* A flag to indicate whether we have initialized ralloc yet.  For
+   Emacs's sake, please do not make this local to malloc_init; on some
+   machines, the dumping procedure makes all static variables
+   read-only.  On these machines, the word static is #defined to be
+   the empty string, meaning that r_alloc_initialized becomes an
+   automatic variable, and loses its value each time Emacs is started up.  */
+static int r_alloc_initialized = 0;
+
+static void r_alloc_init ();
 \f
 /* Declarations for working with the malloc, ralloc, and system breaks.  */
 
-/* System call to set the break value. */
-extern POINTER sbrk ();
+/* Function to set the real break value. */
+static POINTER (*real_morecore) ();
 
 /* The break value, as seen by malloc (). */
 static POINTER virtual_break_value;
@@ -46,70 +94,29 @@ static POINTER break_value;
 /* The REAL (i.e., page aligned) break value of the process. */
 static POINTER page_break_value;
 
+/* This is the size of a page.  We round memory requests to this boundary.  */
+static int page_size;
+
+/* Whenever we get memory from the system, get this many extra bytes.  This 
+   must be a multiple of page_size.  */
+static int extra_bytes;
+
 /* Macros for rounding.  Note that rounding to any value is possible
    by changing the definition of PAGE. */
 #define PAGE (getpagesize ())
-#define ALIGNED(addr) (((unsigned int) (addr) & (PAGE - 1)) == 0)
-#define ROUNDUP(size) (((unsigned int) (size) + PAGE) & ~(PAGE - 1))
-#define ROUND_TO_PAGE(addr) (addr & (~(PAGE - 1)))
-\f
-/* Managing "almost out of memory" warnings.  */
-
-/* Level of warnings issued. */
-static int warnlevel;
-
-/* Function to call to issue a warning;
-   0 means don't issue them.  */
-static void (*warnfunction) ();
-
-static void
-check_memory_limits (address)
-     POINTER address;
-{
-  SIZE data_size = address - data_space_start;
-
-  switch (warnlevel)
-    {
-    case 0: 
-      if (data_size > (lim_data / 4) * 3)
-       {
-         warnlevel++;
-         (*warnfunction) ("Warning: past 75% of memory limit");
-       }
-      break;
-
-    case 1: 
-      if (data_size > (lim_data / 20) * 17)
-       {
-         warnlevel++;
-         (*warnfunction) ("Warning: past 85% of memory limit");
-       }
-      break;
-
-    case 2: 
-      if (data_size > (lim_data / 20) * 19)
-       {
-         warnlevel++;
-         (*warnfunction) ("Warning: past 95% of memory limit");
-       }
-      break;
-
-    default:
-      (*warnfunction) ("Warning: past acceptable memory limits");
-      break;
-    }
-
-    if (EXCEEDS_ELISP_PTR (address))
-      memory_full ();
-}
+#define ALIGNED(addr) (((unsigned int) (addr) & (page_size - 1)) == 0)
+#define ROUNDUP(size) (((unsigned int) (size) + page_size - 1) & ~(page_size - 1))
+#define ROUND_TO_PAGE(addr) (addr & (~(page_size - 1)))
 \f
 /* Functions to get and return memory from the system.  */
 
 /* Obtain SIZE bytes of space.  If enough space is not presently available
    in our process reserve, (i.e., (page_break_value - break_value)),
-   this means getting more page-aligned space from the system. */
+   this means getting more page-aligned space from the system.
 
-static void
+   Return non-zero if all went well, or zero if we couldn't allocate
+   the memory.  */
+static int
 obtain (size)
      SIZE size;
 {
@@ -118,28 +125,32 @@ obtain (size)
   if (already_available < size)
     {
       SIZE get = ROUNDUP (size - already_available);
+      /* Get some extra, so we can come here less often.  */
+      get += extra_bytes;
 
-      if (warnfunction)
-       check_memory_limits (page_break_value);
-
-      if (((int) sbrk (get)) < 0)
-       abort ();
+      if ((*real_morecore) (get) == 0)
+       return 0;
 
       page_break_value += get;
     }
 
   break_value += size;
+
+  return 1;
 }
 
-/* Obtain SIZE bytes of space and return a pointer to the new area. */
+/* Obtain SIZE bytes of space and return a pointer to the new area.
+   If we could not allocate the space, return zero.  */
 
 static POINTER
 get_more_space (size)
      SIZE size;
 {
   POINTER ptr = break_value;
-  obtain (size);
-  return ptr;
+  if (obtain (size))
+    return ptr;
+  else
+    return 0;
 }
 
 /* Note that SIZE bytes of space have been relinquished by the process.
@@ -150,17 +161,20 @@ relinquish (size)
      SIZE size;
 {
   POINTER new_page_break;
+  int excess;
 
   break_value -= size;
   new_page_break = (POINTER) ROUNDUP (break_value);
+  excess = (char *) page_break_value - (char *) new_page_break;
   
-  if (new_page_break != page_break_value)
+  if (excess > extra_bytes * 2)
     {
-      if (((int) (sbrk ((char *) new_page_break
-                       - (char *) page_break_value))) < 0)
+      /* Keep extra_bytes worth of empty space.
+        And don't free anything unless we can free at least extra_bytes.  */
+      if ((*real_morecore) (extra_bytes - excess) == 0)
        abort ();
 
-      page_break_value = new_page_break;
+      page_break_value += extra_bytes - excess;
     }
 
   /* Zero the space from the end of the "official" break to the actual
@@ -189,10 +203,6 @@ typedef struct bp
 /* Head and tail of the list of relocatable blocs. */
 static bloc_ptr first_bloc, last_bloc;
 
-/* Declared in dispnew.c, this version doesn't screw up if regions
-   overlap.  */
-extern void safe_bcopy ();
-
 /* Find the bloc referenced by the address in PTR.  Returns a pointer
    to that block. */
 
@@ -214,18 +224,27 @@ find_bloc (ptr)
 }
 
 /* Allocate a bloc of SIZE bytes and append it to the chain of blocs.
-   Returns a pointer to the new bloc. */
+   Returns a pointer to the new bloc, or zero if we couldn't allocate
+   memory for the new block.  */
 
 static bloc_ptr
 get_bloc (size)
      SIZE size;
 {
-  register bloc_ptr new_bloc = (bloc_ptr) malloc (BLOC_PTR_SIZE);
+  register bloc_ptr new_bloc;
+
+  if (! (new_bloc = (bloc_ptr) malloc (BLOC_PTR_SIZE))
+      || ! (new_bloc->data = get_more_space (size)))
+    {
+      if (new_bloc)
+       free (new_bloc);
+
+      return 0;
+    }
 
-  new_bloc->data = get_more_space (size);
   new_bloc->size = size;
   new_bloc->next = NIL_BLOC;
-  new_bloc->variable = NIL;
+  new_bloc->variable = (POINTER *) NIL;
 
   if (first_bloc)
     {
@@ -246,6 +265,8 @@ get_bloc (size)
    indicated by ADDRESS.  Direction of relocation is determined by
    the position of ADDRESS relative to BLOC->data.
 
+   If BLOC is NIL_BLOC, nothing is done.
+
    Note that ordering of blocs is not affected by this function. */
 
 static void
@@ -253,22 +274,24 @@ relocate_some_blocs (bloc, address)
      bloc_ptr bloc;
      POINTER address;
 {
-  register bloc_ptr b;
-  POINTER data_zone = bloc->data;
-  register SIZE data_zone_size = 0;
-  register SIZE offset = bloc->data - address;
-  POINTER new_data_zone = data_zone - offset;
-
-  for (b = bloc; b != NIL_BLOC; b = b->next)
+  if (bloc != NIL_BLOC)
     {
-      data_zone_size += b->size;
-      b->data -= offset;
-      *b->variable = b->data;
-    }
+      register SIZE offset = address - bloc->data;
+      register SIZE data_size = 0;
+      register bloc_ptr b;
+      
+      for (b = bloc; b != NIL_BLOC; b = b->next)
+       {
+         data_size += b->size;
+         b->data += offset;
+         *b->variable = b->data;
+       }
 
-  safe_bcopy (data_zone, new_data_zone, data_zone_size);
+      safe_bcopy (address - offset, address, data_size);
+    }
 }
 
+
 /* Free BLOC from the chain of blocs, relocating any blocs above it
    and returning BLOC->size bytes to the free area. */
 
@@ -289,15 +312,14 @@ free_bloc (bloc)
     {
       first_bloc = bloc->next;
       first_bloc->prev = NIL_BLOC;
-      relocate_some_blocs (bloc->next, bloc->data);
     }
   else
     {
       bloc->next->prev = bloc->prev;
       bloc->prev->next = bloc->next;
-      relocate_some_blocs (bloc->next, bloc->data);
     }
 
+  relocate_some_blocs (bloc->next, bloc->data);
   relinquish (bloc->size);
   free (bloc);
 }
@@ -306,46 +328,72 @@ free_bloc (bloc)
 
 static int use_relocatable_buffers;
 
-/* Obtain SIZE bytes of storage from the free pool, or the system,
-   as neccessary.  If relocatable blocs are in use, this means
-   relocating them. */
+/* Obtain SIZE bytes of storage from the free pool, or the system, as
+   necessary.  If relocatable blocs are in use, this means relocating
+   them.  This function gets plugged into the GNU malloc's __morecore
+   hook.
+
+   We provide hysteresis, never relocating by less than extra_bytes.
+
+   If we're out of memory, we should return zero, to imitate the other
+   __morecore hook values - in particular, __default_morecore in the
+   GNU malloc package.  */
 
 POINTER 
 r_alloc_sbrk (size)
      long size;
 {
+  /* This is the first address not currently available for the heap.  */
+  POINTER top;
+  /* Amount of empty space below that.  */
+  SIZE already_available;
   POINTER ptr;
 
   if (! use_relocatable_buffers)
-    return sbrk (size);
+    return (*real_morecore) (size);
+
+  top = first_bloc ? first_bloc->data : page_break_value;
+  already_available = (char *) top - (char *) virtual_break_value;
 
-  if (size > 0)
+  /* Do we not have enough gap already?  */
+  if (size > 0 && already_available < size)
     {
-      obtain (size);
+      /* Get what we need, plus some extra so we can come here less often.  */
+      SIZE get = size - already_available + extra_bytes;
+
+      if (! obtain (get))
+       return 0;
+
       if (first_bloc)
-       {
-         relocate_some_blocs (first_bloc, first_bloc->data + size);
+       relocate_some_blocs (first_bloc, first_bloc->data + get);
 
-         /* Zero out the space we just allocated, to help catch bugs
-            quickly.  */
-         bzero (virtual_break_value, size);
-       }
+      /* Zero out the space we just allocated, to help catch bugs
+        quickly.  */
+      bzero (virtual_break_value, get);
     }
-  else if (size < 0)
+  /* Can we keep extra_bytes of gap while freeing at least extra_bytes?  */
+  else if (size < 0 && already_available - size > 2 * extra_bytes)
     {
+      /* Ok, do so.  This is how many to free.  */
+      SIZE give_back = already_available - size - extra_bytes;
+
       if (first_bloc)
-        relocate_some_blocs (first_bloc, first_bloc->data + size);
-      relinquish (- size);
+       relocate_some_blocs (first_bloc, first_bloc->data - give_back);
+      relinquish (give_back);
     }
 
   ptr = virtual_break_value;
   virtual_break_value += size;
+
   return ptr;
 }
 
 /* Allocate a relocatable bloc of storage of size SIZE.  A pointer to
    the data is returned in *PTR.  PTR is thus the address of some variable
-   which will use the data area. */
+   which will use the data area.
+
+   If we can't allocate the necessary memory, set *PTR to zero, and
+   return zero.  */
 
 POINTER
 r_alloc (ptr, size)
@@ -354,14 +402,23 @@ r_alloc (ptr, size)
 {
   register bloc_ptr new_bloc;
 
+  if (! r_alloc_initialized)
+    r_alloc_init ();
+
   new_bloc = get_bloc (size);
-  new_bloc->variable = ptr;
-  *ptr = new_bloc->data;
+  if (new_bloc)
+    {
+      new_bloc->variable = ptr;
+      *ptr = new_bloc->data;
+    }
+  else
+    *ptr = 0;
 
   return *ptr;
 }
 
-/* Free a bloc of relocatable storage whose data is pointed to by PTR. */
+/* Free a bloc of relocatable storage whose data is pointed to by PTR.
+   Store 0 in *PTR to show there's no block allocated.  */
 
 void
 r_alloc_free (ptr)
@@ -374,37 +431,44 @@ r_alloc_free (ptr)
     abort ();
 
   free_bloc (dead_bloc);
+  *ptr = 0;
 }
 
-/* Given a pointer at address PTR to relocatable data, resize it
-   to SIZE.  This is done by obtaining a new block and freeing the
-   old, unless SIZE is less than or equal to the current bloc size,
-   in which case nothing happens and the current value is returned.
+/* Given a pointer at address PTR to relocatable data, resize it to SIZE.
+   Do this by shifting all blocks above this one up in memory, unless
+   SIZE is less than or equal to the current bloc size, in which case
+   do nothing.
 
-   The contents of PTR is changed to reflect the new bloc, and this
-   value is returned. */
+   Change *PTR to reflect the new bloc, and return this value.
+
+   If more memory cannot be allocated, then leave *PTR unchanged, and
+   return zero.  */
 
 POINTER
 r_re_alloc (ptr, size)
      POINTER *ptr;
      SIZE size;
 {
-  register bloc_ptr old_bloc, new_bloc;
+  register bloc_ptr bloc;
 
-  old_bloc = find_bloc (ptr);
-  if (old_bloc == NIL_BLOC)
+  bloc = find_bloc (ptr);
+  if (bloc == NIL_BLOC)
     abort ();
 
-  if (size <= old_bloc->size)
+  if (size <= bloc->size)
     /* Wouldn't it be useful to actually resize the bloc here?  */
     return *ptr;
 
-  new_bloc = get_bloc (size);
-  new_bloc->variable = ptr;
-  safe_bcopy (old_bloc->data, new_bloc->data, old_bloc->size);
-  *ptr = new_bloc->data;
+  if (! obtain (size - bloc->size))
+    return 0;
+
+  relocate_some_blocs (bloc->next, bloc->data + size);
 
-  free_bloc (old_bloc);
+  /* Zero out the new space in the bloc, to help catch bugs faster.  */
+  bzero (bloc->data + bloc->size, size - bloc->size);
+
+  /* Indicate that this block has a new size.  */
+  bloc->size = size;
 
   return *ptr;
 }
@@ -413,38 +477,28 @@ r_re_alloc (ptr, size)
    from the system.  */
 extern POINTER (*__morecore) ();
 
-/* A flag to indicate whether we have initialized ralloc yet.  For
-   Emacs's sake, please do not make this local to malloc_init; on some
-   machines, the dumping procedure makes all static variables
-   read-only.  On these machines, the word static is #defined to be
-   the empty string, meaning that malloc_initialized becomes an
-   automatic variable, and loses its value each time Emacs is started
-   up.  */
-static int malloc_initialized = 0;
-
 /* Intialize various things for memory allocation. */
 
-void
-malloc_init (start, warn_func)
-     POINTER start;
-     void (*warn_func) ();
+static void
+r_alloc_init ()
 {
-  if (start)
-    data_space_start = start;
-
-  if (malloc_initialized)
+  if (r_alloc_initialized)
     return;
 
-  malloc_initialized = 1;
+  r_alloc_initialized = 1;
+  real_morecore = __morecore;
   __morecore = r_alloc_sbrk;
-  virtual_break_value = break_value = sbrk (0);
+
+  virtual_break_value = break_value = (*real_morecore) (0);
+  if (break_value == NIL)
+    abort ();
+
+  page_size = PAGE;
+  extra_bytes = ROUNDUP (50000);
+
   page_break_value = (POINTER) ROUNDUP (break_value);
+  /* Clear the rest of the last page; this memory is in our address space
+     even though it is after the sbrk value.  */
   bzero (break_value, (page_break_value - break_value));
   use_relocatable_buffers = 1;
-
-  lim_data = 0;
-  warnlevel = 0;
-  warnfunction = warn_func;
-
-  get_lim_data ();
 }