/* xsimage.c - xscheme memory image save/restore functions */
/*	Copyright (c) 1988, by David Michael Betz
	All Rights Reserved
	Permission is granted for unrestricted non-commercial use	*/

#include "xscheme.h"

/* virtual machine registers */
extern LVAL xlfun;		/* current function */
extern LVAL xlenv;		/* current environment */
extern LVAL xlval;		/* value of most recent instruction */

/* stack limits */
extern LVAL *xlstkbase;		/* base of value stack */
extern LVAL *xlstktop;		/* top of value stack */

/* node space */
extern NSEGMENT *nsegments;	/* list of node segments */

/* vector (and string) space */
extern VSEGMENT *vsegments;	/* list of vector segments */
extern LVAL *vfree;		/* next free location in vector space */
extern LVAL *vtop;		/* top of vector space */

/* global variables */
extern LVAL obarray,eof_object,default_object;
extern jmp_buf top_level;
extern FUNDEF funtab[];

/* local variables */
static OFFTYPE off,foff;
static FILE *fp;

/* external routines */
extern FILE *osbopen();

/* forward declarations */
OFFTYPE readptr();
OFFTYPE cvoptr();
LVAL cviptr();

/* xlisave - save the memory image */
int xlisave(fname)
  char *fname;
{
    unsigned char *cp;
    NSEGMENT *nseg;
    int size,n;
    LVAL p,*vp;

    /* open the output file */
    if ((fp = osbopen(fname,"w")) == NULL)
	return (FALSE);

    /* first call the garbage collector to clean up memory */
    gc();

    /* write out the stack size */
    writeptr((OFFTYPE)(xlstktop-xlstkbase));

    /* write out the *obarray* symbol and various constants */
    writeptr(cvoptr(obarray));
    writeptr(cvoptr(eof_object));
    writeptr(cvoptr(default_object));

    /* setup the initial file offsets */
    off = foff = (OFFTYPE)2;

    /* write out all nodes that are still in use */
    for (nseg = nsegments; nseg != NULL; nseg = nseg->ns_next) {
	p = &nseg->ns_data[0];
	n = nseg->ns_size;
	for (; --n >= 0; ++p, off += sizeof(NODE))
	    switch (ntype(p)) {
	    case FREE:
		break;
	    case CONS:
	    case CLOSURE:
	    case METHOD:
	    case PROMISE:
	    case ENV:
		setoffset();
		osbputc(p->n_type,fp);
		writeptr(cvoptr(car(p)));
		writeptr(cvoptr(cdr(p)));
		foff += sizeof(NODE);
		break;
	    case SYMBOL:
	    case OBJECT:
	    case VECTOR:
	    case CODE:
	    case CONTINUATION:
		setoffset();
		osbputc(p->n_type,fp);
		size = getsize(p);
		writeptr((OFFTYPE)size);
		for (vp = p->n_vdata; --size >= 0; )
		    writeptr(cvoptr(*vp++));
		foff += sizeof(NODE);
		break;
	    case STRING:
		setoffset();
		osbputc(p->n_type,fp);
		size = getslength(p);
		writeptr((OFFTYPE)size);
		for (cp = getstring(p); --size >= 0; )
		    osbputc(*cp++,fp);
		foff += sizeof(NODE);
		break;
	    default:
		setoffset();
		writenode(p);
		foff += sizeof(NODE);
		break;
	    }
    }

    /* write the terminator */
    osbputc(FREE,fp);
    writeptr((OFFTYPE)0);

    /* close the output file */
    osclose(fp);

    /* return successfully */
    return (TRUE);
}

/* xlirestore - restore a saved memory image */
int xlirestore(fname)
  char *fname;
{
    LVAL *getvspace();
    unsigned int ssize;
    unsigned char *cp;
    int size,type;
    LVAL p,*vp;

    /* open the file */
    if ((fp = osbopen(fname,"r")) == NULL)
	return (FALSE);

    /* free the old memory image */
    freeimage();

    /* read the stack size */
    ssize = (unsigned int)readptr();

    /* allocate memory for the workspace */
    xlminit(ssize);

    /* read the *obarray* symbol and various constants */
    obarray = cviptr(readptr());
    eof_object = cviptr(readptr());
    default_object = cviptr(readptr());
    
    /* read each node */
    for (off = (OFFTYPE)2; (type = osbgetc(fp)) >= 0; )
	switch (type) {
	case FREE:
	    if ((off = readptr()) == (OFFTYPE)0)
		goto done;
	    break;
	case CONS:
	case CLOSURE:
	case METHOD:
	case PROMISE:
	case ENV:
	    p = cviptr(off);
	    p->n_type = type;
	    rplaca(p,cviptr(readptr()));
	    rplacd(p,cviptr(readptr()));
	    off += sizeof(NODE);
	    break;
	case SYMBOL:
	case OBJECT:
	case VECTOR:
	case CODE:
	case CONTINUATION:
	    p = cviptr(off);
	    p->n_type = type;
	    p->n_vsize = size = (int)readptr();
	    p->n_vdata = getvspace(p,size);
	    for (vp = p->n_vdata; --size >= 0; )
		*vp++ = cviptr(readptr());
	    off += sizeof(NODE);
	    break;
	case STRING:
	    p = cviptr(off);
	    p->n_type = type;
	    p->n_vsize = size = (int)readptr();
	    p->n_vdata = getvspace(p,btow_size(size));
	    for (cp = getstring(p); --size >= 0; )
		*cp++ = osbgetc(fp);
	    off += sizeof(NODE);
	    break;
	case PORT:
	    p = cviptr(off);
	    readnode(type,p);
	    setfile(p,NULL);
	    off += sizeof(NODE);
	    break;
	case SUBR:
	case XSUBR:
	    p = cviptr(off);
	    readnode(type,p);
	    p->n_subr = funtab[getoffset(p)].fd_subr;
	    off += sizeof(NODE);
	    break;
	default:
	    readnode(type,cviptr(off));
	    off += sizeof(NODE);
	    break;
	}
done:

    /* close the input file */
    osclose(fp);

    /* collect to initialize the free space */
    gc();

    /* lookup all of the symbols the interpreter uses */
    xlsymbols();

    /* return successfully */
    return (TRUE);
}

