1201 lines
30 KiB
C++
1201 lines
30 KiB
C++
//----------------------------------------------------------------------
|
|
// vx-scheme : Scheme interpreter.
|
|
// Copyright (c) 2002,2003,2006 Colin Smith.
|
|
//
|
|
// You may distribute under the terms of the Artistic License,
|
|
// as specified in the LICENSE file.
|
|
//
|
|
// cell.cpp : cell creation, storage management, garbage collection.
|
|
|
|
#include "vx-scheme.h"
|
|
|
|
static const char * nomem_error = "out of memory";
|
|
|
|
Cell * Context::make ()
|
|
{
|
|
Cell * c = alloc (Cell::Cons);
|
|
c->ca.p = c->cd.p = &Cell::Nil;
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_int (intptr_t i)
|
|
{
|
|
// SHORT INTEGER support: if the integer fits in 24 bits,
|
|
// then return a phony pointer with the short flag set and
|
|
// the integer in the upper 24. This avoids storage allocation
|
|
// and the attendant eventual garbage.
|
|
#if 1
|
|
if ((i << 8) >> 8 == i) {
|
|
return reinterpret_cast <Cell*> ((i << 8) | Cell::SHORT | Cell::ATOM);
|
|
}
|
|
#endif
|
|
Cell * c = alloc (Cell::Int);
|
|
c->cd.i = i;
|
|
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_char (char ch)
|
|
{
|
|
Cell * c = alloc (Cell::Char);
|
|
c->cd.c = ch;
|
|
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_real (double d)
|
|
{
|
|
Cell * c = alloc (Cell::Real);
|
|
double *pd = (double*) malloc(sizeof(double));
|
|
*pd = d;
|
|
c->cd.d = pd;
|
|
return c;
|
|
}
|
|
|
|
// Context::make_string
|
|
// Makes a string of the indicated length -- it is UNINITIALIZED
|
|
|
|
Cell * Context::make_string (size_t len)
|
|
{
|
|
Cell * c = alloc (Cell::String);
|
|
size_t boxsize = sizeof(Cell::StringBox) + len + 1;
|
|
Cell::StringBox* pbox = (Cell::StringBox*) xmalloc (boxsize);
|
|
pbox->length = len;
|
|
c->cd.s = pbox;
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_string (int len, char ch)
|
|
{
|
|
Cell * c = make_string (len);
|
|
memset (c->cd.s->s, ch, len);
|
|
c->cd.s->s[len] = '\0';
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_string (const char * s)
|
|
{
|
|
return make_string (s, strlen (s));
|
|
}
|
|
|
|
Cell * Context::make_string (const char * s, size_t len)
|
|
{
|
|
Cell * c = make_string (len);
|
|
strncpy(c->cd.s->s, s, len);
|
|
c->cd.s->s[len] = '\0';
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_subr (subr_f s, const char * name)
|
|
{
|
|
Cell * c = alloc (Cell::Subr);
|
|
Cell::SubrBox * psubr = new Cell::SubrBox ();
|
|
psubr->subr = s;
|
|
psubr->name = name;
|
|
c->cd.f = psubr;
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_builtin (psymbol y)
|
|
{
|
|
Cell * c = alloc (Cell::Builtin);
|
|
c->cd.y = y;
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_symbol (psymbol y)
|
|
{
|
|
Cell * c = alloc (Cell::Symbol);
|
|
c->cd.y = y;
|
|
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_boolean (bool b)
|
|
{
|
|
return b ? &Cell::Bool_T : &Cell::Bool_F;
|
|
}
|
|
|
|
Cell * Context::make_vector (int n, Cell * init /* = &Unspecified */)
|
|
{
|
|
Cell * c = alloc (Cell::Vec);
|
|
c->cd.cv = cellvector::alloc(n);
|
|
c->flag (Cell::VREF, true);
|
|
|
|
for (int ix = 0; ix < n; ++ix)
|
|
c->cd.cv->set (ix, init);
|
|
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_iport (const char * fname)
|
|
{
|
|
FILE * ip = fopen (fname, "r");
|
|
if (ip)
|
|
return make_iport (ip);
|
|
|
|
error ("unable to open stream for reading");
|
|
return nil;
|
|
}
|
|
|
|
Cell * Context::make_iport (FILE * ip)
|
|
{
|
|
Cell * c = alloc (Cell::Iport);
|
|
c->cd.ip = ip;
|
|
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_oport (const char * fname)
|
|
{
|
|
FILE * ofs = fopen (fname, "w");
|
|
if (ofs)
|
|
return make_oport (ofs);
|
|
|
|
|
|
error ("unable to open stream for writing");
|
|
return nil;
|
|
}
|
|
|
|
Cell * Context::make_oport (FILE * op)
|
|
{
|
|
Cell * c = alloc (Cell::Oport);
|
|
c->cd.op = op;
|
|
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make (Cell * ca, Cell * cd /* = &Nil*/)
|
|
{
|
|
Cell * c = alloc (Cell::Cons);
|
|
c->ca.p = ca;
|
|
c->cd.p = cd;
|
|
return c;
|
|
}
|
|
|
|
Cell * Context::make_magic (void * key, magic_set_f set_f, magic_get_f get_f)
|
|
{
|
|
Cell * c = alloc (Cell::Magic);
|
|
Cell::MagicBox* mbox = (Cell::MagicBox*) xmalloc(sizeof(Cell::MagicBox));
|
|
mbox->key = key;
|
|
mbox->set_f = set_f;
|
|
mbox->get_f = get_f;
|
|
return c;
|
|
}
|
|
|
|
Cell * Cell::notcons ()
|
|
{
|
|
error ("expecting a Cons");
|
|
return nil;
|
|
}
|
|
|
|
bool Cell::ispair() {
|
|
return type () == Cell::Cons
|
|
&& this != unspecified
|
|
&& this != nil;
|
|
}
|
|
|
|
void Cell::sanity_check ()
|
|
{
|
|
int bad = 0;
|
|
|
|
// Make sure that there are enough typebits to contain
|
|
// all the types we know about.
|
|
|
|
if ((1 << TYPEBITS) < NUM_ATOMS)
|
|
++bad, printf ("Not enough typebits!\n");
|
|
|
|
// Make sure that the size of a cell has not become greater
|
|
// than two machine pointers (car & cdr).
|
|
|
|
if (sizeof (Cell) > 2 * sizeof (void *))
|
|
printf ("Cell (%Zu) is larger than CAR+CDR!\n", sizeof (Cell));
|
|
|
|
// Make sure that the "zero zone" (the least significant
|
|
// bits of a pointer to a cell) is wide enough to accomodate
|
|
// the type and GC information stored there, assuming that
|
|
// a Cell is aligned to its own size in memory
|
|
|
|
if (sizeof (Cell) < (1 << TAGBITS))
|
|
++bad, printf ("Too many tag bits for cell size\n");
|
|
|
|
if (bad)
|
|
exit (bad);
|
|
};
|
|
|
|
bool Cell::eq (Cell * that)
|
|
{
|
|
if (this == that) // the easy case
|
|
return true;
|
|
if (short_atom (this) || short_atom (that))
|
|
return false; // then the above case would have detected equality
|
|
if (long_atom(ca.p) && long_atom(that->ca.p))
|
|
{
|
|
bool part1 = (ca.i & IGN_MASK) == (that->ca.i & IGN_MASK)
|
|
&& cd.i == that->cd.i;
|
|
|
|
return part1;
|
|
}
|
|
// If both are conses, they are eq iff they are the same cons.
|
|
// But that would have been detected by the first test.
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Cell::equal (Cell * c)
|
|
{
|
|
Type t0 = type ();
|
|
Type t1 = c->type ();
|
|
|
|
if (this == &Nil && c == &Nil)
|
|
return true;
|
|
else if (t0 == Cons && t1 == Cons)
|
|
return ca.p->equal (c->ca.p) && cd.p->equal (c->cd.p);
|
|
else if (t0 == Vec && t1 == Vec)
|
|
{
|
|
cellvector * cv = VectorValue();
|
|
cellvector * ocv = c->VectorValue ();
|
|
int s = cv->size ();
|
|
|
|
if (s != ocv->size ())
|
|
return false;
|
|
|
|
for (int ix = 0; ix < s; ++ix)
|
|
if (! cv->get (ix)->equal (ocv->get (ix)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
else if (t0 == String && t1 == String)
|
|
return !strcmp (StringValue (), c->StringValue ());
|
|
else if (t0 == Real && t1 == Real)
|
|
return RealValue () == c->RealValue ();
|
|
else
|
|
return eq (c);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//
|
|
// Access/Mutate Cons Cells. These are checked calls, in that they
|
|
// will verify that they are traversing a set of cons cells at each
|
|
// step, using "assert_cons", which throws a C++ exception if this is
|
|
// not found to be true.
|
|
|
|
Cell * Cell::caar (Cell * c) {return Cell::car (Cell::car (c));}
|
|
Cell * Cell::cadr (Cell * c) {return Cell::car (Cell::cdr (c));}
|
|
Cell * Cell::cdar (Cell * c) {return Cell::cdr (Cell::car (c));}
|
|
Cell * Cell::cddr (Cell * c) {return Cell::cdr (Cell::cdr (c));}
|
|
Cell * Cell::caaar (Cell * c) {return Cell::car (Cell::caar (c));}
|
|
Cell * Cell::caadr (Cell * c) {return Cell::car (Cell::cadr (c));}
|
|
Cell * Cell::cadar (Cell * c) {return Cell::car (Cell::cdar (c));}
|
|
Cell * Cell::caddr (Cell * c) {return Cell::car (Cell::cddr (c));}
|
|
Cell * Cell::cdaar (Cell * c) {return Cell::cdr (Cell::caar (c));}
|
|
Cell * Cell::cdadr (Cell * c) {return Cell::cdr (Cell::cadr (c));}
|
|
Cell * Cell::cddar (Cell * c) {return Cell::cdr (Cell::cdar (c));}
|
|
Cell * Cell::cdddr (Cell * c) {return Cell::cdr (Cell::cddr (c));}
|
|
Cell * Cell::caaaar (Cell * c) {return Cell::car (Cell::caaar (c));}
|
|
Cell * Cell::caaadr (Cell * c) {return Cell::car (Cell::caadr (c));}
|
|
Cell * Cell::caadar (Cell * c) {return Cell::car (Cell::cadar (c));}
|
|
Cell * Cell::caaddr (Cell * c) {return Cell::car (Cell::caddr (c));}
|
|
Cell * Cell::cadaar (Cell * c) {return Cell::car (Cell::cdaar (c));}
|
|
Cell * Cell::cadadr (Cell * c) {return Cell::car (Cell::cdadr (c));}
|
|
Cell * Cell::caddar (Cell * c) {return Cell::car (Cell::cddar (c));}
|
|
Cell * Cell::cadddr (Cell * c) {return Cell::car (Cell::cdddr (c));}
|
|
Cell * Cell::cdaaar (Cell * c) {return Cell::cdr (Cell::caaar (c));}
|
|
Cell * Cell::cdaadr (Cell * c) {return Cell::cdr (Cell::caadr (c));}
|
|
Cell * Cell::cdadar (Cell * c) {return Cell::cdr (Cell::cadar (c));}
|
|
Cell * Cell::cdaddr (Cell * c) {return Cell::cdr (Cell::caddr (c));}
|
|
Cell * Cell::cddaar (Cell * c) {return Cell::cdr (Cell::cdaar (c));}
|
|
Cell * Cell::cddadr (Cell * c) {return Cell::cdr (Cell::cdadr (c));}
|
|
Cell * Cell::cdddar (Cell * c) {return Cell::cdr (Cell::cddar (c));}
|
|
Cell * Cell::cddddr (Cell * c) {return Cell::cdr (Cell::cdddr (c));}
|
|
|
|
psymbol Cell::SymbolValue () const
|
|
{
|
|
typecheck (Symbol);
|
|
return cd.y;
|
|
}
|
|
|
|
void Cell::stats ()
|
|
{
|
|
for (int ix = 0; ix < NUM_TYPES; ++ix)
|
|
printf ("%s %d ", typeName [ix], typeCount [ix]);
|
|
|
|
printf ("\n");
|
|
}
|
|
|
|
//======================================================================
|
|
//
|
|
// Value Extractors
|
|
//
|
|
//======================================================================
|
|
|
|
intptr_t Cell::IntValue () const
|
|
{
|
|
if (short_atom (this))
|
|
return reinterpret_cast <intptr_t> (this) >> 8;
|
|
|
|
typecheck (Int); return cd.i;
|
|
}
|
|
|
|
char Cell::CharValue () const
|
|
{
|
|
typecheck (Char);
|
|
return cd.c;
|
|
}
|
|
|
|
Cell::SubrBox* Cell::SubrValue () const
|
|
{
|
|
typecheck (Subr);
|
|
return cd.f;
|
|
}
|
|
|
|
char * Cell::StringValue () const
|
|
{
|
|
typecheck (String);
|
|
return cd.s->s;
|
|
}
|
|
|
|
size_t Cell::StringLength () const
|
|
{
|
|
typecheck (String);
|
|
return cd.s->length;
|
|
}
|
|
|
|
FILE * Cell::IportValue () const
|
|
{
|
|
typecheck (Iport); return cd.ip;
|
|
}
|
|
|
|
FILE * Cell::OportValue () const
|
|
{
|
|
typecheck (Oport); return cd.op;
|
|
}
|
|
|
|
void * Cell::ContValue () const
|
|
{
|
|
typecheck (Cont); return cd.j;
|
|
}
|
|
|
|
cellvector * Cell::VectorValue () const
|
|
{
|
|
typecheck(Vec); return cd.cv;
|
|
}
|
|
|
|
cellvector * Cell::CProcValue () const
|
|
{
|
|
typecheck(Cproc); return cd.cv;
|
|
}
|
|
|
|
Cell* Cell::PromiseValue () const {
|
|
typecheck (Promise);
|
|
return cd.cv->get (0);
|
|
}
|
|
|
|
Cell* Cell::CPromiseValue() const {
|
|
typecheck(Cpromise);
|
|
return cd.cv->get(0);
|
|
}
|
|
|
|
psymbol Cell::BuiltinValue () const
|
|
{
|
|
typecheck (Builtin); return cd.y;
|
|
}
|
|
|
|
Cell::Procedure Cell::LambdaValue () const
|
|
{
|
|
typecheck (Lambda);
|
|
return Procedure (cd.cv->get (0), cd.cv->get (1), cd.cv->get (2));
|
|
}
|
|
|
|
double Cell::RealValue () const
|
|
{
|
|
typecheck (Real);
|
|
return *(cd.d);
|
|
}
|
|
|
|
const char * Cell::name () const
|
|
{
|
|
return typeName [type ()];
|
|
}
|
|
|
|
void Cell::typefail (Type t1, Type t2) const
|
|
{
|
|
sprintf (OS::errbuf, "type check failure: wanted %s, got %s",
|
|
typeName [t2], typeName [t1]); /* XXX sprintf into fixed buf */
|
|
|
|
OS::exception();
|
|
}
|
|
|
|
void Cell::dump (FILE * out)
|
|
{
|
|
Type t = type ();
|
|
fprintf (out, "[%p ", this);
|
|
if (ca.i == (FREE|ATOM)) fputs ("free ", out);
|
|
else
|
|
{
|
|
if (ca.i & MARK) fputs ("mark ", out);
|
|
if (short_atom (ca.p))
|
|
{
|
|
printf ("short %" PRIdPTR " ", ca.p->IntValue ());
|
|
}
|
|
else
|
|
{
|
|
if (ca.i & ATOM)
|
|
{
|
|
printf ("atom %04" PRIxPTR " ", ca.i);
|
|
if (ca.i & FORCED) fputs ("forced ", out);
|
|
if (ca.i & QUICK) fputs ("quick ", out);
|
|
if (ca.i & MACRO) fputs ("macro ", out);
|
|
if (ca.i & VREF) fputs ("vref ", out);
|
|
}
|
|
|
|
fputs (typeName [t], out);
|
|
|
|
switch (t)
|
|
{
|
|
case Cons:
|
|
fputs (" ", out);
|
|
if (ca.p == nil)
|
|
fputs ("nil", out);
|
|
else
|
|
fprintf (out, "%p", ca.p);
|
|
fputs (" ", out);
|
|
if (cd.p == nil)
|
|
fputs ("nil", out);
|
|
else
|
|
fprintf (out, "%p", cd.p);
|
|
break;
|
|
|
|
case Int: fprintf (out, " %" PRIdPTR, cd.i); break;
|
|
case Real: fprintf (out, " %g", RealValue ()); break;
|
|
case Unique: fprintf (out, " %s", cd.u); break;
|
|
case Symbol: fprintf (out, " %s", SymbolValue ()->key);
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
fputc (']', out);
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
//
|
|
// Cell Vectors
|
|
//
|
|
//======================================================================
|
|
|
|
cellvector::cellvector (int size /* = 0 */)
|
|
{
|
|
int allocate = (size == 0 ? 10 : size);
|
|
make_cv (size, allocate);
|
|
}
|
|
|
|
cellvector::cellvector (int size, int alloc)
|
|
{
|
|
make_cv (size, alloc);
|
|
}
|
|
|
|
void cellvector::make_cv (int size, int alloc)
|
|
{
|
|
v = (Cell **) malloc (alloc * sizeof (Cell *));
|
|
if (!v)
|
|
error (nomem_error);
|
|
allocated = alloc;
|
|
|
|
for (int ix = 0; ix < alloc; ++ix)
|
|
v [ix] = nil;
|
|
|
|
gc_index = 0;
|
|
gc_uplink = 0;
|
|
sz = size;
|
|
}
|
|
|
|
Cell *& cellvector::operator [] (int ix)
|
|
{
|
|
if (ix < 0 || ix >= sz)
|
|
vref_error ();
|
|
|
|
return v [ix];
|
|
}
|
|
|
|
void cellvector::set
|
|
(
|
|
int ix,
|
|
Cell * c
|
|
)
|
|
|
|
{
|
|
if (ix < 0 || ix >= sz)
|
|
vref_error ();
|
|
|
|
v [ix] = c;
|
|
}
|
|
|
|
void cellvector::expand ()
|
|
{
|
|
// Must expand vector: double size.
|
|
int new_alloc = 2 * allocated;
|
|
Cell ** v2 = (Cell **) malloc (new_alloc * sizeof (Cell *));
|
|
|
|
if (!v2)
|
|
error (nomem_error);
|
|
|
|
memcpy (v2, v, allocated * sizeof (Cell *));
|
|
::free (v);
|
|
v = v2;
|
|
allocated = new_alloc;
|
|
}
|
|
|
|
Cell * cellvector::shift ()
|
|
{
|
|
Cell * val = v[0];
|
|
for (int ix = 0; ix < sz - 1; ++ix)
|
|
v [ix] = v [ix+1];
|
|
pop ();
|
|
return val;
|
|
}
|
|
|
|
void cellvector::unshift (Cell * val)
|
|
{
|
|
push (nil);
|
|
for (int ix = sz-1; ix > 0; --ix)
|
|
v [ix] = v [ix-1];
|
|
v[0] = val;
|
|
}
|
|
|
|
void cellvector::vref_error ()
|
|
{
|
|
error ("vector reference out of bounds");
|
|
}
|
|
|
|
void cellvector::clear ()
|
|
{
|
|
sz = 0;
|
|
}
|
|
|
|
cellvector::~cellvector ()
|
|
{
|
|
::free (v);
|
|
sz = 0;
|
|
allocated = 0;
|
|
v = 0;
|
|
}
|
|
|
|
// Cellvector freelist management
|
|
|
|
cellvector* cellvector::freelist_head[cellvector::keep_size+1];
|
|
int cellvector::freelist_count[cellvector::keep_size+1];
|
|
|
|
cellvector* cellvector::alloc(int size) {
|
|
int allocate = size;
|
|
if (allocate == 0) allocate = 2;
|
|
return alloc(size, allocate);
|
|
}
|
|
|
|
cellvector* cellvector::alloc(int size, int allocate) {
|
|
cellvector* result;
|
|
if (allocate <= keep_size) {
|
|
if ((result = freelist_head[allocate])) {
|
|
freelist_head[allocate] = result->next_free;
|
|
for (int ix = 0; ix < allocate; ++ix)
|
|
result->v[ix] = nil;
|
|
result->sz = size;
|
|
result->next_free = 0;
|
|
--freelist_count[allocate];
|
|
return result;
|
|
}
|
|
}
|
|
return new cellvector(size, allocate);
|
|
}
|
|
|
|
void cellvector::free() {
|
|
if (allocated <= keep_size && freelist_count[allocated] <= keep_count) {
|
|
next_free = freelist_head[allocated];
|
|
++freelist_count[allocated];
|
|
freelist_head[allocated] = this;
|
|
} else {
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
//======================================================================
|
|
//
|
|
// Memory Allocation and Garbage Collection
|
|
//
|
|
//======================================================================
|
|
|
|
class Slab
|
|
{
|
|
public:
|
|
|
|
Cell * alloc ()
|
|
{
|
|
if (next + 1 > end)
|
|
return 0;
|
|
|
|
Cell * r = next;
|
|
++next;
|
|
return r;
|
|
}
|
|
|
|
int remaining ()
|
|
{
|
|
return static_cast<int>(end - next);
|
|
}
|
|
|
|
void reset ()
|
|
{
|
|
next = start;
|
|
}
|
|
|
|
void sweep (Context *);
|
|
|
|
Slab (Context * ctx)
|
|
{
|
|
// We avoid the temptation to call new Cell [slabsize],
|
|
// since that would invoke the constructor on each cell,
|
|
// which we don't need (alloc will take care of preparing
|
|
// cells for use).
|
|
//
|
|
// It is essential that Cells be 8-aligned to preserve
|
|
// three bits for type and GC information. If new has
|
|
// stiffed us with 4-aligned memory, we "burn" 4 bytes
|
|
// of it.
|
|
|
|
int storage_size = slabsize * sizeof (Cell) + 4;
|
|
storage = (char *) malloc (storage_size);
|
|
|
|
if (!storage)
|
|
error ("out of memory");
|
|
|
|
// Supposedly the ANSI library guarantees that storage
|
|
// is 4-aligned!
|
|
|
|
if ((reinterpret_cast<intptr_t>(storage)) & 3)
|
|
abort ();
|
|
|
|
// But if it's not 8-aligned we can fix that using the
|
|
// extra 4 bytes we allocated.
|
|
|
|
if ((reinterpret_cast<intptr_t>(storage)) & 7)
|
|
start = reinterpret_cast <Cell *> (storage + 4);
|
|
else
|
|
start = reinterpret_cast <Cell *> (storage);
|
|
|
|
memset (storage, 0, storage_size);
|
|
|
|
ctx->cellsTotal += slabsize;
|
|
end = start + slabsize;
|
|
reset ();
|
|
}
|
|
|
|
~Slab ()
|
|
{
|
|
free (storage);
|
|
}
|
|
|
|
static int slabsize;
|
|
|
|
private:
|
|
|
|
Cell * start;
|
|
Cell * end;
|
|
Cell * next;
|
|
char * storage;
|
|
};
|
|
|
|
int Slab::slabsize = 10000;
|
|
|
|
Cell * Context::alloc (Cell::Type t)
|
|
{
|
|
Cell * a;
|
|
|
|
mem.last_alloc_gc = false;
|
|
// Select a cell from the free list if one is available.
|
|
|
|
TOP:
|
|
if ((a = mem.free))
|
|
{
|
|
++cellsAlloc;
|
|
mem.free = a->cd.p;
|
|
a->ca.i = a->cd.i = 0;
|
|
a->set_type (t);
|
|
--mem.c_free;
|
|
return a;
|
|
}
|
|
|
|
// IF there aren't any slabs in the active pool,
|
|
// we must never have allocated any slabs at all
|
|
// yet, so allocate the first one.
|
|
|
|
if (mem.active.size () == 0)
|
|
{
|
|
// Configurable slabsize
|
|
|
|
char * c;
|
|
if ((c = getenv ("SLABSIZE")) != NULL)
|
|
Slab::slabsize = atoi (c);
|
|
|
|
mem.active.push ((Cell *) new Slab (this));
|
|
mem.free = 0;
|
|
mem.low_water = false;
|
|
mem.no_inline_gc = OS::flag (DEBUG_NO_INLINE_GC);
|
|
}
|
|
|
|
// Check the "top" slab to see if there's any room
|
|
// left in it.
|
|
|
|
if ((a = mem.current ()->alloc ()))
|
|
{
|
|
++cellsAlloc;
|
|
a->cd.i = 0;
|
|
a->set_type (t);
|
|
return a;
|
|
}
|
|
|
|
// There wasn't any room in the top slab. We can try
|
|
// to GC. If we do, and still 80% of the allocated
|
|
// memory is occupied, we set a flag admitting that
|
|
// the last GC was "unproductive", and next time 'round
|
|
// we'll allocate a new slab.
|
|
|
|
if (mem.no_inline_gc || mem.last_alloc_gc || mem.low_water)
|
|
{
|
|
mem.active.push ((Cell *) new Slab (this)); // trip to the well
|
|
mem.low_water = false; // low_water is a one-shot
|
|
}
|
|
else
|
|
{
|
|
mem.last_alloc_gc = true;
|
|
gc ();
|
|
}
|
|
|
|
goto TOP;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// GARBAGE COLLECTION
|
|
//
|
|
|
|
|
|
inline Cell * Cell::untagged (Cell * c) {
|
|
static const uintptr_t not_tagmask = ~Cell::TAGMASK;
|
|
|
|
return reinterpret_cast <Cell *>
|
|
(reinterpret_cast <uintptr_t> (c) & not_tagmask);
|
|
}
|
|
|
|
inline void Cell::gc_set_car (Cell * src)
|
|
{
|
|
unsigned int tagbits = ca.i & TAGMASK;
|
|
ca.p = src;
|
|
ca.i |= tagbits;
|
|
}
|
|
|
|
inline void Cell::gc_set_cdr (Cell * src)
|
|
{
|
|
unsigned int tagbits = cd.i & TAGMASK;
|
|
setcdr (this, src);
|
|
cd.i |= tagbits;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
// Marking for Garbage Collection
|
|
//
|
|
// This implementation is Knuth's Algorithm 2.3.5E (TAoCP 3ed. vol I
|
|
// p. 418) We follow Knuth's presentation carefully (using the same
|
|
// variable names and statement labels). Like the evaluator, this
|
|
// code has to take some care to avoid recursion: we want to be able
|
|
// to perform a GC mark wihtout allocating any additional space (not
|
|
// even C stack space). That accounts for some of the complexity in
|
|
// this routine. The other part is that, due to vectors, we have to
|
|
// support n-way marking instead of just 2-way marking.
|
|
|
|
void Context::mark (Cell * P)
|
|
{
|
|
bool traceall = OS::flag (TRACE_GC_ALL);
|
|
if (P == nil || P == 0 || Cell::short_atom(P) || P->ca.i & Cell::MARK)
|
|
return;
|
|
|
|
// In Knuth's presentation, a NODE contains two pointers
|
|
// (which he calls ALINK and BLINK, we car and cdr), and
|
|
// MARK and ATOM fields. In his layout, the MARK and
|
|
// ATOM fields can be manipulated easily without changing
|
|
// ALINK and BLINK, but in our case we store MARK and ATOM
|
|
// in the lower three bits of ALINK. We must therefore
|
|
// be cautious when transcribing the algorithm to avoid,
|
|
// e.g., clearing MARK and ATOM when copying a `car' pointer.
|
|
// We use "gc_set_car" and "gc_set_cdr" for this purpose.
|
|
|
|
// Secondly, Knuth occasionally sets the ATOM bit of a CONS
|
|
// to determine which of the pointers has been placed on
|
|
// the stack of deferred objects. But short atoms makes this
|
|
// difficult for us, as it's possible to have a cons of two
|
|
// short integers, say: then we need both ATOM fields of the
|
|
// CONS to contain that information. Instead of using the ATOM
|
|
// field, we use the MARK field of the cdr, which is not used
|
|
// for GC purposes.
|
|
|
|
//E1:
|
|
Cell * T = nil;
|
|
Cell * Q = nil;
|
|
|
|
E2: P->ca.i |= Cell::MARK;
|
|
if (traceall) { printf ("m "); P->dump (stdout); putchar ('\n'); }
|
|
|
|
// -- EXTENSION to Knuth's Step E2
|
|
//
|
|
// If the cell is a cons, Knuth's algorithm will take care of
|
|
// marking the things referenced as a result quite handily. But
|
|
// there are some atoms that can hold references too. Knuth's
|
|
// algorithm works for binary trees, but to deal with vectors et
|
|
// al. we need to make it work for n-way trees.
|
|
//
|
|
// When an atom can hold references to other cells, we organize
|
|
// these into a cellvector. In this way, we can treat all of them
|
|
// the same way.
|
|
//
|
|
// Whereas Knuth uses an atom bit to tell which side of a cons
|
|
// (car or cdr) he has stashed the pointer back to the
|
|
// as-yet-unmarked cells, when we traverse a vector we use an
|
|
// auxiliary integer field to tell us how many vector slots we
|
|
// have marked so far.
|
|
|
|
if (Cell::atomic (P))
|
|
{
|
|
if (P->flag (Cell::VREF))
|
|
{
|
|
// Getting "here" in the code means that we're seeing the
|
|
// vector of additional cell references for the first time
|
|
// (otherwise the mark bit will already be set). Our job is
|
|
// to kick off the iteration by stashing the back-link and
|
|
// starting the mark counter. The rest of the iteration will
|
|
// be handled in the "up" step below.
|
|
|
|
if (P->cd.cv->size () > 0)
|
|
{
|
|
P->cd.cv->gc_uplink = T;
|
|
P->cd.cv->gc_index = 0;
|
|
T = P;
|
|
}
|
|
}
|
|
else if (P->type () == Cell::Symbol)
|
|
{
|
|
// Symbols have property-list vectors, and so receive
|
|
// similar treatment to the above. But don't do this
|
|
// if we've already started to mark the properties
|
|
// (gc_uplink will be non-NULL in that case).
|
|
|
|
psymbol ps = P->SymbolValue ();
|
|
if (ps->plist
|
|
&& ps->plist->gc_uplink == 0
|
|
&& ps->plist->size () > 0)
|
|
{
|
|
ps->plist->gc_uplink = T;
|
|
ps->plist->gc_index = 0;
|
|
T = P;
|
|
}
|
|
}
|
|
|
|
goto E6; // E3
|
|
}
|
|
|
|
if (!Cell::short_atom(P->ca.p))
|
|
{
|
|
Q = Cell::untagged (P->ca.p); // E4
|
|
if (Q != nil && !(Q->ca.i & Cell::MARK))
|
|
{
|
|
//if (!Cell::short_atom(P->cd.p))
|
|
// {
|
|
// P->cd.i |= Cell::ATOM;
|
|
P->cd.i |= Cell::MARK;
|
|
// END
|
|
P->gc_set_car (T);
|
|
T = P;
|
|
// }
|
|
P = Q;
|
|
goto E2;
|
|
}
|
|
}
|
|
|
|
E5: if (!Cell::short_atom(P->cd.p))
|
|
{
|
|
Q = Cell::untagged (P->cd.p);
|
|
if (Q != nil && !(Q->ca.i & Cell::MARK))
|
|
{
|
|
P->gc_set_cdr (T);
|
|
T = P;
|
|
P = Q;
|
|
|
|
goto E2;
|
|
}
|
|
}
|
|
|
|
E6: if (T == nil)
|
|
return;
|
|
|
|
Q = T;
|
|
|
|
if (Q->flag (Cell::VREF))
|
|
{
|
|
// We are popping a vector cell from the GC stack.
|
|
// If there are more cells to mark within it, keep
|
|
// going.
|
|
|
|
next_element:
|
|
|
|
int i = Q->cd.cv->gc_index++;
|
|
|
|
if (i >= Q->cd.cv->size ()) // all done?
|
|
{
|
|
T = Q->cd.cv->gc_uplink;
|
|
Q->cd.cv->gc_index = 0; // reset for next time
|
|
P = Q;
|
|
goto E6;
|
|
}
|
|
else // resume iteration
|
|
{
|
|
P = Q->cd.cv->get (i); // with next element
|
|
|
|
// One wrinkle: captured continuations are implemented
|
|
// as vectors, and like the machine stack, these vectors
|
|
// can contain integer VM codes as well as cell pointers.
|
|
// These latter are marked with the ATOM flag.
|
|
|
|
if (reinterpret_cast <intptr_t> (P) & Cell::ATOM)
|
|
goto next_element;
|
|
|
|
// Otherwise we mark, if not marked already.
|
|
if (P->ca.i & Cell::MARK)
|
|
goto next_element;
|
|
|
|
P = Cell::untagged (P);
|
|
if (P == nil)
|
|
goto next_element;
|
|
|
|
goto E2;
|
|
}
|
|
}
|
|
else if (Q->type () == Cell::Symbol)
|
|
{
|
|
// Continue iterating over the property list of a symbol.
|
|
psymbol ps = Q->SymbolValue ();
|
|
|
|
next_property:
|
|
int i = ps->plist->gc_index++;
|
|
if (i >= ps->plist->size ()) // all done?
|
|
{
|
|
T = ps->plist->gc_uplink;
|
|
ps->plist->gc_index = 0;
|
|
ps->plist->gc_uplink = 0;
|
|
P = Q;
|
|
goto E6;
|
|
}
|
|
else
|
|
{
|
|
P = ps->plist->get (i);
|
|
if (P->ca.i & Cell::MARK)
|
|
goto next_property;
|
|
P = Cell::untagged (P);
|
|
if (P == nil)
|
|
goto next_property;
|
|
goto E2;
|
|
}
|
|
}
|
|
|
|
// if (Q->cd.i & Cell::ATOM)
|
|
if (Q->cd.i & Cell::MARK)
|
|
{
|
|
// Q->cd.i &= ~Cell::ATOM;
|
|
Q->cd.i &= ~Cell::MARK;
|
|
T = Cell::untagged (Q->ca.p);
|
|
Q->gc_set_car (P);
|
|
P = Q;
|
|
goto E5;
|
|
}
|
|
else
|
|
{
|
|
T = Cell::untagged (Q->cd.p);
|
|
Q->gc_set_cdr (P);
|
|
P = Q;
|
|
goto E6;
|
|
}
|
|
}
|
|
|
|
void Slab::sweep (Context * ctx)
|
|
{
|
|
bool traceall = OS::flag (TRACE_GC_ALL);
|
|
|
|
for (Cell * p = start; p < next; ++p)
|
|
{
|
|
unsigned int word = p->ca.i;
|
|
|
|
if (word & Cell::MARK)
|
|
{
|
|
p->ca.i &= ~Cell::MARK;
|
|
}
|
|
else if (word != (Cell::FREE|Cell::ATOM))
|
|
{
|
|
// FINALIZATION
|
|
//
|
|
|
|
if (traceall) { printf ("s "); p->dump (stdout); putchar ('\n'); }
|
|
Cell::Type t = p->type ();
|
|
|
|
switch (t)
|
|
{
|
|
case Cell::Cont:
|
|
case Cell::Promise:
|
|
case Cell::Cproc:
|
|
case Cell::Cpromise:
|
|
case Cell::Lambda:
|
|
case Cell::Vec: // Free the vector of cell pointers.
|
|
p->cd.cv->free();
|
|
// XXX delete p->cd.cv;
|
|
p->cd.cv = 0;
|
|
break;
|
|
|
|
case Cell::Iport: // Ports hold streams
|
|
fclose (p->cd.ip);
|
|
break;
|
|
|
|
case Cell::Oport: // Ports hold streams
|
|
fclose (p->cd.op);
|
|
break;
|
|
|
|
case Cell::Real: // Reals hold a malloc'd double
|
|
free (p->cd.d);
|
|
break;
|
|
|
|
case Cell::Subr: // Subrs hold a SubrBox
|
|
free (p->cd.f);
|
|
break;
|
|
|
|
case Cell::Magic: // Magic cells hold a MagicBox
|
|
free (p->cd.m);
|
|
break;
|
|
|
|
case Cell::String: // Strings hold StringBoxes
|
|
free (p->cd.s);
|
|
break;
|
|
|
|
default: // Ordinarily cells hold no other storage.
|
|
;
|
|
}
|
|
|
|
--ctx->cellsAlloc;
|
|
p->ca.i = Cell::FREE | Cell::ATOM;
|
|
p->cd.p = ctx->mem.free;
|
|
ctx->mem.free = p;
|
|
++ctx->mem.c_free;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Context::gc ()
|
|
{
|
|
bool gc_verbose = OS::flag (TRACE_GC);
|
|
Cell * p;
|
|
|
|
if (!ok_to_gc)
|
|
{
|
|
fprintf (stderr, "initial memory budget insufficient to set up VM\n"
|
|
"Try setting the environment variable SLABSIZE to\n"
|
|
"something greater than %d\n", Slab::slabsize);
|
|
exit (1);
|
|
}
|
|
if (gc_verbose)
|
|
printf ("; start gc: %d/%d\n", cellsAlloc, cellsTotal);
|
|
|
|
//
|
|
// MARK PHASE
|
|
//
|
|
// We have to mark everything reachable from the "register machine"
|
|
// registers.
|
|
|
|
mark (root_envt);
|
|
mark (r_env);
|
|
mark (Cell::car (&r_argl));
|
|
mark (Cell::cdr (&r_argl));
|
|
mark (Cell::car (&r_varl));
|
|
mark (Cell::cdr (&r_varl));
|
|
mark (r_proc);
|
|
mark (r_exp);
|
|
mark (r_unev);
|
|
mark (r_val);
|
|
mark (r_tmp);
|
|
mark (r_elt);
|
|
mark (r_nu);
|
|
mark (cc_procedure);
|
|
mark (empty_vector);
|
|
|
|
// Mark the things is the compiler VM.
|
|
//
|
|
|
|
mark (r_cproc);
|
|
mark (r_envt);
|
|
|
|
// Mark everything reachable from the machine stack. Watch out
|
|
// for integers hiding in the machine stack, though! They are
|
|
// marked with the ATOM flag.
|
|
|
|
for (int ix = 0; ix < m_stack.size (); ++ix)
|
|
if ((reinterpret_cast <intptr_t> ((p = m_stack [ix])) & Cell::ATOM) == 0)
|
|
mark (p);
|
|
|
|
// Mark the I/O ports referenced in this environment stack.
|
|
|
|
for (int ix = 0; ix < istack.size (); ++ix)
|
|
mark (istack [ix]);
|
|
for (int ix = 0; ix < ostack.size (); ++ix)
|
|
mark (ostack [ix]);
|
|
|
|
// Mark the things that "C" implementations of Scheme functions
|
|
// have requested protection for.
|
|
|
|
for (int ix = 0; ix < r_gcp.size (); ++ix)
|
|
mark (r_gcp [ix]);
|
|
|
|
//
|
|
// SWEEP PHASE
|
|
//
|
|
|
|
for (int ix = 0; ix < mem.active.size (); ++ix)
|
|
((Slab *) mem.active [ix])->sweep (this);
|
|
|
|
// If this mark/sweep phase managed to reduce the cell utilization
|
|
// to <= 80% of the allocated cells, we consider that success. On
|
|
// the other hand, if the GC produced less than 20% free cells, we
|
|
// set a flag which will provoke the allocation of a new slab at
|
|
// the next allocation failure. In this way we hope to avoid
|
|
// "grinding away" at the last few cells in a slab.
|
|
|
|
if ((double) cellsAlloc / cellsTotal > 0.8)
|
|
mem.low_water = true;
|
|
|
|
if (gc_verbose)
|
|
printf ("; end gc: %d/%d %s\n", cellsAlloc, cellsTotal,
|
|
mem.low_water ? " low" : " ok");
|
|
}
|
|
|
|
void Context::gc_if_needed ()
|
|
{
|
|
if (cellsAlloc >= cellsTotal / 4 * 3)
|
|
gc ();
|
|
}
|
|
|
|
void Context::print_mem_stats (FILE * out)
|
|
{
|
|
fprintf (out, "; mem %d/%d\n", cellsAlloc, cellsTotal);
|
|
}
|
|
|
|
void * Context::xmalloc (size_t sz)
|
|
{
|
|
void * v = malloc (sz);
|
|
if (!v)
|
|
error ("out of heap memory");
|
|
return v;
|
|
}
|