/* File gc.c                    Copyright (c) Codemist Ltd, 1990-2003 */

/*
 * Garbage collection.
 *    Fourth major version - now using Foster-style
 *    algorithm for relocating vector heap, and support for handling
 *    BPS via segmented handles. Pointer-reversing mark phase to go
 *    with same.
 *
 *    Furthermore there is (optionally) a copying 2-space garbage
 *    collector as well as the mark/slide one.  Now do you understand
 *    why this file seems so very long?
 *
 * The code in parts of this file (and also in preserve.c & restart.c)
 * is painfully sensitive to memory layout and I have some messy
 * conditional inclusion of code depending on whether a Lisp_Object is
 * a 32 or 64-bit value.
 */

/*
 * This code may be used and modified, and redistributed in binary
 * or source form, subject to the "CCL Public License", which should
 * accompany it. This license is a variant on the BSD license, and thus
 * permits use of code derived from this in either open and commercial
 * projects: but it does require that updates to this code be made
 * available back to the originators of the package.
 * Before merging other code in with this or linking this code
 * with other packages or libraries please check that the license terms
 * of the other material are compatible with those of this.
 */


/* Signature: 0651f4cb 13-Jul-2003 */

#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include "machine.h"
#include "tags.h"
#include "cslerror.h"
#include "externs.h"
#include "arith.h"
#include "entries.h"
#include "sys.h"
#include "stream.h"
#ifdef SOCKETS
#include "sockhdr.h"
#endif
#ifdef TIMEOUT
#include "timeout.h"
#endif

/*
 * This function is provided so that if some C compiler generates bad
 * code and I can not see a better way of correcting things, and if the
 * bad code goes away when there are lots of function calls to cause the
 * compiler to unload its registers etc, then I can write things like
 *     dummy_function_call("Ha Ha", reference_this_var, and_this);
 * without it having any big effect on the results I obtain. I will
 * generally want to guard any such un-natural calls by a test that identifies
 * the C compiler involved. Of course I must try not to use this as a way
 * of permitting system-sensitive code to creep into CSL...
 */

void MS_CDECL dummy_function_call(char *why, ...)
{
    CSL_IGNORE(why);
    return;
}

CSLbool gc_method_is_copying;     /* YES if copying, NO if sliding */
int32 gc_number = 0;

static intxx cons_cells, symbol_heads, strings, user_vectors,
             big_numbers, box_floats, bytestreams, other_mem,
             litvecs, getvecs;

#define is_immed(x) (is_immed_or_cons(x) && !is_cons(x))

#ifndef DEMO_MODE

