text: /* 
   * Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers
   * Copyright (c) 1991-1994 by Xerox Corporation.  All rights reserved.
   *
   * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
   * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
   *
   * Permission is hereby granted to use or copy this program
   * for any purpose,  provided the above notices are retained on all copies.
   * Permission to modify the code and to distribute modified code is granted,
   * provided the above notices are retained, and a notice that the code was
   * modified is included with the above copyright notice.
   */
  /* Boehm, July 31, 1995 5:02 pm PDT */
  
  
  #include "private/gc_priv.h"
  
  # ifdef STUBBORN_ALLOC
  /* Stubborn object (hard to change, nearly immutable) allocation. */
  
  extern ptr_t GC_clear_stack();	/* in misc.c, behaves like identity */
  
  #define GENERAL_MALLOC(lb,k) \
      (GC_PTR)GC_clear_stack(GC_generic_malloc((word)lb, k))
  
  /* Data structure representing immutable objects that 	*/
  /* are still being initialized.				*/
  /* This is a bit baroque in order to avoid acquiring	*/
  /* the lock twice for a typical allocation.		*/
  
  GC_PTR * GC_changing_list_start;
  
  void GC_push_stubborn_structures GC_PROTO((void))
  {
      GC_push_all((ptr_t)(&GC_changing_list_start),
  		(ptr_t)(&GC_changing_list_start) + sizeof(GC_PTR *));
  }
  
  # ifdef THREADS
    VOLATILE GC_PTR * VOLATILE GC_changing_list_current;
  # else
    GC_PTR * GC_changing_list_current;
  # endif
  	/* Points at last added element.  Also (ab)used for		*/
  	/* synchronization.  Updates and reads are assumed atomic.	*/
  
  GC_PTR * GC_changing_list_limit;
  	/* Points at the last word of the buffer, which is always 0	*/
  	/* All entries in (GC_changing_list_current,			*/
  	/* GC_changing_list_limit] are 0				*/
  
  
  void GC_stubborn_init()
  {
  #   define INIT_SIZE 10
  
      GC_changing_list_start = (GC_PTR *)
      			GC_INTERNAL_MALLOC(
      				(word)(INIT_SIZE * sizeof(GC_PTR)),
      				PTRFREE);
      BZERO(GC_changing_list_start,
      	  INIT_SIZE * sizeof(GC_PTR));
      if (GC_changing_list_start == 0) {
          GC_err_printf0("Insufficient space to start up\n");
          ABORT("GC_stubborn_init: put of space");
      }
      GC_changing_list_current = GC_changing_list_start;
      GC_changing_list_limit = GC_changing_list_start + INIT_SIZE - 1;
      * GC_changing_list_limit = 0;
  }
  
  /* Compact and possibly grow GC_uninit_list.  The old copy is		*/
  /* left alone.	Lock must be held.					*/
  /* When called GC_changing_list_current == GC_changing_list_limit	*/
  /* which is one past the current element.				*/
  /* When we finish GC_changing_list_current again points one past last	*/
  /* element.								*/
  /* Invariant while this is running: GC_changing_list_current    	*/
  /* points at a word containing 0.					*/
  /* Returns FALSE on failure.						*/
  GC_bool GC_compact_changing_list()
  {
      register GC_PTR *p, *q;
      register word count = 0;
      word old_size = (char **)GC_changing_list_limit
      		    - (char **)GC_changing_list_start+1;
      		    /* The casts are needed as a workaround for an Amiga bug */
      register word new_size = old_size;
      GC_PTR * new_list;
      
      for (p = GC_changing_list_start; p < GC_changing_list_limit; p++) {
          if (*p != 0) count++;
      }
      if (2 * count > old_size) new_size = 2 * count;
      new_list = (GC_PTR *)
      		GC_INTERNAL_MALLOC(
      				new_size * sizeof(GC_PTR), PTRFREE);
      		/* PTRFREE is a lie.  But we don't want the collector to  */
      		/* consider these.  We do want the list itself to be  	  */
      		/* collectable.						  */
      if (new_list == 0) return(FALSE);
      BZERO(new_list, new_size * sizeof(GC_PTR));
      q = new_list;
      for (p = GC_changing_list_start; p < GC_changing_list_limit; p++) {
          if (*p != 0) *q++ = *p;
      }
      GC_changing_list_start = new_list;
      GC_changing_list_limit = new_list + new_size - 1;
      GC_changing_list_current = q;
      return(TRUE);
  }
  
  /* Add p to changing list.  Clear p on failure.	*/
  # define ADD_CHANGING(p) \
  	{	\
  	    register struct hblk * h = HBLKPTR(p);	\
  	    register word index = PHT_HASH(h);	\
  	    \
  	    set_pht_entry_from_index(GC_changed_pages, index);	\
  	}	\
  	if (*GC_changing_list_current != 0 \
  	    && ++GC_changing_list_current == GC_changing_list_limit) { \
  	    if (!GC_compact_changing_list()) (p) = 0; \
  	} \
  	*GC_changing_list_current = p;
  
  void GC_change_stubborn(p)
  GC_PTR p;
  {
      DCL_LOCK_STATE;
      
      DISABLE_SIGNALS();
      LOCK();
      ADD_CHANGING(p);
      UNLOCK();
      ENABLE_SIGNALS();
  }
  
  void GC_end_stubborn_change(p)
  GC_PTR p;
  {
  #   ifdef THREADS
        register VOLATILE GC_PTR * my_current = GC_changing_list_current;
  #   else
        register GC_PTR * my_current = GC_changing_list_current;
  #   endif
      register GC_bool tried_quick;
      DCL_LOCK_STATE;
      
      if (*my_current == p) {
          /* Hopefully the normal case.					*/
          /* Compaction could not have been running when we started.	*/
          *my_current = 0;
  #	ifdef THREADS
            if (my_current == GC_changing_list_current) {
              /* Compaction can't have run in the interim. 	*/
              /* We got away with the quick and dirty approach.   */
              return;
            }
            tried_quick = TRUE;
  #	else
  	  return;
  #	endif
      } else {
          tried_quick = FALSE;
      }
      DISABLE_SIGNALS();
      LOCK();
      my_current = GC_changing_list_current;
      for (; my_current >= GC_changing_list_start; my_current--) {
          if (*my_current == p) {
              *my_current = 0;
              UNLOCK();
              ENABLE_SIGNALS();
              return;
          }
      }
      if (!tried_quick) {
          GC_err_printf1("Bad arg to GC_end_stubborn_change: 0x%lx\n",
          	       (unsigned long)p);
          ABORT("Bad arg to GC_end_stubborn_change");
      }
      UNLOCK();
      ENABLE_SIGNALS();
  }
  
  /* Allocate lb bytes of composite (pointerful) data	*/
  /* No pointer fields may be changed after a call to	*/
  /* GC_end_stubborn_change(p) where p is the value	*/
  /* returned by GC_malloc_stubborn.			*/
  # ifdef __STDC__
      GC_PTR GC_malloc_stubborn(size_t lb)
  # else
      GC_PTR GC_malloc_stubborn(lb)
      size_t lb;
  # endif
  {
  register ptr_t op;
  register ptr_t *opp;
  register word lw;
  ptr_t result;
  DCL_LOCK_STATE;
  
      if( SMALL_OBJ(lb) ) {
  #       ifdef MERGE_SIZES
  	  lw = GC_size_map[lb];
  #	else
  	  lw = ALIGNED_WORDS(lb);
  #       endif
  	opp = &(GC_sobjfreelist[lw]);
  	FASTLOCK();
          if( !FASTLOCK_SUCCEEDED() || (op = *opp) == 0 ) {
              FASTUNLOCK();
              result = GC_generic_malloc((word)lb, STUBBORN);
              goto record;
          }
          *opp = obj_link(op);
          obj_link(op) = 0;
          GC_words_allocd += lw;
          result = (GC_PTR) op;
          ADD_CHANGING(result);
          FASTUNLOCK();
          return((GC_PTR)result);
     } else {
         result = (GC_PTR)
            	GC_generic_malloc((word)lb, STUBBORN);
     }
  record:
     DISABLE_SIGNALS();
     LOCK();
     ADD_CHANGING(result);
     UNLOCK();
     ENABLE_SIGNALS();
     return((GC_PTR)GC_clear_stack(result));
  }
  
  
  /* Functions analogous to GC_read_dirty and GC_page_was_dirty.	*/
  /* Report pages on which stubborn objects were changed.		*/
  void GC_read_changed()
  {
      register GC_PTR * p = GC_changing_list_start;
      register GC_PTR q;
      register struct hblk * h;
      register word index;
      
      if (p == 0) /* initializing */ return;
      BCOPY(GC_changed_pages, GC_prev_changed_pages,
            (sizeof GC_changed_pages));
      BZERO(GC_changed_pages, (sizeof GC_changed_pages));
      for (; p <= GC_changing_list_current; p++) {
          if ((q = *p) != 0) {
              h = HBLKPTR(q);
              index = PHT_HASH(h);
              set_pht_entry_from_index(GC_changed_pages, index);
          }
      }
  }
  
  GC_bool GC_page_was_changed(h)
  struct hblk * h;
  {
      register word index = PHT_HASH(h);
      
      return(get_pht_entry_from_index(GC_prev_changed_pages, index));
  }
  
  /* Remove unreachable entries from changed list. Should only be	*/
  /* called with mark bits consistent and lock held.		*/
  void GC_clean_changing_list()
  {
      register GC_PTR * p = GC_changing_list_start;
      register GC_PTR q;
      register ptr_t r;
      register unsigned long count = 0;
      register unsigned long dropped_count = 0;
      
      if (p == 0) /* initializing */ return;
      for (; p <= GC_changing_list_current; p++) {
          if ((q = *p) != 0) {
              count++;
              r = (ptr_t)GC_base(q);
              if (r == 0 || !GC_is_marked(r)) {
                  *p = 0;
                  dropped_count++;
  	    }
          }
      }
  #   ifdef PRINTSTATS
        if (count > 0) {
          GC_printf2("%lu entries in changing list: reclaimed %lu\n",
                    (unsigned long)count, (unsigned long)dropped_count);
        }
  #   endif
  }
  
  #else /* !STUBBORN_ALLOC */
  
  # ifdef __STDC__
      GC_PTR GC_malloc_stubborn(size_t lb)
  # else
      GC_PTR GC_malloc_stubborn(lb)
      size_t lb;
  # endif
  {
      return(GC_malloc(lb));
  }
  
  /*ARGSUSED*/
  void GC_end_stubborn_change(p)
  GC_PTR p;
  {
  }
  
  /*ARGSUSED*/
  void GC_change_stubborn(p)
  GC_PTR p;
  {
  }
  
  void GC_push_stubborn_structures GC_PROTO((void))
  {
  }
  
  #endif
  