/* freeimage - free the current memory image */
LOCAL freeimage()
{
    NSEGMENT *nextnseg;
    VSEGMENT *nextvseg;
    FILE *fp;
    LVAL p;
    int n;

    /* close all open ports and free each node segment */
    for (; nsegments != NULL; nsegments = nextnseg) {
	nextnseg = nsegments->ns_next;
	p = &nsegments->ns_data[0];
	n = nsegments->ns_size;
	for (; --n >= 0; ++p)
	    switch (ntype(p)) {
	    case PORT:
		if ((fp = getfile(p))
		 && (fp != stdin && fp != stdout && fp != stderr))
		    osclose(getfile(p));
		break;
	    }
	free(nsegments);
    }

    /* free each vector segment */
    for (; vsegments != NULL; vsegments = nextvseg) {
	nextvseg = vsegments->vs_next;
	free(vsegments);
    }
    
    /* free the stack */
    if (xlstkbase)
	free(xlstkbase);
}

/* setoffset - output a positioning command if nodes have been skipped */
LOCAL setoffset()
{
    if (off != foff) {
	osbputc(FREE,fp);
	writeptr(off);
	foff = off;
    }
}

/* writenode - write a node to a file */
LOCAL writenode(node)
  LVAL node;
{
    char *p = (char *)&node->n_info;
    int n = sizeof(union ninfo);
    osbputc(node->n_type,fp);
    while (--n >= 0)
	osbputc(*p++,fp);
}

/* writeptr - write a pointer to a file */
LOCAL writeptr(off)
  OFFTYPE off;
{
    char *p = (char *)&off;
    int n = sizeof(OFFTYPE);
    while (--n >= 0)
	osbputc(*p++,fp);
}

/* readnode - read a node */
LOCAL readnode(type,node)
  int type; LVAL node;
{
    char *p = (char *)&node->n_info;
    int n = sizeof(union ninfo);
    node->n_type = type;
    while (--n >= 0)
	*p++ = osbgetc(fp);
}

/* readptr - read a pointer */
LOCAL OFFTYPE readptr()
{
    OFFTYPE off;
    char *p = (char *)&off;
    int n = sizeof(OFFTYPE);
    while (--n >= 0)
	*p++ = osbgetc(fp);
    return (off);
}

/* cviptr - convert a pointer on input */
LOCAL LVAL cviptr(o)
  OFFTYPE o;
{
    NSEGMENT *newnsegment(),*nseg;
    OFFTYPE off = (OFFTYPE)2;
    OFFTYPE nextoff;

    /* check for nil and small fixnums */
    if (o == (OFFTYPE)0 || (o & 1) == 1)
	return ((LVAL)o);

    /* compute a pointer for this offset */
    for (nseg = nsegments; nseg != NULL; nseg = nseg->ns_next) {
	nextoff = off + (OFFTYPE)(nseg->ns_size * sizeof(NODE));
	if (o >= off && o < nextoff)
	    return ((LVAL)((OFFTYPE)&nseg->ns_data[0] + o - off));
	off = nextoff;
    }

    /* create new segments if necessary */
    for (;;) {

	/* create the next segment */
	if ((nseg = newnsegment(NSSIZE)) == NULL)
	    xlfatal("insufficient memory - segment");

	/* check to see if the offset is in this segment */
	nextoff = off + (OFFTYPE)(nseg->ns_size * sizeof(NODE));
	if (o >= off && o < nextoff)
	    return ((LVAL)((OFFTYPE)&nseg->ns_data[0] + o - off));
	off = nextoff;
    }
}

/* cvoptr - convert a pointer on output */
LOCAL OFFTYPE cvoptr(p)
  LVAL p;
{
    OFFTYPE off = (OFFTYPE)2;
    NSEGMENT *nseg;

    /* check for nil and small fixnums */
    if (p == NIL || !ispointer(p))
	return ((OFFTYPE)p);

    /* compute an offset for this pointer */
    for (nseg = nsegments; nseg != NULL; nseg = nseg->ns_next) {
	if (INSEGMENT(p,nseg))
	    return (off + ((OFFTYPE)p - (OFFTYPE)&nseg->ns_data[0]));
	off += (OFFTYPE)(nseg->ns_size * sizeof(NODE));
    }

    /* pointer not within any segment */
    xlerror("bad pointer found during image save",p);
}

/* getvspace - allocate vector space */
LOCAL LVAL *getvspace(node,size)
  LVAL node; unsigned int size;
{
    LVAL *p;
    ++size; /* space for the back pointer */
    if (!VCOMPARE(vfree,size,vtop)
    &&  !checkvmemory(size)
    &&  !makevmemory(size))
	xlfatal("insufficient vector space");
    p = vfree;
    vfree += size;
    *p++ = node;
    return (p);
}