static void non_recursive_mark(Lisp_Object *top)
{
/*
 * This code is written about as neatly as I know how ... I want to think of
 * it in terms of three chunks - descending down into lists, regular
 * climbing back out, and the special case of climbing back out when I have
 * just processed a vector.  I like to think of this as a finite state
 * machine with three major groups of states, and a bunch of subsidiary
 * states that deal with (e.g.) scanning along vectors.
 */
    Lisp_Object b = (Lisp_Object)top,
                p = *top,
                w,
                nil = C_nil;
    Header h, *q;
    intxx i;
/*
 * When I get to descend I have b as an unmarked address that is either
 * equal to top, or is a back-pointer as set up below.  p is a normal
 * (i.e. unmarked) Lisp pointer, representing a tree to be marked. Only
 * at the very top of a call can p be immediate data at descend, and in that
 * case no marking is needed.
 * NB that the initial back pointer will seem tagged as either a CONS or a
 * SYMBOL, but certainly as a pointer datatype.
 */

descend:
    switch ((int)p & TAG_BITS)
    {
/*
 * If I have a cons cell I need to check if it has been visited before or
 * if one or both components are immediate - and extend my backwards
 * chain one step.
 */
case TAG_CONS:
#ifdef COMMON
        if (p == nil) goto ascend;
#endif
        w = qcar(p);
        if (is_immed(w))
        {   if (is_marked_i(w)) goto ascend;
/*
 * To test if this cons cell was marked I had to classify the item
 * in its car, and if this was immediate data it makes sense to go
 * right ahead and probe the cdr.
 */
            qcar(p) = flip_mark_bit_i(w);
            w = qcdr(p);
/*
 * Since I am not allowing myself to descend onto immediate data
 * I check for it here, and if both car and cdr of p were immediate
 * I can ascend forthwith.
 */
            if (is_immed(w) || w == nil) goto ascend;
/*
 * Here I fill in a back-pointer and descend into the cdr of a pair.
 */
            qcdr(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
        else if (is_marked_p(w)) goto ascend;
/*
 * Here I fill in a back-pointer and descend into the car of a pair.
 * [would it be worth taking a special case on w == nil here?]
 */
        qcar(p) = flip_mark_bit_p(b);
        b = p;
        p = w;
        goto descend;

/*
 *  case TAG_FIXNUM:
 *  case TAG_ODDS:
 *  case TAG_SFLOAT:
 */
default:
        return;             /* assert (b==(Lisp_Object)top) here. */

case TAG_SYMBOL:
#ifndef COMMON
        if (p == nil) goto ascend;
#endif
        h = qheader(p);
/*
 * When I have finished every item that has been visited must be marked,
 * with cons cells marked in their car fields and vectors etc in the header
 * word.  Furthermore the header of all vectors (including symbols) must
 * have been replaced by the start of a back-pointer chain identifying the
 * words that started off pointing at the vector.  The pointers in this
 * chain must be marked, word-aligned pointers. Note a special curiosity:
 * the back-chain of references to a vector can thread through the CDR
 * field of CONS cells and through either odd or even words within vectors.
 * Thus althouh marked with the pointer mark bit the rest of tagging on these
 * chain words is a bit funny! Specifically the tag bits will say "0" or "4",
 * ie CONS or SYMBOL (and not ODDS).
 */
        if (!is_odds(h) || is_marked_h(h))  /* Visited before       */
        {   q = &qheader(p);                /* where I should chain */
            p = h;                          /* the previous header  */
            goto ascend_from_vector;
        }
/*
 * Now this is the first visit to a symbol.
 */
        qheader(p) = h = flip_mark_bit_h(h);
/*
 * When components of a symbol are immediate or nil I do nothing.
 * (the test for nil is because I expect it to be cheap and to catch
 * common cases)
 */
        w = qvalue(p);
        if (!is_immed(w) && w != nil)
        {   qvalue(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
        w = qenv(p);
        if (!is_immed(w) && w != nil)
        {   qenv(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
        w = qpname(p);
        if (!is_immed(w) && w != nil)
        {   qpname(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
        w = qplist(p);
        if (!is_immed(w) && w != nil)
        {   qplist(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
        w = qfastgets(p);
        if (!is_immed(w) && w != nil)
        {   qfastgets(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
#ifdef COMMON
        w = qpackage(p);
        if (!is_immed(w) && w != nil)
        {   qpackage(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
#endif
/*
 * When all components of the vector are marked I climb up the
 * back-pointer chain.
 */
        q = &qheader(p);
        p = h;
        goto ascend_from_vector;

case TAG_NUMBERS:
        h = numhdr(p);
        if (!is_odds(h) || is_marked_h(h)) /* marked already. */
        {   q = &numhdr(p);
            p = h;
            goto ascend_from_vector;
        }
/*
 * For CSL the only case here is that of big integers, which have just
 * binary data in them.  For Common Lisp I also have to cope with
 * ratios and complex numbers.
 */
        if (is_bignum_header(h))
        {   q = &numhdr(p);
            p = flip_mark_bit_h(h);
            goto ascend_from_vector;
        }
#ifdef COMMON
        numhdr(p) = h = flip_mark_bit_h(h);
        w = real_part(p);   /* Or numerator of a ratio! */
        if (!is_immed(w))
        {   real_part(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
        w = imag_part(p);   /* Or denominator of a ratio! */
        if (!is_immed(w))
        {   imag_part(p) = flip_mark_bit_p(b);
            b = p;
            p = w;
            goto descend;
        }
/*
 * get here if both components of a ratio/complex are immediate (e.g fixnums)
 */
        q = &numhdr(p);
        p = h;
        goto ascend_from_vector;
#else
        term_printf("Bad numeric code detected in GC\n");
        ensure_screen();
        abort(); /* Bad numeric type in CSL mode. */
#endif

case TAG_BOXFLOAT:
        h = flthdr(p);
        if (!is_odds(h) || is_marked_h(h))
        {   q = &flthdr(p);
            p = h;
            goto ascend_from_vector;
        }
        q = &flthdr(p);
        p = flip_mark_bit_h(h);
        goto ascend_from_vector;

case TAG_VECTOR:
        h = vechdr(p);
        if (!is_odds(h) || is_marked_h(h))
        {   q = &vechdr(p);
            p = h;
            goto ascend_from_vector;
        }
        if (vector_holds_binary(h))
        {   q = &vechdr(p);
            p = flip_mark_bit_h(h);
            goto ascend_from_vector;
        }
        vechdr(p) = h = flip_mark_bit_h(h);
        i = (intxx)doubleword_align_up(length_of_header(h));
        if (is_mixed_header(h)) 
            i = 4*CELL;  /* Only use first few pointers */
        while (i >= 2*CELL)
        {   i -= CELL;
            q = (Header *)((char *)p - TAG_VECTOR + i);
            w = *q;
            if (is_immed(w) || w == nil) continue;
/*
 * For vectors I have to use all available mark bits to keep track of
 * where I am...
 */
            if (i == CELL)
/*
 * When descending into the first (or only) word of a vector I leave the
 * back-pointer marked, and note that the header word just before it
 * will be marked (either as a header word or as a pointer)
 */
            {   *q = flip_mark_bit_p(b);
                b = p;
                p = w;
            }
#ifndef ADDRESS_64
            else if ((i & 4) == 0)
#endif
/*
 * When descending a pointer at an even (word) address I leave the
 * back-pointer unmarked.
 */
            {   *q = b;
                b = (Lisp_Object)((char *)p + i);
                p = w;
            }
#ifndef ADDRESS_64
            else
/*
 * Finally when I descend into a pointer at an odd (word) address other
 * than the special case of the first such, I leave an unmarked back-pointer
 * but mark the word before the one I am following.  The effect of all this is
 * that when I get back to the vector I am able to discriminate between these
 * various cases...
 */
            {   *q = b;
                b = (Lisp_Object)((char *)p + i - 4);
                p = w;
                w = *(Lisp_Object *)((char *)b - TAG_VECTOR);
                if (is_immed(w)) w = flip_mark_bit_i(w);
                else w = flip_mark_bit_p(w);
                *(Lisp_Object *)((char *)b - TAG_VECTOR) = w;
            }
#endif /* ADDRESS_64 */
            goto descend;
        }
/*
 * I drop through to here if all items in the vector were in fact
 * immediate values (e.g. fixnums), and thus there was no need to
 * dig deeper.
 */
        q = &vechdr(p);
        p = h;
        goto ascend_from_vector;
    }





/*
 * When I get to ascend b is a back-pointer, and p is an unmarked pointer
 * to be put back into the place I descended through.
 */
ascend:
    if (b == (Lisp_Object)top) return;

    switch ((int)b & TAG_BITS)
    {
default:
        term_printf("Bad tag bits in GC\n");
        ensure_screen();
        abort();

case TAG_CONS:
        w = qcdr(b);
        if (is_immed(w) || w == nil)
        {   w = qcar(b);
            qcar(b) = flip_mark_bit_p(p);
            p = b;
            b = flip_mark_bit_p(w);
            goto ascend;
        }
        else if (is_marked_p(w))
        {   qcdr(b) = p;
            p = b;
            b = flip_mark_bit_p(w);
            goto ascend;
        }
        else
        {   qcdr(b) = qcar(b);
            qcar(b) = flip_mark_bit_p(p);
            p = w;
            goto descend;
        }

case TAG_SYMBOL:
#ifdef COMMON
        w = qpackage(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qpackage(b) = p;
            goto try_nothing;
        }
#endif
        w = qfastgets(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qfastgets(b) = p;
            goto try_package;
        }
        w = qplist(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qplist(b) = p;
            goto try_fastgets;
        }
        w = qpname(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qpname(b) = p;
            goto try_plist;
        }
        w = qenv(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qenv(b) = p;
            goto try_pname;
        }
        w = qvalue(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qvalue(b) = p;
            goto try_env;
        }
        term_printf("Backpointer not found in GC\n");
        ensure_screen();
        abort();            /* backpointer not found */

try_env:
        p = qenv(b);
        if (!is_immed(p) && p != nil && !is_marked_p(p))
        {   qenv(b) = w;
            goto descend;
        }
try_pname:
        p = qpname(b);
        if (!is_immed(p) && p != nil && !is_marked_p(p))
        {   qpname(b) = w;
            goto descend;
        }
try_plist:
        p = qplist(b);
        if (!is_immed(p) && p != nil && !is_marked_p(p))
        {   qplist(b) = w;
            goto descend;
        }
try_fastgets:
        p = qfastgets(b);
        if (!is_immed(p) && p != nil && !is_marked_p(p))
        {   qfastgets(b) = w;
            goto descend;
        }
try_package:
#ifdef COMMON
        p = qpackage(b);
        if (!is_immed(p) && p != nil && !is_marked_p(p))
        {   qpackage(b) = w;
            goto descend;
        }
try_nothing:
#endif
        q = &qheader(b);
        p = *q;
        b = flip_mark_bit_p(w);
        goto ascend_from_vector;

#ifdef COMMON
case TAG_NUMBERS:
/*
 * If I get back to a NUMBERS than it must have been a ratio or a complex.
 */
        w = imag_part(b);
        if (is_immed(w))
        {   w = real_part(b);
            real_part(b) = p;
            q = &numhdr(b);
            p = *q;
            b = flip_mark_bit_p(w);
            goto ascend_from_vector;
        }
        else if (is_marked_p(w))
        {   imag_part(b) = p;
            q = &numhdr(p);
            p = *q;
            b = flip_mark_bit_p(w);
            goto ascend_from_vector;
        }
        else
        {   imag_part(b) = real_part(b);
            real_part(b) = p;
            p = w;
            goto descend;
        }
#endif

case TAG_VECTOR:
/*
 * If I get back to a vector it must have been a vector of Lisp_Objects,
 * not a vector of binary data.  My back-pointer points part-way into it.
 * The back-pointer will be doubleword aligned, so on 32-bit systems
 * it is not quite enough to tell me which cell of the vector was involved,
 * and so in that case I do a further inspection of mark bits in the two
 * parts of the doubelword concerned.
 */
        w = *(Lisp_Object *)((char *)b - TAG_VECTOR);
#ifndef ADDRESS_64
        if (is_immed(w) || is_marked_p(w))
/*
 * Here I had been marking the pointer that was stored at an odd (word)
 * address.
 */
        {   Lisp_Object w1 = *(Lisp_Object *)((char *)b - TAG_VECTOR + 4);
            *(Lisp_Object *)((char *)b - TAG_VECTOR + 4) = p;
            if (is_marked_p(w1))     /* End of this vector */
            {   q = (Header *)((char *)b - TAG_VECTOR);
                p = w;
                b = flip_mark_bit_p(w1);
                goto ascend_from_vector;
            }
            p = w;
            w = w1;
            if (!is_immed(p))
            {   p = flip_mark_bit_p(p);
                if (p != nil)
                {   *(Lisp_Object *)((char *)b - TAG_VECTOR) = w1;
                    goto descend;
                }
            }
            else p = flip_mark_bit_i(p);
        }
#endif /* ADDRESS_64 */
        *(Lisp_Object *)((char *)b - TAG_VECTOR) = p;
/*
 * Now the doubleword I returned to has been marked (and tidied up),
 * so I need to scan back towards the header.
 */
scan_vector_more:
        for (;;)
        {   Lisp_Object w2;
/*
 * NB on the next line I step back by 8 on both 32 and 64-bit machines!
 * That is because the back-pointers I use can only refer to a doubleword
 * so on 32-bit systems I have to go 2 cells at a go. Ugh.
 */
            b = (Lisp_Object)((char *)b - 8);
            w2 = *(Lisp_Object *)((char *)b - TAG_VECTOR);
#ifndef ADDRESS_64
            p = *(Lisp_Object *)((char *)b - TAG_VECTOR + CELL);
#endif
            if ((is_odds(w2) && is_header(w2)) ||
                (!is_immed(w2) && is_marked_p(w2)))
/*
 * In this case I have reached the doubleword containing the header.
 */
            {
#ifndef ADDRESS_64
                if (!is_immed(p) && p != nil)
                {   *(Lisp_Object *)((char *)b - TAG_VECTOR + CELL) =
                        flip_mark_bit_p(w);
                    goto descend;
                }
                else
#endif
                {   q = (Header *)((char *)b - TAG_VECTOR);
                    p = w2;
                    b = w;
                    goto ascend_from_vector;
                }
            }
/*
 * Otherwise I have another general doubleword to cope with.
 */
#ifndef ADDRESS_64
            if (!is_immed(p) && p != nil)
            {   if (is_immed(w2)) w2 = flip_mark_bit_i(w2);
                else w2 = flip_mark_bit_p(w2);
                *(Lisp_Object *)((char *)b - TAG_VECTOR) = w2;
                *(Lisp_Object *)((char *)b - TAG_VECTOR + CELL) = w;
                goto descend;
            }
#endif
            if (!is_immed(w2) && w2 != nil)
            {   p = w2;
                *(Lisp_Object *)((char *)b - TAG_VECTOR) = w;
                goto descend;
            }
            continue;   /* Step back another doubleword */
        }
    }

ascend_from_vector:
/*
 * Here the item just marked was a vector.  I need to leave a reversed
 * chain of pointers through its header word. q points to that header,
 * and p contains what used to be in the word at q.
 */
    if (b == (Lisp_Object)top)
    {   *q = flip_mark_bit_p(b);
        *top = p;
        return;
    }

    switch ((int)b & TAG_BITS)
    {
default:
        term_printf("Bad tag bits in GC\n");
        ensure_screen();
        abort();

case TAG_CONS:
        w = qcdr(b);
        if (is_immed(w) || w == nil)
        {   w = qcar(b);
            qcar(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qcar(b));
            p = b;
            b = flip_mark_bit_p(w);
            goto ascend;
        }
        else if (is_marked_p(w))
        {   qcdr(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qcdr(b));
            p = b;
            b = flip_mark_bit_p(w);
            goto ascend;
        }
        else
        {   qcdr(b) = qcar(b);
            qcar(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qcar(b));
            p = w;
            goto descend;
        }

case TAG_SYMBOL:
#ifdef COMMON
        w = qpackage(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qpackage(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qpackage(b));
            goto try_nothing;
        }
#endif
        w = qfastgets(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qfastgets(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qfastgets(b));
            goto try_package;
        }
        w = qplist(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qplist(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qplist(b));
            goto try_fastgets;
        }
        w = qpname(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qpname(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qpname(b));
            goto try_plist;
        }
        w = qenv(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qenv(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qenv(b));
            goto try_pname;
        }
        w = qvalue(b);
        if (!is_immed(w) && is_marked_p(w))
        {   qvalue(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&qvalue(b));
            goto try_env;
        }
        term_printf("Failure in GC\n");
        ensure_screen();
        abort();

#ifdef COMMON
case TAG_NUMBERS:
/*
 * If I get back to a NUMBERS than it must have been a ratio or a complex.
 */
        w = imag_part(b);
        if (is_immed(w))
        {   w = real_part(b);
            real_part(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&real_part(b));
            q = &numhdr(b);
            p = *q;
            b = flip_mark_bit_p(w);
            goto ascend_from_vector;
        }
        else if (is_marked_p(w))
        {   imag_part(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&imag_part(b));
            q = &numhdr(p);
            p = *q;
            b = flip_mark_bit_p(w);
            goto ascend_from_vector;
        }
        else
        {   imag_part(b) = real_part(b);
            real_part(b) = p;
            *q = flip_mark_bit_p((Lisp_Object *)&real_part(b));
            p = w;
            goto descend;
        }
#endif

case TAG_VECTOR:
/*
 * If I get back to a vector it must have been a vector of Lisp_Objects,
 * not a vector of binary data.  My back-pointer points part-way into it.
 * I can tell where I am by inspecting the state of mark bits on both parts
 * of the doubleword so identified.
 */
        w = *(Lisp_Object *)((char *)b - TAG_VECTOR);
#ifndef ADDRESS_64
        if (is_immed(w) || is_marked_p(w))
/*
 * Here I had been marking the pointer that was stored at an odd (word)
 * address.
 */
        {   Lisp_Object w1 = *(Lisp_Object *)((char *)b - TAG_VECTOR + 4);
            *(Lisp_Object *)((char *)b - TAG_VECTOR + 4) = p;
            *q = flip_mark_bit_p((Lisp_Object)((char *)b - TAG_VECTOR + 4));
            if (is_marked_p(w1))     /* End of this vector */
            {   q = (Header *)((char *)b - TAG_VECTOR);
                p = *q;              /* May not be same as w still! */
                b = flip_mark_bit_p(w1);
                goto ascend_from_vector;
            }
            p = w;
            w = w1;
            if (!is_immed(p))
            {   p = flip_mark_bit_p(p);
                if (p != nil)
                {   *(Lisp_Object *)((char *)b - TAG_VECTOR) = w1;
                    goto descend;
                }
            }
            else p = flip_mark_bit_i(p);
            *(Lisp_Object *)((char *)b - TAG_VECTOR) = p;
        }
        else
#endif /* ADDRESS_64 */
        {   *(Lisp_Object *)((char *)b - TAG_VECTOR) = p;
            *q = flip_mark_bit_p((Lisp_Object)((char *)b - TAG_VECTOR));
        }

/*
 * Now the doubleword I returned to has been marked (and tidied up),
 * so I need to scan back towards the header.
 */
        goto scan_vector_more;
    }
}

static void mark(Lisp_Object *pp)
{
/*
 * This mark procedure works by using the regular Lisp stack to
 * store things while traversing the lists.  A null pointer on the
 * stack marks the end of the section that is being used.  If too
 * much stack is (about to be) used I switch over to the pointer-
 * reversing code given above, which is slower but which uses
 * bounded workspace.  My measurements (on just one computer) show the
 * stack-based code only 25% faster than the pointer-reversing version,
 * which HARDLY seems enough to justify all this extra code, but then
 * fast garbage collection is very desirable and every little speed-up
 * will help.
 */
    Lisp_Object p, q, nil = C_nil;
    Lisp_Object *sp = stack, *sl = stacklimit;
    Header h;
    intxx i;
    *++sp = (Lisp_Object)NULL;
top:
/*
 * normally here pp is a pointer to a Lisp_Object and hence an even
 * number - I exploit this so that if I find an odd number stacked I
 * view it as indicating a return into a vector...
 */
    if (((intxx)pp & 1) != 0)
    {   i = ((intxx)pp) - 1;        /* saved value of i */
        p = *sp--;
        goto in_vector;
    }
    p = *pp;
    if (is_immed_or_cons(p))
    {
#ifdef COMMON
        if (!is_cons(p) || p == nil || flip_mark_bit_p(p) == nil)
        {   pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
#else
        if (!is_cons(p))
        {   pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
#endif
/*
 * Here, and in analagous places, I have to unset the mark bit - this is
 * because I set the mark bit on a cons cell early (as I need to) then
 * call mark(&car(p)) [in effect], and the effect is that p here sees the
 * marked pointer...
 */
        if (is_marked_p(p)) p = flip_mark_bit_p(p);
        q = qcar(p);
        if (is_immed_or_cons(q) && !is_cons(q))
        {   if (is_marked_i(q))
            {   pp = (Lisp_Object *)(*sp--);
                if (pp == NULL) return;
                else goto top;
            }
            qcar(p) = flip_mark_bit_i(q);
            pp = &qcdr(p);
            goto top;
        }
        else if (is_marked_p(q))
        {   pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
        else
        {   qcar(p) = flip_mark_bit_p(q);
            q = qcdr(p);
            if (!is_immed(q) && q != nil)
            {   if (sp >= sl) non_recursive_mark(&qcdr(p));
                else *++sp = (Lisp_Object)&qcdr(p);
            }
            pp = &qcar(p);
            goto top;
        }
    }
/* here we have a vector of some sort */
    switch ((int)p & TAG_BITS)
    {
default:            /* The case-list is exhaustive! */
case TAG_CONS:      /* Already processed */
case TAG_FIXNUM:    /* Invalid here */
case TAG_ODDS:      /* Invalid here */
#ifdef COMMON
case TAG_SFLOAT:    /* Invalid here */
#endif
        /* Fatal error really called for here */
        term_printf("\nBad object in GC (%.8lx)\n", (long)p);
        ensure_screen();
        abort();
        return;

case TAG_SYMBOL:
        if (is_marked_p(p)) p = flip_mark_bit_p(p);
#ifndef COMMON
/*
 * NIL is outside the main heap, and so marking it must NOT involve
 * the regular pointer-chaining operations.
 */
        if (p == nil)
        {   pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
#endif
        h = qheader(p);
        if (!is_odds(h)) /* already visited */
        {   *pp = (Lisp_Object)h;
            qheader(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
            pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
#ifdef DAMAGED_SYMBOLS
        term_printf("Symbol at %.8x ", p);
        q = qpname(p);
        if (is_vector(q) && is_odds(vechdr(q)))
        {   term_printf("(%.*s)\n",
                        (int)(length_of_header(vechdr(q))-CELL),
                        &celt(q, 0));
        }
        else term_printf("(pname not available)\n");
        ensure_screen();
#endif
        *pp = flip_mark_bit_i(h);
        qheader(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
        if (sp >= sl)
        {   non_recursive_mark(&qvalue(p));
            non_recursive_mark(&qenv(p));
            non_recursive_mark(&qpname(p));
#ifdef COMMON
            non_recursive_mark(&qpackage(p));
#endif
        }
        else
        {   q = qvalue(p);
            if (!is_immed(q) && q != nil)
                *++sp = (Lisp_Object)&qvalue(p);
            q = qenv(p);
            if (!is_immed(q) && q != nil)
                *++sp = (Lisp_Object)&qenv(p);
            q = qpname(p);
            if (!is_immed(q) && q != nil)
                *++sp = (Lisp_Object)&qpname(p);
            q = qfastgets(p);
            if (!is_immed(q) && q != nil)
                *++sp = (Lisp_Object)&qfastgets(p);
#ifdef COMMON
            q = qpackage(p);
            if (!is_immed(q) && q != nil)
                *++sp = (Lisp_Object)&qpackage(p);
#endif
        }
        pp = &qplist(p);    /* iterate into plist not value? */
        goto top;

case TAG_NUMBERS:
        if (is_marked_p(p)) p = flip_mark_bit_p(p);
        h = numhdr(p);
        if (!is_odds(h)) /* already visited */
        {   *pp = (Lisp_Object)h;
            numhdr(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
            pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
        *pp = flip_mark_bit_i(h);
        numhdr(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
        if (is_bignum_header(h))
        {   pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
#ifdef COMMON
        q = real_part(p);
        if (!is_immed(q))
        {   if (sp >= sl) non_recursive_mark(&real_part(p));
            else *++sp = (Lisp_Object)&real_part(p);
        }
        pp = (Lisp_Object *)&imag_part(p);
        goto top;
#else
        term_printf("Bad numeric type found %.8lx\n", (long)h);
        ensure_screen();
        abort();
        return;
#endif

case TAG_BOXFLOAT:
        if (is_marked_p(p)) p = flip_mark_bit_p(p);
        h = flthdr(p);
        if (!is_odds(h)) /* already visited */
        {   *pp = (Lisp_Object)h;
            flthdr(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
            pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
        *pp = flip_mark_bit_i(h);
        flthdr(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
        pp = (Lisp_Object *)(*sp--);
        if (pp == NULL) return;
        else goto top;

case TAG_VECTOR:
        if (is_marked_p(p)) p = flip_mark_bit_p(p);
        h = vechdr(p);
        if (!is_odds(h)) /* already visited */
        {   *pp = (Lisp_Object)h;
            vechdr(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
            pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
        *pp = flip_mark_bit_i(h);
        vechdr(p) = (Header)flip_mark_bit_p((Lisp_Object)pp);
        if (vector_holds_binary(h))  /* strings & bitvecs */
        {   pp = (Lisp_Object *)(*sp--);
            if (pp == NULL) return;
            else goto top;
        }
        i = (intxx)doubleword_align_up(length_of_header(h));
        if (is_mixed_header(h)) 
            i = 4*CELL;  /* Only use first few pointers */
in_vector:
        if (sp >= sl)
        {   while (i >= 3*CELL)
            {   i -= CELL;
                non_recursive_mark((Lisp_Object *)((char *)p - TAG_VECTOR + i));
            }
        }
        else
        {   while (i >= 3*CELL)
            {   i -= CELL;
                pp = (Lisp_Object *)((char *)p - TAG_VECTOR + i);
                q = *pp;
                if (!is_immed(q) && q != nil)
                {   *++sp = p;
                    *++sp = i + 1;
#ifdef DAMAGED_SYMBOLS
                    term_printf("Marking item %d of vector %.8x\n", i, p);
                    ensure_screen();
#endif
                    goto top;
                }
            }
        }
/*
 * Because we padded up to an even number of words for the vector in total
 * there are always an odd number of pointers to trace, and in particular
 * always at least one - so it IS reasonable to iterate on the first item in
 * the vector, and there can not be any worries about zero-length vectors
 * to hurt me.  WELL actually in ADDRESS_64 mode I might have had a zero
 * length vector! I should have treated that as if it contained binary..
 */
#ifdef ADDRESS_64
/* /* #  error messed up here */
#endif
        pp = (Lisp_Object *)((char *)p - TAG_VECTOR + i - CELL);
        goto top;
    }
}

#endif /* DEMO_MODE */

Lisp_Object MS_CDECL Lgc0(Lisp_Object nil, int nargs, ...)
{
    argcheck(nargs, 0, "reclaim");
    return Lgc(nil, lisp_true);
}

Lisp_Object Lgc(Lisp_Object nil, Lisp_Object a)
{
/*
 * If GC is called with a non-nil argument the garbage collection
 * will be a full one - otherwise it will be soft and may do hardly
 * anything.
 */
#ifdef DEMO_MODE
    return onevalue(nil);
#else
    return reclaim(nil, "user request",
                   a != nil ? GC_USER_HARD : GC_USER_SOFT, 0);
#endif
}

Lisp_Object Lverbos(Lisp_Object nil, Lisp_Object a)
/*
 * (verbos 0) or (verbos nil)       silent garbage collection
 * (verbos 1) or (verbos t)         standard GC messages
 * (verbos 2)                       messages when FASL module loaded
 * (verbos 4)                       extra timing info for GC process
 * These bits can be added to get combination effects, except that
 * "4" has no effect unless "1" is present.
 */
{
    int32 code, old_code = verbos_flag;
    if (a == nil) code = 0;
    else if (is_fixnum(a)) code = int_of_fixnum(a);
    else code = 1;
/*
 * -G on the command line makes garbage collection noisy always...
 */
    if (miscflags & ALWAYS_NOISY) code |= 3;
    miscflags = (miscflags & ~GC_MSG_BITS) | (code & GC_MSG_BITS);
    return onevalue(fixnum_of_int(old_code));
}


CSLbool volatile already_in_gc, tick_on_gc_exit;
CSLbool volatile interrupt_pending, tick_pending, polltick_pending;
Lisp_Object volatile saveheaplimit;
Lisp_Object volatile savevheaplimit;
Lisp_Object volatile savecodelimit;
Lisp_Object * volatile savestacklimit;


static int stop_after_gc = 0;


static int fold_cons_heap(void)
{
/*
 * This is amazingly messy because the heap is segmented.
 */
    nil_as_base
    int top_page_number = 0,
        bottom_page_number = (int)heap_pages_count - 1;
    void *top_page = heap_pages[top_page_number],
         *bottom_page = heap_pages[bottom_page_number];
    char *top_low = (char *)quadword_align_up((intxx)top_page),
         *bottom_low = (char *)quadword_align_up((intxx)bottom_page);
    char *top_start = top_low + CSL_PAGE_SIZE,
         *bottom_start = bottom_low + CSL_PAGE_SIZE;
    char *top_fringe = top_low + car32(top_low),
         *bottom_fringe = bottom_low + car32(bottom_low);
    if (bottom_fringe != (char *)fringe)
    {   term_printf("disaster wrt heap fringe %.8lx %.8lx\n",
                 (long)bottom_fringe, (long)fringe);
        my_exit(EXIT_FAILURE);
    }
    fringe -= sizeof(Cons_Cell);
    for (;;)
    {
        Lisp_Object p;
/* scan up from fringe to find a busy cell */
        for (;;)
        {   fringe += sizeof(Cons_Cell);
            if (top_page_number == bottom_page_number &&
                top_start == (char *)fringe)
/*
 * The cast to (unsigned) on the next line is unexpectedly delicate.  The
 * difference between two pointers is of type ptrdiff_t, which is a signed
 * type.  If this is implemented as int (and that in turn is a 16 bit value)
 * then the following subtraction can overflow and give a value that appears
 * to have the wrong sign.  The implicit widening to (Lisp_Object) could
 * then propagate the sign bit in an unhelpful manner.  Going via a variable
 * of type (unsigned) ought to mend things. Ok so 16-bit ints are now a
 * thing of that past so this no longer worried me!
 */
            {   unsignedxx len = (unsignedxx)((char *)fringe - top_low);
                car32(top_low) = len;
                return bottom_page_number;
            }
            if ((char *)fringe >= bottom_start)
            {
/*
 * If the heap were to be left totally empty this would be WRONG
 */
                bottom_page = heap_pages[--bottom_page_number];
                bottom_low = (char *)quadword_align_up((intxx)bottom_page);
                bottom_start = bottom_low + CSL_PAGE_SIZE;
                fringe = (Lisp_Object)(bottom_low + car32(bottom_low));
                heaplimit = (Lisp_Object)(bottom_low + SPARE);
                fringe -= sizeof(Cons_Cell);
#ifdef TICK_STREAM
/*
 * From time to time in the GC I poll for clock ticks, just to keep
 * responsiveness of the window system up.
 */
                if (tick_on_gc_exit)
                {   Lisp_Object nil;
                    inject_randomness((int)clock());
                    inject_randomness((int)fringe);
                    accept_tick();
                    nil = C_nil;
                    if (exception_pending())
                    {   stop_after_gc = 1;
                        flip_exception();
                    }
                    tick_on_gc_exit = NO;
                }
#endif
                continue;
            }
            p = qcar(fringe);
            if (is_immed_or_cons(p) && !is_cons(p))
            {   if (is_marked_i(p))
                {   qcar(fringe) = flip_mark_bit_i(p);
                    break;
                }
            }
            else if (is_marked_p(p))
            {   qcar(fringe) = flip_mark_bit_p(p);
                break;
            }
        }
/* scan down from the top to find a free cell, unmarking is I go */
        for (;;)
        {   top_start -= sizeof(Cons_Cell);
            if (top_page_number == bottom_page_number &&
                top_start == (char *)fringe)
            {   unsignedxx len = (unsignedxx)((char *)fringe - top_low);
                car32(top_low) = len;
                return bottom_page_number;
            }
            if (top_start < top_fringe)
            {   top_page_number++;
                top_page = heap_pages[top_page_number];
                top_low = (char *)quadword_align_up((intxx)top_page);
                top_start = top_low + CSL_PAGE_SIZE;
                top_fringe = top_low + car32(top_low);
                continue;
            }
            p = qcar(top_start);
            if (is_immed_or_cons(p) && !is_cons(p))
            {   if (!is_marked_i(p)) break;
                else qcar(top_start) = flip_mark_bit_i(p);
            }
            else if (!is_marked_p(p)) break;
            else qcar(top_start) = flip_mark_bit_p(p);
        }
/* Now relocate one cell */
        qcar(top_start) = qcar(fringe);
        qcdr(top_start) = qcdr(fringe);
        {   Lisp_Object forward = flip_mark_bit_p(top_start + TAG_VECTOR);
            qcar(fringe) = forward;
            qcdr(fringe) = forward;
        }
    }
}

static void adjust_vec_heap(void)
/*
 * This scans over the vector heap working out where each vector
 * is going to get relocated to, and then changing pointers to reflect
 * where the vectors will end up.
 */
{
    Lisp_Object nil = C_nil;
    int32 page_number, new_page_number = 0;
    void *new_page = vheap_pages[0];
    char *new_low = (char *)doubleword_align_up((intxx)new_page);
    char *new_high = new_low + (CSL_PAGE_SIZE - 8);
    char *p = new_low + 8;
    for (page_number = 0; page_number < vheap_pages_count; page_number++)
    {   void *page = vheap_pages[page_number];
        char *low = (char *)doubleword_align_up((intxx)page);
        char *fr = low + car32(low);
#ifdef TICK_STREAM
        if (tick_on_gc_exit)
        {
            inject_randomness((int)clock());
            inject_randomness((int)fr);
            accept_tick();
            tick_on_gc_exit = NO;
        }
#endif
        *(Lisp_Object *)fr = nil;
        low += 8;
        for (;;)
        {   Header h;
            Lisp_Object h1;
            char *p1;
            int32 l;
            unsignedxx free;
/*
 * Here a vector will have an ordinary vector-header (which is tagged
 * as ODDS) if it was not marked.
 */
            while (is_odds(h = *(Header *)low))
            {
                if (is_symbol_header(h)) low += symhdr_length;
                else low += doubleword_align_up(length_of_header(h));
            }
/*
 * It could be that all (remaining) the vectors in this page are unmarked...
 */
            if (low >= fr) break;
/*
 * Otherwise I have found an active vector. Its header should have been
 * left with a back-pointer chain through all places that refereed to the
 * vector.
 */
            h1 = h;
            while (!is_odds(h1))
            {   Lisp_Object h2;
                h2 = *(Lisp_Object *)clear_mark_bit_p(h1);
                if (is_vector(h2))
/*
 * Forwarding pointer for relocated cons cell. This is delicate because
 * of the number of spare bits I have on a 32-bit system. The back-pointer
 * chain via the heaver word of a vector can run through other vector
 * cells (in the middle of vectors) and it can also go via either CAR or CDR
 * field of a cons cell. The funny case here is if it is via the CDR field
 * of a CONS cell and that CONS has been relocated. Then the CONS contains
 * a forwarding address that points to the start of the relocated object.
 * Sometimes I want to end up with a pointer to the CDR bit again. The
 * "+ (h1 & CELL)" is there to achieve that. I somewhat feel that I ought to
 * have been able to do something cleaner, but changing it now seems to me
 * to be too delicate.
 */
                    h1 = (Lisp_Object)((char *)h2 - TAG_VECTOR + (h1 & CELL));
                else h1 = h2;
            }

            if (is_symbol_header(h1)) l = symhdr_length;
            else l = doubleword_align_up(length_of_header(h1));
/*
 * I subtract the pointers (new_high - p) into an unsigned int because
 * on a 16-bit machine that might be vital!  The type ptrdiff_t is a signed
 * type and in bad cases the subtraction might overflow, but I know that the
 * answer here is supposed to be positive.  Hmm I think that these days
 * worry about 16 bit machines is no longer worthwhile...
 */
            free = (unsignedxx)(new_high - p);
            if (l > (intxx)free)
            {   new_page_number++;
                new_page = vheap_pages[new_page_number];
                new_low = (char *)doubleword_align_up((intxx)new_page);
                new_high = new_low + (CSL_PAGE_SIZE - 8);
                p = new_low + 8;
            }

/*
 * Because I did not have enough bits to store the critical information
 * somewhere nicer I have to reconstruct the tag bits to go with the
 * vector out of the header word associated with it.
 * Here is had BETTER be a vector!
 */
            if (is_symbol_header(h1)) p1 = p + TAG_SYMBOL;
            else if (is_numbers_header(h1)) p1 = p + TAG_NUMBERS;
            else if (is_boxfloat_header(h1)) p1 = p + TAG_BOXFLOAT;
            else p1 = p + TAG_VECTOR;

            while (!is_odds(h))
            {   h = clear_mark_bit_p(h);
                h1 = *(Lisp_Object *)h;
/*
 * The two above lines fail if amalgamated - both on Zortech C 3.0.1 and
 * on a VAX/VMS C compiler.  Hence two lines of code where once I had one.
 */
                if (is_vector(h1))
                    h = (Lisp_Object)((char *)h1 - TAG_VECTOR + (h & CELL));
                else
                {   *(Lisp_Object *)h = (Lisp_Object)p1;
                    h = h1;
                }
            }
            *(Lisp_Object *)low = set_mark_bit_h(h);
            low += l;
            p += l;
            if (low >= fr) break;
        }
    }
}

static void move_vec_heap(void)
/*
 * This moves data down in the vector heap, supposing that all pointer
 * relocation will be dealt with elsewhere.  Calculations made here must remain
 * in step with those in adjust_vecheap.
 */
{
    nil_as_base
    int32 page_number, new_page_number = 0;
    void *new_page = vheap_pages[0];
    char *new_low = (char *)doubleword_align_up((intxx)new_page);
    char *new_high = new_low + (CSL_PAGE_SIZE - 8);
    char *p = new_low + 8;
    for (page_number = 0; page_number < vheap_pages_count; page_number++)
    {   void *page = vheap_pages[page_number];
        char *low = (char *)doubleword_align_up((intxx)page);
        char *fr = low + car32(low);
#ifdef TICK_STREAM
        if (tick_on_gc_exit)
        {
            inject_randomness((int)clock());
            inject_randomness((int)fr);
            accept_tick();
            tick_on_gc_exit = NO;
        }
#endif
        *(Lisp_Object *)fr = set_mark_bit_h(TAG_ODDS + (8<<10));
        low += 8;
        for (;;)
        {   Header h;
            intxx l;
            unsignedxx free;
            while (!is_marked_h(h = *(Header *)low))
                if (is_symbol_header(h)) low += symhdr_length;
                else low += doubleword_align_up(length_of_header(h));
            if (low >= fr) break;

            if (is_symbol_header(h)) l = symhdr_length;
            else l = doubleword_align_up(length_of_header(h));
#ifdef DEBUG
            if (l >= CSL_PAGE_SIZE)
            {   term_printf("heap mangled - vector length wrong\n");
                ensure_screen();
                abort();
            }
#endif
            free = (unsignedxx)(new_high - p);
            if (l > (intxx)free)
            {   unsignedxx len = (unsignedxx)(p - new_low);
                car32(new_low) = len;
/*
 * I fill the end of the page with zero words so that the data there is
 * definite in value, and to help file-compression when I dump a heap
 * image.
 */
#ifdef CLEAR_OUT_MEMORY
                while (free != 0)
                {   *(int32 *)p = 0;
                    p += 4;
                    free -= 4;
                }
#endif
                new_page_number++;
                new_page = vheap_pages[new_page_number];
                new_low = (char *)doubleword_align_up((intxx)new_page);
                new_high = new_low + (CSL_PAGE_SIZE - 8);
                p = new_low + 8;
            }

            *(Header *)p = clear_mark_bit_h(h);
            p += CELL;
            low += CELL;
            l -= CELL;
            while (l != 0)
            {   *(int32 *)p = *(int32 *)low;
                p += 4;
                low += 4;
                l -= 4;
            }
        }
    }
    {   unsignedxx len = (unsignedxx)(p - new_low);
#ifdef CLEAR_OUT_MEMORY
        unsignedxx free = (unsignedxx)(new_high - p);
#endif
        car32(new_low) = len;
#ifdef CLEAR_OUT_MEMORY
        while (free != 0)
        {   *(int32 *)p = 0;
            p += 4;
            free -= 4;
        }
#endif
    }
    vfringe = (Lisp_Object)p;
    vheaplimit = (Lisp_Object)(new_low + (CSL_PAGE_SIZE - 8));
    new_page_number++;
    while (vheap_pages_count > new_page_number)
        pages[pages_count++] = vheap_pages[--vheap_pages_count];

}

static int compress_heap(void)
{
    int n = fold_cons_heap();
    adjust_vec_heap();
    move_vec_heap();
    return n;
}

static void relocate(Lisp_Object *cp)
/*
 * If p is a pointer to a cons cell that has been moved, fix it up.
 */
{
    Lisp_Object nil = C_nil,
                p = (*cp);   /* BEWARE "p =* cp;" anachronism here! */
    if (p == nil) return; /* nil is separate from the main heap */
    else if (is_cons(p))
    {   Lisp_Object p1;
        p1 = qcar(p);
        if (is_vector(p1) && is_marked_p(p1))
            *cp = clear_mark_bit_p(p1 - TAG_VECTOR + TAG_CONS);
    }
}

static void relocate_consheap(int bottom_page_number)
{
    int page_number;
    for (page_number = 0; page_number <= bottom_page_number; page_number++)
    {   void *page = heap_pages[page_number];
        char *low = (char *)quadword_align_up((intxx)page);
        char *start = low + CSL_PAGE_SIZE;
        char *fr = low + car32(low);
#ifdef TICK_STREAM
        if (tick_on_gc_exit)
        {
            inject_randomness((int)clock());
            inject_randomness((int)fr);
            accept_tick();
            tick_on_gc_exit = NO;
        }
#endif
        while (fr < start)
        {   relocate((Lisp_Object *)fr);
            fr += sizeof(Lisp_Object);
            cons_cells += sizeof(Lisp_Object);
        }
    }
}

static void relocate_vecheap(void)
{
    int page_number;
    intxx i;
    for (page_number = 0; page_number < vheap_pages_count; page_number++)
    {   void *page = vheap_pages[page_number];
        char *low = (char *)doubleword_align_up((intxx)page);
        char *fr = low + car32(low);
#ifdef TICK_STREAM
        if (tick_on_gc_exit)
        {
            inject_randomness((int)clock());
            inject_randomness((int)fr);
            accept_tick();
            tick_on_gc_exit = NO;
        }
#endif
        low += 8;
        while (low < fr)
        {   Header h = *(Header *)low;
            if (is_symbol_header(h))
            {   Symbol_Head *s = (Symbol_Head *)low;
                relocate(&(s->value));
                relocate(&(s->env));
/*
 * To keep track of literal vectors I suppose here that they are never shared,
 * and I then account for things that are either V or (B . V) in an environment
 * cell, where B is binary code and V is a vector.
 */
                {   Lisp_Object e = s->env;
                    if (is_cons(e) && is_bps(qcar(e))) e = qcdr(e);
                    if (is_vector(e))
                        litvecs += doubleword_align_up(
                                       length_of_header(vechdr(e)));
                }
/*              relocate(&(s->pname));  can never be a cons cell */
                relocate(&(s->plist));
                relocate(&(s->fastgets));
                {   Lisp_Object e = s->fastgets;
                    if (is_vector(e))
                        getvecs += doubleword_align_up(
                                       length_of_header(vechdr(e)));
                }
#ifdef COMMON
                relocate(&(s->package));
#endif
                low += symhdr_length;
                symbol_heads += symhdr_length;
                continue;
            }
            else switch (type_of_header(h))
            {
#ifdef COMMON
        case TYPE_RATNUM:
        case TYPE_COMPLEX_NUM:
                relocate((Lisp_Object *)(low+CELL));
                relocate((Lisp_Object *)(low+2*CELL));
                other_mem += 2*CELL;
                break;
#endif
        case TYPE_MIXED1:
        case TYPE_MIXED2:
        case TYPE_MIXED3:
        case TYPE_STREAM:
                for (i=CELL; i<4*CELL; i+=CELL)
                    relocate((Lisp_Object *)(low+i));
                other_mem += doubleword_align_up(length_of_header(h));
                break;
        case TYPE_HASH:
        case TYPE_SIMPLE_VEC:
        case TYPE_ARRAY:
        case TYPE_STRUCTURE:
                for (i=CELL; i<doubleword_align_up(length_of_header(h)); i+=CELL)
                    relocate((Lisp_Object *)(low+i));
                if (type_of_header(h) == TYPE_SIMPLE_VEC)
                    user_vectors += doubleword_align_up(length_of_header(h));
                else other_mem += doubleword_align_up(length_of_header(h));
                break;
        case TYPE_STRING:
                strings += doubleword_align_up(length_of_header(h));
                break;
        case TYPE_BIGNUM:
                big_numbers += doubleword_align_up(length_of_header(h));
                break;
#ifdef COMMON
        case TYPE_SINGLE_FLOAT:
        case TYPE_LONG_FLOAT:
#endif
        case TYPE_DOUBLE_FLOAT:
                box_floats += doubleword_align_up(length_of_header(h));
                break;

        default:
                break;
            }
            low += doubleword_align_up(length_of_header(h));
        }
    }
}

static void abandon_heap_pages(int bottom_page_number)
{
    bottom_page_number++;
    while (heap_pages_count > bottom_page_number)
        pages[pages_count++] = heap_pages[--heap_pages_count];
}

static void zero_out(void *p)
{
    char *p1 = (char *)doubleword_align_up((intxx)p);
    memset(p1, 0, CSL_PAGE_SIZE);
}

#ifndef NO_COPYING_GC

/*
 * You may like to observe how much more compact the code for the copying
 * garbage collector is when compared with the mark/slide mess.  It is
 * naturally and easily non-recursive and does not get involved in any
 * over-dubious punning on bit-patterns... It just requires a lot of spare
 * memory for the new semi-space.
 */

static int trailing_heap_pages_count,
           trailing_vheap_pages_count;

static void copy(Lisp_Object *p)
/*
 * This copies the object pointed at by p from the old to the new semi-space,
 * and returns a copy to the pointer.  If scans the copied material to copy
 * all relevent sub-structures to the new semi-space.
 */
{
    Lisp_Object nil = C_nil;
    char *fr = (char *)fringe, *vfr = (char *)vfringe;
    char *tr_fr = fr, *tr_vfr = vfr;
    void *p1;
#define CONT           0
#define DONE_CAR      -1
#define DONE_VALUE    -2
#define DONE_ENV      -3
#define DONE_PNAME    -4
#define DONE_PLIST    -5
#define DONE_FASTGETS -6
    int next = CONT;
    char *tr;
#ifdef DEBUG_GC
    term_printf("Copy [%.8lx] %.8lx\n", (long)p, (long)*p);
#endif
/*
 * The code here is a simulation of multiple procedure calls to the
 * code that copies a single object.  What might otherwise have been
 * a "return address" in the calls is handled by the variable "next" which
 * takes positive values while copying vectors, and negative ones in
 * the more common cases. I use "for (;;)" blocks a lot so that I can
 * use "break" and "continue" to leap around in the code - maybe I
 * would do better to be honest and use regular labels and "goto"
 * statements.
 */
    for (;;)
    {
/*
 * Copy one object, pointed at by p, from the old semi-space into the new
 * one.
 */
        Lisp_Object a = *p;
#ifdef DEBUG_GC
    term_printf("Next copy [%.8lx] %.8lx\n", (long)p, (long)*p);
#endif
        for (;;)
        {
            if (a == nil) break;    /* common and cheap enough to test here */
            else if (is_immed_or_cons(a))
            {   if (is_cons(a))
                {
                    Lisp_Object w;
                    w = qcar(a);
                    if (is_cons(w) && is_marked_p(w)) /* a forwarding address */
                    {   *p = flip_mark_bit_p(w);
                        break;
                    }
                    fr = fr - sizeof(Cons_Cell);
                    cons_cells += 2*CELL;
/*
 * When I am doing regular calculation I leave myself a bunch of spare
 * words (size SPARE bytes) so that I can afford to do several cons operations
 * between tests.  Here I do careful tests on every step, and so I can
 * sail much closer to the wind wrt filling up space.
 */
                    if (fr <= (char *)heaplimit - SPARE + 32)
                    {   char *hl = (char *)heaplimit;
                        void *p;
                        unsignedxx len = (unsignedxx)(fr - (hl - SPARE) +
                                                      sizeof(Cons_Cell));
                        car32(hl - SPARE) = len;
                        qcar(fr) = SPID_GCMARK;
                        if (pages_count == 0)
                        {   term_printf("pages_count = 0 in GC\n");
                            ensure_screen();
                            abort();
                            return;
                        }
                        p = pages[--pages_count];
                        zero_out(p);
                        new_heap_pages[new_heap_pages_count++] = p;
                        heaplimit = quadword_align_up((intxx)p);
                        hl = (char *)heaplimit;
                        car32(heaplimit) = CSL_PAGE_SIZE;
                        fr = hl + CSL_PAGE_SIZE - sizeof(Cons_Cell);
                        heaplimit = (Lisp_Object)(hl + SPARE);
                    }
                    qcar(fr) = w;
                    qcdr(fr) = qcdr(a);
                    *p = w = (Lisp_Object)(fr + TAG_CONS);
                    qcar(a) = flip_mark_bit_p(w);
                    break;
                }
                else if (is_bps(a))
                {   char *d = data_of_bps(a) - CELL, *rr;
                    intxx alloc_size;
                    Header h = *(Header *)d;
                    intxx len;
                    if (is_bps(h))  /* Replacement handle in header field? */
                    {   *p = h ;
                        break;
                    }
                    len = length_of_header(h);
                    alloc_size = (intxx)doubleword_align_up(len);
                    bytestreams += alloc_size;
                    for (;;)
                    {   char *cf = (char *)codefringe,
                             *cl = (char *)codelimit;
                        unsignedxx free = (unsignedxx)(cf - cl);
                        if (alloc_size > (intxx)free)
                        {
                            void *p;
                            if (codelimit != 0)
                            {   unsignedxx len = (unsignedxx)(cf - (cl - 8));
                                car32(cl - 8) = len;
                            }
                            if (pages_count == 0)
                            {   term_printf("pages_count = 0 in GC\n");
                                ensure_screen();
                                abort();
                                return;
                            }
                            p = pages[--pages_count];
                            zero_out(p);
                            new_bps_pages[new_bps_pages_count++] = p;
                            cl = (char *)doubleword_align_up((intxx)p);
                            codefringe = (Lisp_Object)(cl + CSL_PAGE_SIZE);
                            codelimit = (Lisp_Object)(cl + 8);
                            continue;
                        }
                        rr = cf - alloc_size;
                        codefringe = (Lisp_Object)rr;
/*
 * See comments in fns2.c for the curious packing here!
 */
                        *(Header *)d = *p = TAG_BPS +
                           (((intxx)((rr + CELL) - (cl - 8)) &
                             (PAGE_POWER_OF_TWO-4)) << 6) +
                           (((intxx)(new_bps_pages_count-1))<<(PAGE_BITS+6));
                        /* Wow! How obscure!! */
                        *(Header *)rr = h;
                        memcpy(rr+CELL, d+CELL, alloc_size-CELL);
                        break;
                    }
                    break;
                }
                else break;        /* Immediate data drops out here */
            }
            else                    /* Here I have a symbol or vector */
            {   Header h;
                int tag;
                intxx len;
                tag = ((int)a) & TAG_BITS;
                a = (Lisp_Object)((char *)a - tag);
                h = *(Header *)a;
#ifdef DEBUG_GC
                term_printf("Header is %.8lx\n", (long)h);
#endif
                if (!is_odds(h))
                {   *p = h;
                    break;
                }
                if (tag == TAG_SYMBOL)
                    len = symhdr_length, symbol_heads += symhdr_length;
                else
                {   len = doubleword_align_up(length_of_header(h));
                    switch (type_of_header(h))
                    {
                case TYPE_STRING:
                        strings += len; break;
                case TYPE_BIGNUM:
                        big_numbers += len; break;
#ifdef COMMON
                case TYPE_SINGLE_FLOAT:
                case TYPE_LONG_FLOAT:
#endif
                case TYPE_DOUBLE_FLOAT:
                        box_floats += len; break;
                case TYPE_SIMPLE_VEC:
                        user_vectors += len; break;
                default:
                        other_mem += len; break;
                    }
                }
                for (;;)
                {   char *vl = (char *)vheaplimit;
                    unsignedxx free = (unsignedxx)(vl - vfr);
                    if (len > (intxx)free)
                    {   unsignedxx free1 =
                            (unsignedxx)(vfr - (vl - (CSL_PAGE_SIZE - 8)));
                        car32(vl - (CSL_PAGE_SIZE - 8)) = free1;
                        qcar(vfr) = 0;          /* sentinel value */
                        if (pages_count == 0)
                        {   term_printf("pages_count = 0 in GC\n");
                            ensure_screen();
                            abort();
                            return;
                        }
                        p1 = pages[--pages_count];
                        zero_out(p1);
                        new_vheap_pages[new_vheap_pages_count++] = p1;
                        vfr = (char *)doubleword_align_up((intxx)p1) + 8;
                        vl = vfr + (CSL_PAGE_SIZE - 16);
                        vheaplimit = (Lisp_Object)vl;
                        free1 = (unsignedxx)(vfr - (vl - (CSL_PAGE_SIZE - 8)));
                        car32(vl - (CSL_PAGE_SIZE - 8)) = free1;
                        continue;
                    }
                    *(Lisp_Object *)a = *p = (Lisp_Object)(vfr + tag);
                    *(Header *)vfr = h;
                    memcpy((char *)vfr+CELL, (char *)a+CELL, len-CELL);
                    vfr += len;
                    break;
                }
                break;
            }
        }
/*
 * Now I have copied one object - the next thing to do is to scan to see
 * if any further items are in the new space, and if so I will copy
 * their offspring.
 */
        for (;;)
        {
            switch (next)
            {
        case CONT:
                if (tr_fr != fr)
                {   tr_fr = tr_fr - sizeof(Cons_Cell);
                    if (qcar(tr_fr) == SPID_GCMARK)
                    {   char *w;
                        p1 = new_heap_pages[trailing_heap_pages_count++];
                        w = (char *)quadword_align_up((intxx)p1);
                        tr_fr = w + (CSL_PAGE_SIZE - sizeof(Cons_Cell));
                    }
                    next = DONE_CAR;
                    p = &qcar(tr_fr);
                    break;              /* Takes me to the outer loop */
                }
                else if (tr_vfr != vfr)
                {   Header h;
                    h = *(Header *)tr_vfr;
                    if (h == 0)
                    {   char *w;
                        p1 = new_vheap_pages[trailing_vheap_pages_count++];
                        w = (char *)doubleword_align_up((intxx)p1);
                        tr_vfr = w + 8;
                        h = *(Header *)tr_vfr;
                    }
                    if (is_symbol_header(h))
                    {   next = DONE_VALUE;   
                        p = &(((Symbol_Head *)tr_vfr)->value);
                        break;
                    }
                    else
                    {   intxx len = doubleword_align_up(length_of_header(h));
                        tr = tr_vfr;
                        tr_vfr = tr_vfr + len;
                        switch (type_of_header(h))
                        {
#ifdef COMMON
                    case TYPE_SINGLE_FLOAT:
                    case TYPE_LONG_FLOAT:
#endif
                    case TYPE_DOUBLE_FLOAT:
                    case TYPE_BIGNUM:
                            continue;
                    case TYPE_MIXED1: case TYPE_MIXED2:
                    case TYPE_MIXED3: case TYPE_STREAM:
                            next = 2*CELL;
                            break;
/*
 * There is a slight delight here. The test "vector_holds_binary" is only
 * applicable if the header to be checked is a header of a genuine vector,
 * ie something that would have TAG_VECTOR in the pointer to it. But here
 * various numeric data types also live in the vector heap, so I need to
 * separate them out explicitly. The switch block here does slightly more than
 * it actually HAS to, since the vector_holds_binary test would happen to
 * deal with several of the numeric types "by accident", but I feel that
 * the security of listing them as separate cases is more important than the
 * minor speed-up that might come from exploiting such marginal behaviour.
 */
                    default:
                            if (vector_holds_binary(h)) continue;
#ifdef COMMON
                    case TYPE_RATNUM:
                    case TYPE_COMPLEX_NUM:
#endif
                            next = len - 2*CELL;
                            break;
                        }
                        p = (Lisp_Object *)(tr + next + CELL);
                        break;
                    }
                }
                else
                {   fringe = (Lisp_Object)fr;
                    vfringe = (Lisp_Object)vfr;
                    return;        /* Final exit when all has been copied */
                }
        case DONE_CAR:
                next = CONT;
                p = &qcdr(tr_fr);
                break;
        case DONE_VALUE:
                next = DONE_ENV;   
                p = &(((Symbol_Head *)tr_vfr)->env);
                break;
        case DONE_ENV:
                next = DONE_FASTGETS;   
                p = &(((Symbol_Head *)tr_vfr)->fastgets);
                break;
        case DONE_FASTGETS:
                next = DONE_PNAME;   
                p = &(((Symbol_Head *)tr_vfr)->pname);
                break;
        case DONE_PNAME:
#ifndef COMMON
                next = CONT;
                p = &(((Symbol_Head *)tr_vfr)->plist);
                tr_vfr = tr_vfr + symhdr_length;
                break;
#else
                next = DONE_PLIST;   
                p = &(((Symbol_Head *)tr_vfr)->plist);
                break;
        case DONE_PLIST:
                next = CONT;
                p = &(((Symbol_Head *)tr_vfr)->package);
                tr_vfr = tr_vfr + symhdr_length;
                break;
#endif
        default:
                p = (Lisp_Object *)(tr + next);
                next -= CELL;
                break;
            }
            break;
        }
    }
}

#endif  /* NO_COPYING_GC */


#ifndef DEMO_MODE

typedef struct mapstore_item
{
    double w;
    double n;
    unsigned32 n1;
    Lisp_Object p;
} mapstore_item;

int profile_count_mode;

static int MS_CDECL profile_cf(const void *a, const void *b)
{
    mapstore_item *aa = (mapstore_item *)a,
                  *bb = (mapstore_item *)b;
    
    if (profile_count_mode)
    {   if (aa->n1 == bb->n1) return 0;
	if (aa->n1 < bb->n1) return 1;
	else return -1;
    }
    if (aa->w == bb->w) return 0;
    else if (aa->w < bb->w) return 1;
    else return -1;
}

#endif

Lisp_Object Lmapstore(Lisp_Object nil, Lisp_Object a)
/*
 * Argument controls what happens:
 *    nil or 0      print statistics and reset to zero
 *           1      print, but do not reset
 *           2      return list of stats, reset to zero
 *           3      return list, do not reset
 *           4      reset to zero, do not print, return nil
 *	     8	    Toggle call count mode
 */
{
#ifdef DEMO_MODE
    return onevalue(nil);
#else
    int pass, what;
    int32 j, gcn;
    double itotal = 0.0, total = 0.0;
    Lisp_Object res = nil;
    mapstore_item *buff;
    int32 buffp, buffn;
    if (a == nil) a = fixnum_of_int(0);
    if (is_fixnum(a)) what = int_of_fixnum(a);
    else what = 0;
    if ((what & 6) == 0)
    {   buff = (mapstore_item *)(*malloc_hook)(100*sizeof(mapstore_item));
        if (buff == NULL) return onevalue(nil); /* fail */
        buffp = 0;
        buffn = 100;
    }
    if ((what & 2) != 0)
    {   Lgc0(nil, 0); /* Force GC at start to avoid one in the middle */
        nil = C_nil;
        if (exception_pending()) return nil;
        gcn = gc_number;
    }

    if ((what & 8) != 0)
	profile_count_mode = !profile_count_mode;
#ifdef PROFILED
/*
 * PROFILED is intended to be defined if we were compiled with a -p option,
 * and we take system dependent action to dump out results (e.g. on some systems
 * it may be useful to invoke monitor() or moncontrol() here.
 */

#ifdef SHOW_COUNTS_AVAILABLE
    show_counts();
    write_profile("counts");   /* Useful if -px option to compiler */
#endif

#endif /* PROFILED */
    {   char *vf = (char *)vfringe,
             *vl = (char *)vheaplimit;
        unsignedxx len = (unsignedxx)(vf - (vl - (CSL_PAGE_SIZE - 8)));
/*
 * Set up the current page so I can tell where the active data is.
 */
        car32(vl - (CSL_PAGE_SIZE - 8)) = len;
    }
    for (pass=0; pass<2; pass++)
    {   for (j=0; j<vheap_pages_count; j++)
        {   void *page = vheap_pages[j];
            char *low = (char *)doubleword_align_up((intxx)page);
            char *high = low + car32(low);
            low += 8;
            while (low<high)
            {   Header h = *(Header *)low;
                if (is_symbol_header(h))
                {   Lisp_Object e = qenv(low + TAG_SYMBOL);
                    intxx clen = 0;
                    unsignedxx n;
                    if (is_cons(e))
                    {   e = qcar(e);
                        if (is_bps(e))
                        {   Header ch = *(Header *)(data_of_bps(e) - CELL);
                            clen = length_of_header(ch);
                        }
                    }
                    n = qcount(low + TAG_SYMBOL);
                    if (n != 0 && clen != 0)
                    {   double w = (double)n/(double)clen;
/*
 * Here I want a measure that will give a good idea of how worthwhile it
 * would be to compile the given function into C - what I have chosen is
 * a count of bytecodes executed scaled by the length
 * of the bytestream code defining the function.  This will cause "good value"
 * cases to show up best.  I scale this relative to the total across all
 * functions recorded to make the numbers less sensitive to details of
 * how I generate test cases.  For interest I also display the proportion
 * of actual bytecodes interpreted.  In each case I record these out of
 * a total of 100.0 (percent) to give comfortable ranges of numbers to admire.
 */
                        if (pass == 0) itotal += (double)n, total += w;
                        else
                        {   if (w/total > 0.00001 ||
                                (double)n/itotal > 0.0001)
                            {   if ((what & 6) == 0)
                                {   if (buffp == buffn)
                                    {   buffn += 100;
                                        buff = (mapstore_item *)
                                            (*realloc_hook)((void *)buff,
                                                    sizeof(mapstore_item)*buffn);
                                        if (buff == NULL) return onevalue(nil);
                                    }
                                    buff[buffp].w = 100.0*w/total;
                                    buff[buffp].n = 100.0*(double)n/itotal;
                                    buff[buffp].n1 = n;
                                    buff[buffp].p = (Lisp_Object)(low + TAG_SYMBOL);
                                    buffp++;
                                }
                                if ((what & 2) != 0)
                                {   Lisp_Object w1;
/* Result is a list of items ((name size bytes-executed) ...).
 * You might think that I needed to push res here - but I abort if there
 * is a GC, so it is not necessary after all.
 */
                                    w1 = list3((Lisp_Object)(low + TAG_SYMBOL),
                                              fixnum_of_int(clen),
                                              fixnum_of_int(n));
                                    nil = C_nil;
                                    if (exception_pending() || gcn != gc_number)
                                        return nil;
                                    res = cons(w1, res);
                                    nil = C_nil;
                                    if (exception_pending() || gcn != gc_number)
                                        return nil;
                                }
                            }
/*
 * Reset count unless 1 bit of arg is set
 */
                            if ((what & 1) == 0)
                                qcount(low + TAG_SYMBOL) = 0;
                        }
                    }
                    low += symhdr_length;
                }
                else low += (intxx)doubleword_align_up(length_of_header(h));
            }
        }
    }
    if ((what & 6) == 0)
    {   double running = 0.0;
        qsort((void *)buff, buffp, sizeof(buff[0]), profile_cf);
        trace_printf("\n  Value  %%bytes (So far) Bytecodes  Function name\n");
        for (j=0; j<buffp; j++)
        {   running += buff[j].n;
            trace_printf("%7.2f %7.2f (%6.2f) %9lu: ",
                buff[j].w, buff[j].n, running, (long unsigned)buff[j].n1);
            prin_to_trace(buff[j].p);
            trace_printf("\n");
        }
        trace_printf("\n");
        (*free_hook)((void *)buff);
    }
    return onevalue(res);
#endif /* DEMO_MODE */
}

Lisp_Object MS_CDECL Lmapstore0(Lisp_Object nil, int nargs, ...)
{
    argcheck(nargs, 0, "mapstore");
    return Lmapstore(nil, nil);
}

static CSLbool reset_limit_registers(intxx vheap_need,
                                     intxx bps_need,
                                     intxx native_need)
/*
 * returns YES if after resetting the limit registers there was
 * enough space left for me to proceed. Return NO on failure, ie
 * need for a more genuine GC.
 */
{
    void *p;
    nil_as_base
    unsignedxx len;
    CSLbool full;
#ifndef NO_COPYING_GC
    if (gc_method_is_copying)
        full = (pages_count <=
                 heap_pages_count + vheap_pages_count +
                 bps_pages_count + native_pages_count);
    else
#endif
        full = (pages_count == 0);
    if (fringe <= heaplimit)
    {   if (full) return NO;
        p = pages[--pages_count];
        zero_out(p);
        heap_pages[heap_pages_count++] = p;
        heaplimit = quadword_align_up((intxx)p);
        car32(heaplimit) = CSL_PAGE_SIZE;
        fringe = (Lisp_Object)((char *)heaplimit + CSL_PAGE_SIZE);
        heaplimit = (Lisp_Object)((char *)heaplimit + SPARE);
    }
    {   char *vh = (char *)vheaplimit,
             *vf = (char *)vfringe;
        len = (unsignedxx)(vh - vf);
    }
    if (vheap_need > (intxx)len)
    {   char *vf, *vh;
        if (full) return NO;
        p = pages[--pages_count];
        zero_out(p);
        vheap_pages[vheap_pages_count++] = p;
        vf = (char *)doubleword_align_up((intxx)p) + 8;
        vfringe = (Lisp_Object)vf;
        vh = vf + (CSL_PAGE_SIZE - 16);
        vheaplimit = (Lisp_Object)vh;
        len = (unsignedxx)(vf - (vh - (CSL_PAGE_SIZE - 8)));
        car32(vh - (CSL_PAGE_SIZE - 8)) = len;
    }
    {   char *cl = (char *)codelimit,
             *cf = (char *)codefringe;
        len = (unsignedxx)(cf - cl);
    }
    if (bps_need != 0 && bps_need >= (intxx)len)
    {   char *cl;
        if (full || bps_pages_count >= MAX_BPS_PAGES - 1) return NO;
        p = pages[--pages_count];
        zero_out(p);
        bps_pages[bps_pages_count++] = p;
        cl = (char *)doubleword_align_up((intxx)p);
        codefringe = (Lisp_Object)(cl + CSL_PAGE_SIZE);
        codelimit = (Lisp_Object)(cl + 8);
    }
    if (native_need != 0)
    {   if (full || native_pages_count >= MAX_NATIVE_PAGES - 1) return NO;
        p = pages[--pages_count];
        zero_out(p);
        native_pages[native_pages_count++] = p;
        native_fringe = 8;
    }
    return (stack < stacklimit);
}

static void tidy_fringes(void)
/*
 * heaplimit was SPARE bytes above the actual base of the page,
 * so the next line dumps fringe somewhere where it can be found
 * later on when needed while scanning a page of heap.  Similarly
 * vfringe is stashed away at the end of its page.
 */
{   nil_as_base
    char *fr = (char *)fringe,
         *vf = (char *)vfringe,
         *cf = (char *)codefringe,
         *hl = (char *)heaplimit,
         *vl = (char *)vheaplimit,
         *cl = (char *)codelimit;
    unsignedxx len = (unsignedxx)(fr - (hl - SPARE));
    car32(hl - SPARE) = len;
    len = (unsignedxx)(vf - (vl - (CSL_PAGE_SIZE - 8)));
    car32(vl - (CSL_PAGE_SIZE - 8)) = (Lisp_Object)len;
    if (codelimit != 0)
    {   len = (unsignedxx)(cf - (cl - 8));
        car32(cl - 8) = len;
    }
}

static void lose_dead_hashtables(void)
/*
 * This splices out from the list of hash tables all entries that point to
 * tables that have not been marked or copied this garbage collection.
 */
{
    Lisp_Object *p = &eq_hash_tables, q, r;
    while ((q = *p) != C_nil)
    {   Header h;
        r = qcar(q);
        h = vechdr(r);
        if (is_odds(h) && !is_marked_h(h)) *p = qcdr(q);
        else p = &qcdr(q);
    }
    p = &equal_hash_tables;
    while ((q = *p) != C_nil)
    {   Header h;
        r = qcar(q);
        h = vechdr(r);
        if (is_odds(h) && !is_marked_h(h)) *p = qcdr(q);
        else p = &qcdr(q);
    }
}

#ifdef DEMO_MODE

extern CSLbool terminal_pushed;

void give_up()
{
    Lisp_Object nil;
#define m(s) err_printf(s)
m("\n+++ DEMONSTRATION VERSION OF REDUCE - RESOURCE LIMIT EXCEEDED +++\n");
m("This version of REDUCE has been provided for testing and\n");
m("demonstration purposes.  It has a built-in cut-out that will\n");
m("terminate processing after a time that should be sufficient for\n");
m("various small tests to run, but which will probably stop it\n");
m("from being useful as a serious tool.  You are permitted to copy\n");
m("the demonstration version and pass it on to friends subject to\n");
m("not changing it, and in particular neither changing the various\n");
m("messages it prints nor attempting to circumvent the time-out\n");
m("mechanism.  Full versions of REDUCE are available to run on a\n");
m("wide range of types of computer, and a machine-readable file\n");
m("listing suppliers was provided with the documentation that goes\n");
m("with this version.  Some suppliers are:\n");
m("  Codemist Ltd, Alta, Horsecombe Vale, Combe Down, Bath BA2 5QR,\n");
m("    England.  Phone and fax +44-225-837430,\n");
m("    http://www.codemist.co.uk\n");
m("  Winfried Neun, Konrad-Zuse-Zentrum fuer Informationstechnik Berlin\n");
m("    Heilbronner Str. 10, D 10711 Berlin-Wilmersdorf, GERMANY\n");
m("    Phone: +44-30-89604-195  Fax +49-30-89604-125.\n");
m("  (Codemist provided this version, the ZIB differs slightly)\n");
m("<Close window/type RETURN to exit>\n");
#undef m
    nil = C_nil;
    prompt_thing = CHAR_EOF;    /* Disables the prompt */
    ensure_screen();
    terminal_pushed = NOT_CHAR;
    tty_count = 0;
    char_from_terminal(0);
    my_exit(EXIT_FAILURE);
}

#endif

Lisp_Object reclaim(Lisp_Object p, char *why, int stg_class, intxx size)
{
    intxx i;
    clock_t t0, t1, t2, t3;
    int bottom_page_number;
    Lisp_Object *sp, nil = C_nil;
    intxx vheap_need = 0, bps_need = 0, native_need = 0;
#if defined(FLEX) && defined(UNIX)
    extern int timnag_();
    timnag_();
#endif
    stop_after_gc = 0;
    if (stg_class == GC_VEC) vheap_need = size;
    else if (stg_class == GC_BPS) bps_need = size;
    else if (stg_class == GC_NATIVE) native_need = size;
    already_in_gc = YES;
#ifdef SOCKETS
    if (socket_server != 0)
    {   time_t tt0 = time(NULL);
        t0 = clock();
        tt0 = time(NULL);
        if (t0 > cpu_timeout ||
            tt0 > elapsed_timeout)
        {   cpu_timeout = t0 + 20;
            elapsed_timeout = tt0 + 20;
            term_printf("\nSorry: timeout on this session. Closing down\n");
            return Lstop(nil, fixnum_of_int(1));
        }
    }
#endif

    push_clock(); t0 = base_time;

#ifdef TICK_STREAM
    if (tick_pending != 0)
    {   tick_pending = 0;
        heaplimit = saveheaplimit;
        vheaplimit = savevheaplimit;
        codelimit = savecodelimit;
        stacklimit = savestacklimit;
        tidy_fringes();
        if (stg_class != GC_PRESERVE &&
             stg_class != GC_USER_HARD &&
             reset_limit_registers(vheap_need, bps_need, native_need))
        {   already_in_gc = NO;
            tick_on_gc_exit = NO;
#ifdef POLL_FOR_ATTN
            poll_for_attn();
            nil = C_nil;
            if (exception_pending())
            {   pop_clock();
                return nil;
            }
#endif
            if (interrupt_pending)
            {   interrupt_pending = NO;
                pop_clock();
                return interrupted(p);
            }
            else
            {
                inject_randomness((int)clock());
                accept_tick();
                pop_clock();
                return onevalue(p);
            }
        }
        else tick_on_gc_exit = YES;
    }
    else
#else
	if (interrupt_pending)
	{   if (tick_pending)
            {   tick_pending = 0;
                heaplimit = saveheaplimit;
	        vheaplimit = savevheaplimit;
	        codelimit = savecodelimit;
	        stacklimit = savestacklimit;
            }
	    tidy_fringes();
	    interrupt_pending = NO;
	    pop_clock();
	    return interrupted(p);
	}
#endif
    {   tidy_fringes();
        if (stg_class != GC_PRESERVE &&
            stg_class != GC_USER_HARD &&
            reset_limit_registers(vheap_need, bps_need, native_need))
        {   already_in_gc = NO;
            pop_clock();
            return onevalue(p); /* Soft GC */
        }
    }

    if (stack >= stacklimit)
    {   stacklimit = &stacklimit[50];  /* Allow a bit of slack */
        pop_clock();
        return error(0, err_stack_overflow);
    }

#ifdef MEMORY_TRACE
#ifndef CHECK_ONLY
    identify_page_types();
    memory_comment(4);
#endif
#endif

#ifdef DEMO_MODE
    give_up();
    pop_clock();
    return nil;
#else
    push(p);

    gc_number++;

#ifdef WINDOW_SYSTEM
/*
 * If I have a window system I tell it the current time every so often
 * just to keep things cheery...
 */
    {   long int t = (long int)(100.0 * consolidated_time[0]);
        long int gct = (long int)(100.0 * gc_time);
/* /*
 * I guess that I want garbage collection messages, if any, to
 * be sent to stderr rather than whatever output stream happens to
 * be selected at the time of the garbage collection?
 * At present messages go to the normal output stream, which only makes
 * sense if GC messages are almost always disabled - maybe that will
 * be the case!
 */
        report_time(t, gct);
        if (verbos_flag & 1)
        {   freshline_trace();
            trace_printf(
        "+++ Garbage collection %ld (%s) after %ld.%.2ld+%ld.%.2ld seconds\n",
                 (long)gc_number, why, t/100, t%100, gct/100, gct%100);
        }
    }
#else
    if (verbos_flag & 1)
    {   long int t = (long int)(100.0 * consolidated_time[0]);
        long int gct = (long int)(100.0 * gc_time);
/* /* I guess that I want garbage collection messages, if any, to
 * be sent to stderr rather than whatever output stream happens to
 * be selected at the time of the garbage collection?
 * At present messages go to the normal output stream, which only makes
 * sense if GC messages are almost always disabled - maybe that will
 * be the case!
 */
        freshline_trace();
        trace_printf(
         "+++ Garbage collection %ld (%s) after %ld.%.2ld+%ld.%.2ld seconds\n",
         (long)gc_number, why, t/100, t%100, gct/100, gct%100);
    }
#endif
/*
 * If things crash really badly maybe I would rather have my output up
 * to date.
 */
    ensure_screen();
    nil = C_nil;
    if (exception_pending())
    {   stop_after_gc = 1;
        flip_exception();
    }
    if (spool_file != NULL) fflush(spool_file);

    copy_into_nilseg(NO);

    cons_cells = symbol_heads = strings = user_vectors =
             big_numbers = box_floats = bytestreams = other_mem =
             litvecs = getvecs = 0;

#ifndef NO_COPYING_GC
    if (gc_method_is_copying)
    {
        t2 = t1 = t0;   /* Time is not split down in this case */
/*
 * Set up the new half-space initially empty.
 */
        new_heap_pages_count = 0;
        new_vheap_pages_count = 0;
        new_bps_pages_count = 0;
        trailing_heap_pages_count = 1;
        trailing_vheap_pages_count = 1;
        {   void *pp = pages[--pages_count];
            char *vf, *vl;
            unsignedxx len;
/*
 * A first page of (cons-)heap
 */
            zero_out(pp);
            new_heap_pages[new_heap_pages_count++] = pp;
            heaplimit = quadword_align_up((intxx)pp);
            car32(heaplimit) = CSL_PAGE_SIZE;
            vl = (char *)heaplimit;
            fringe = (Lisp_Object)(vl + CSL_PAGE_SIZE);
            heaplimit = (Lisp_Object)(vl + SPARE);
#ifdef DEBUG_GC
            term_printf("fr = %.8lx, hl = %.8lx\n", (long)fringe, (long)heaplimit);
#endif
/*
 * A first page of vector heap.
 */
            pp = pages[--pages_count];
            zero_out(pp);
            new_vheap_pages[new_vheap_pages_count++] = pp;
            vf = (char *)doubleword_align_up((intxx)pp) + 8;
            vfringe = (Lisp_Object)vf;
            vl = vf + (CSL_PAGE_SIZE - 16);
            vheaplimit = (Lisp_Object)vl;
            len = (unsignedxx)(vf - (vl - (CSL_PAGE_SIZE - 8)));
            car32(vl - (CSL_PAGE_SIZE - 8)) = (Lisp_Object)len;
/*
 * The BPS heap can start of utterly non-existent.
 */
            codefringe = codelimit = 0;
        }

/*
 * The very first thing that I will copy will be the main object-vector,
 * this is done early to ensure that it gets a full empty page of vector
 * heap to fit into.
 */
        copy(&BASE[current_package_offset]);
/*
 * The above line is "really"
 *          copy(&current_package);
 * but I use an offset into the nilseg in explicit for because otherwise
 * there is a big foul-up with the NILSEG_EXTERNS option...  Sorry!
 */

/*
 * I should remind you, gentle reader, that the value cell
 * and env cells of nil will always contain nil, which does not move,
 * and so I do not need to copy them here.
 */
        copy(&(qplist(nil)));
        copy(&(qpname(nil)));
        copy(&(qfastgets(nil)));
#ifdef COMMON
        copy(&(qpackage(nil)));
#endif

/*
 * I dislike the special treatment of current_package that follows. Maybe
 * I should arrange something totally different for copying the package
 * structure...
 */
        for (i = first_nil_offset; i<last_nil_offset; i++)
            if (i != current_package_offset)
            /* current-package - already copied by hand */
                copy(&BASE[i]);

        for (sp=stack; sp>(Lisp_Object *)stackbase; sp--) copy(sp);
/*
 * Now I need to perform some magic on the list of hash tables...
 */
        lose_dead_hashtables();
        copy(&eq_hash_tables);
        copy(&equal_hash_tables);

        tidy_fringes();

/*
 * Throw away the old semi-space - it is now junk.
 */
        while (heap_pages_count!=0)
            pages[pages_count++] = heap_pages[--heap_pages_count];
        while (vheap_pages_count!=0)
            pages[pages_count++] = vheap_pages[--vheap_pages_count];
        while (bps_pages_count!=0)
            pages[pages_count++] = bps_pages[--bps_pages_count];

/*
 * Flip the descriptors for the old and new semi-spaces.
 */
        {   void **w = heap_pages;
            heap_pages = new_heap_pages;
            new_heap_pages = w;
            w = vheap_pages;
            vheap_pages = new_vheap_pages;
            new_vheap_pages = w;
            w = bps_pages;
            bps_pages = new_bps_pages;
            new_bps_pages = w;
            heap_pages_count = new_heap_pages_count;
            new_heap_pages_count = 0;
            vheap_pages_count = new_vheap_pages_count;
            new_vheap_pages_count = 0;
            bps_pages_count = new_bps_pages_count;
            new_bps_pages_count = 0;
        }
    }
    else
#endif /* NO_COPYING_GC */
    {
/*
 * The list bases to mark from are
 * (a) nil    [NB: mark(nil) would be ineffective],
 * (b) the special ones addressed relative to nil,
 * (c) everything on the Lisp stack,
 * (d) the package structure,
 * (e) the argument (p) passed to reclaim().
 */
        qheader(nil) = set_mark_bit_h(qheader(nil));
                                    /* nil has nil as value & env   ...  */
        mark(&qplist(nil));         /* ... thus only its plist and ...   */
        mark(&qpname(nil));         /* ... pname cell need marking,      */
                                    /* ... since packages are done later */
        mark(&qfastgets(nil));      /* + the fastgets vector, if any     */

        for (i = first_nil_offset; i<last_nil_offset; i++)
        {
            mark(&BASE[i]);
#ifdef TICK_STREAM
            if (tick_on_gc_exit)
            {
                inject_randomness((int)clock());
                accept_tick();
                tick_on_gc_exit = NO;
            }
#endif
        }
        for (sp=stack; sp>(Lisp_Object *)stackbase; sp--)
        {
            mark(sp);
#ifdef TICK_STREAM
            if (tick_on_gc_exit)
            {
                inject_randomness((int)clock());
                accept_tick();
                tick_on_gc_exit = NO;
            }
#endif
        }
/*
 * Now I need to perform some magic on the list of hash tables...
 */
        lose_dead_hashtables();
        mark(&eq_hash_tables);
        mark(&equal_hash_tables);
/*
 * What about the package structure... ? I assume it has been marked by
 * what I have just done.
 */
        qheader(nil) = clear_mark_bit_h(qheader(nil));
        t1 = read_clock();

        bottom_page_number = compress_heap();    /* Folds cons cells upwards */

        t2 = read_clock();

/*
 * Again I should remind you, gentle reader, that the value cell
 * and env cells of nil will always contain nil, which does not move,
 * and so I do not need to relocate them here.
 */
        relocate(&(qplist(nil)));
/*      relocate(&(qpname(nil)));   never a cons cell */
        relocate(&(qfastgets(nil)));
#ifdef COMMON
        relocate(&(qpackage(nil)));
#endif

        for (i = first_nil_offset; i<last_nil_offset; i++)
            relocate(&BASE[i]);

        for (sp=stack; sp>(Lisp_Object *)stackbase; sp--) relocate(sp);

        relocate_consheap(bottom_page_number);
        relocate(&eq_hash_tables);
        relocate(&equal_hash_tables);
        relocate_vecheap();

        {   char *fr = (char *)fringe,
                 *vf = (char *)vfringe,
                 *cf = (char *)codefringe,
                 *hl = (char *)heaplimit,
                 *vl = (char *)vheaplimit,
                 *cl = (char *)codelimit;
            unsignedxx len = (unsignedxx)(fr - (hl - SPARE));
            car32(hl - SPARE) = len;
            len = (unsignedxx)(vf - (vl - (CSL_PAGE_SIZE - 8)));
            car32(vl - (CSL_PAGE_SIZE - 8)) = len;
            if (codelimit != 0)
            {   len = (unsignedxx)(cf - (cl - 8));
                car32(cl - 8) = len;
            }
        }

        abandon_heap_pages(bottom_page_number);
    }

    {   Lisp_Object qq;
/*
 * Note that EQUAL hash tables do not need to be rehashed here, though
 * they do if a heap image is exported from one system to another.
 */
        for (qq = eq_hash_tables; qq!=nil; qq=qcdr(qq))
            rehash_this_table(qcar(qq));
    }

    gc_time += pop_clock();
    t3 = base_time;

    copy_out_of_nilseg(NO);

    if ((verbos_flag & 5) == 5)
/*
 * (verbos 4) gets the system to tell me how long each phase of GC took,
 * but (verbos 1) must be ORd in too.
 */
    {
#ifndef NO_COPYING_GC
    if (gc_method_is_copying)
      trace_printf("Copy %ld ms\n",
        (long int)(1000.0 * (double)(t3-t0)/(double)CLOCKS_PER_SEC));
    else
#endif
      trace_printf("Mark %ld, compact %ld, relocate %ld ms\n",
        (long int)(1000.0 * (double)(t1-t0)/(double)CLOCKS_PER_SEC),
        (long int)(1000.0 * (double)(t2-t1)/(double)CLOCKS_PER_SEC),
        (long int)(1000.0 * (double)(t3-t2)/(double)CLOCKS_PER_SEC));
    }

/* (verbos 5) causes a display breaking down how space is used */
    if ((verbos_flag & 5) == 5)
    {   trace_printf(
            "cons_cells=%d, symbol_heads=%d, strings=%d, user_vectors=%d\n",
             cons_cells, symbol_heads, strings, user_vectors-litvecs-getvecs);
        trace_printf(
            "bignums=%d, floats=%d, bytestreams=%d, other=%d, litvecs=%d\n",
            big_numbers, box_floats, bytestreams, other_mem, litvecs);
        trace_printf("getvecs=%d\n", getvecs);
    }

    pop(p);

    if (!reset_limit_registers(vheap_need, bps_need, native_need))
    {   term_printf("\n+++ No space left at all\n");
        my_exit(EXIT_FAILURE);    /* totally drastic... */
    }
#ifndef HOLD_BACK_MEMORY
#ifndef MEMORY_TRACE
/*
 * Here I grab more memory (if I am allowed to).
 * An initial version here, and one still suitable on machines that will
 * have plenty of real memory, will be to defined ok_to_grab_memory(n) as
 * 3*n + 2. This expands until the proportion of the heap active at the
 * end of garbage collection is less than 1/4.
 * If the attempt to grab more memory fails I clear the bit in init_flags
 * that allows me to try to expand, so I will not waste time again.  If
 * HOLD_BACK_MEMORY was asserted (for machines where grabbing all seemingly
 * available memory may cause a crash) I do not try this operation.  The
 * aim of keeping the heap less than half full is an heuristic and could be
 * adjusted on the basis of experience with this code.
 * The "+2" at the end of calculating the ideal heap size is intended
 * to keep us (mostly) in the copying GC domain.  If it is omitted the
 * heap tends to stay just 25% full and sliding GC is used. Overall this is
 * roughly as expensive as copying, but it is more disruptive since it comes
 * in larger gulps.
 * On systems where it is possible to measure the amount of available
 * real memory more sophisticated calculations may be possible.
 */
    if (init_flags & INIT_EXPANDABLE)
    {   int32 ideal = ok_to_grab_memory(heap_pages_count +
                                        vheap_pages_count +
                                        bps_pages_count);
        int32 more;
        if (ideal > MAX_PAGES) ideal = MAX_PAGES;
        more = ideal - pages_count;
        while (more-- > 0)
        {   void *page = (void *)my_malloc((size_t)(CSL_PAGE_SIZE + 8));
            intxx pun, pun1;
/*
 * When I first grabbed memory in restart.c I used my_malloc_1(), which
 * gobbles a large stack frame and then called regular malloc - the idea
 * there was to avoid malloc grabbing space needed for the stack. I can
 * not properly do that here since reclaim() may be called with a deep
 * stack already active.  There is thus a danger that expanding the heap here
 * may cause me to run out of stack elsewhere.  Oh well, I guess I can not
 * win in all ways.
 */
/*
 * Verify that new block does not span zero & has correct sign bit
 */
            pun = (intxx)page;
            pun1 = (intxx)((char *)page + CSL_PAGE_SIZE + 8);
            if ((pun ^ pun1) < 0) page = NULL;
#ifdef ADDRESS_SIGN_UNKNOWN
            if ((pun + address_sign) < 0) page = NULL;
#else
#ifdef ADDRESSES_HAVE_TOP_BIT_SET
            if (pun > 0) page = NULL;
#else
            if (pun < 0) page = NULL;
#endif
#endif
            if (page == NULL)
            {   init_flags &= ~INIT_EXPANDABLE;
                break;
            }
            else pages[pages_count++] = page;
        }
    }
#endif /* MEMORY_TRACE */
#endif /* HOLD_BACK_MEMORY */
#ifdef WINDOW_SYSTEM
    {   int32 n = heap_pages_count + vheap_pages_count + bps_pages_count;
        int32 n1 = n + pages_count;
        double z = (100.0*n)/n1;
        report_space(gc_number, z);
        if (verbos_flag & 1) trace_printf(
           "At gc end about %.1f Mbytes of %.1f (%.1f%%) of heap is in use\n",
           ((double)n)*(CSL_PAGE_SIZE/(1024.0*1024.0)),
           ((double)n1)*(CSL_PAGE_SIZE/(1024.0*1024.0)), z);
    }
#else
    if (verbos_flag & 1)
    {   int32 n = heap_pages_count + vheap_pages_count + bps_pages_count;
        int32 n1 = n + pages_count;
        trace_printf(
            "At gc end about %.1f Mbytes of %.1f (%.1f%%) of heap is in use\n",
            (double)n*(CSL_PAGE_SIZE/(1024.0*1024.0)),
            (double)n1*(CSL_PAGE_SIZE/(1024.0*1024.0)),
            (100.0*n)/n1);
    }
#endif
#ifndef NO_COPYING_GC
/*
 * I will make the next garbage collection a copying one if the heap is
 * at most 25% full, or a sliding one if it is more full than that.
 */
    gc_method_is_copying = (pages_count >
                 3*(heap_pages_count + vheap_pages_count + bps_pages_count));
#endif
    if (stop_after_gc)
    {
#ifdef MEMORY_TRACE
#ifndef CHECK_ONLY
        memory_comment(15);
#endif
#endif
        return Lstop(nil, fixnum_of_int(0));
    }
#ifdef POLL_FOR_ATTN
    poll_for_attn();   /* Should cause ctrl-C checking */
    nil = C_nil;
    if (exception_pending())
    {
#ifdef MEMORY_TRACE
#ifndef CHECK_ONLY
        memory_comment(15);
#endif
#endif
        return nil;
    }
#endif
#ifdef MEMORY_TRACE
#ifndef CHECK_ONLY
    memory_comment(15);
#endif
#endif
    if (interrupt_pending)
    {   interrupt_pending = NO;
        already_in_gc = NO;
        tick_on_gc_exit = NO;
        return interrupted(p);
    }
    already_in_gc = NO;
    if (tick_on_gc_exit)
    {
        inject_randomness((int)clock());
        accept_tick();
        nil = C_nil;
        if (exception_pending()) return nil;
        tick_on_gc_exit = NO;
        return onevalue(p);
    }

    return onevalue(p);
#endif /* DEMO_MODE */
}

/* end of file gc.c */
