/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1984-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * compiler and loader for the architecture independent makefile object format
 *
 * The format fields are labeled by sequence number and type:
 *
 *	# sfputu()/sfgetu()
 *	$ size,string (no trailing 0)
 *	@ 0 terminated string
 *
 * The format is designed for per-section backward/forward compatible
 * additions to the header, rules, variables and trailer sections, with
 * the proviso that the section order is not changed and that sequence 1
 * fields must appear in all future formats.  This means that the
 * property, dynamic and status field bit values are permanently fixed
 * by each sequence.  Field addition semantics are controlled by the
 * sequence number.
 *
 * header:
 *
 *	1 4 magic	must match
 *	1 @ ident	identification string (information only)
 *	1 # size	header size
 *	1 # sequence	to label additions/changes
 *	1 # flags	OBJ_* flags
 *	1 # strings	string table size
 *	1 # lists	number of lists
 *	1 # rules	number of rules
 *	1 # rulenum	(RULENUM-MINRULENUM)
 *	1 # rulestr	(RULESTR-MINRULESTR)
 *	1 # variables	number of variables
 *	1 # varnum	(VARNUM-MINVARNUM)
 *	1 # varstr	(VARSTR-MINVARSTR)
 *	1 4 magic	again for verification
 *	* # ...		[header number fields additions here]
 *
 * optional headers:
 *
 *	1 # size	header size
 *	1 # type	header type
 *	* * ...		header contents
 *
 * HEADER_PREREQS:
 *
 *	1 # type	{0:end COMP_*:type}
 *	1 # time	time
 *	1 @ name	unbound name
 *	    ...
 *
 * variables:
 *
 *	1 # property
 *	* # ...		[variable number field additions here]
 *	1 $ name	name string
 *	1 $ value	value string
 *	* $ ...		[variable string field additions here]
 *
 * rules:
 *
 *	1 # property
 *	1 # dynamic
 *	1 # attribute
 *	1 # encoded	status|semaphore|view|scan
 *	1 # prereqs	prereq list index
 *	1 # time	rule time
 *	1 # nsec	rule nsec [2004-12-01]
 *	1 # eventnsec	event nsec [2004-12-01]
 *	* # ...		[rule number field additions here]
 *	1 $ name	name string
 *	1 $ action	action string
 *	1 $ data	event time or state string
 *	* $ ...		[rule string field additions here]
 *
 * lists:
 *
 *	1 # rule	rule index
 *
 * trailer:
 *
 *	1 @ options	option string
 *	* @ ...		[trailer string fields additions here]
 *
 * NOTE: the old format compatibility code should probably be dropped in 95
 * NOTE: HA -- as of 1997-08-11 2.1 was still in production use
 */

#include "make.h"
#include "options.h"

#include <ccode.h>

/*
 * old rule load() replacement puns on struct rule
 *
 * rule.mark is not used as load() may be triggered
 * by staterule() while marks are in use
 */

#define getoldrule(r)	((Rule_t*)r->action)
#define isoldrule(r)	(r->status==OLDRULE)
#define setoldrule(r,o)	(r->status=OLDRULE,r->action=(char*)o,r->prereqs=(List_t*)oldrules,oldrules=r)

#define MAGIC		"\015\001\013\005"
#define MAGICSIZE	(sizeof(MAGIC)-1)

#define SEQUENCE	1		/* track semantic diffs		*/
#define HEADERSIZE	128		/* handle largest header	*/

#define HEADER_PREREQS	1		/* prereqs optional header	*/

#define MINRULENUM	6		/* min # rule number fields	*/
#define MINRULESTR	3		/* min # rule string fields	*/
#define MINVARNUM	1		/* min # variable number fields	*/
#define MINVARSTR	2		/* min # variable string fields	*/

#define RULENUM		(MINRULENUM+2)	/* # rule number fields		*/
#define RULESTR		(MINRULESTR+0)	/* # rule string fields		*/
#define VARNUM		(MINVARNUM+0)	/* # variable number fields	*/
#define VARSTR		(MINVARSTR+0)	/* # variable string fields	*/

typedef struct Compstate_s		/* compile state		*/
{
	char*		sp;		/* string table pointer		*/
	Sfio_t*		fp;		/* object file pointer		*/
	unsigned long	lists;		/* list index			*/
	unsigned long	rules;		/* rule index			*/
	unsigned long	strings;	/* string index			*/
	unsigned long	variables;	/* variable index		*/
} Compstate_t;

typedef struct Loadstate_s		/* load state			*/
{
	char*		sp;		/* string table pointer		*/
} Loadstate_t;

static struct Object_s			/* object global state		*/
{
	Sfio_t*		pp;		/* prerequisite ref pointer	*/
	unsigned char*	a2n;		/* CC_ASCII=>CC_NATIVE		*/
	unsigned char*	n2a;		/* CC_NATIVE=>CC_ASCII		*/
	unsigned long	garbage;	/* state garbage count		*/
	unsigned long	rules;		/* state rule count		*/
	int		initialized;	/* state initialized		*/
	int		lowres;		/* low resolution time state	*/
} object;

/*
 * old object format compatibility
 * frozen 1992-12-25
 */

#define OLD_MAGIC	0x0d010b05
#define OLD_OLD_MAGIC	0x0000ff5d
#define OLD_VERSION	"AT&T Bell Laboratories 08/11/89"
#define OLD_VERSION_2	"AT&T Bell Laboratories 01/24/89"
#define OLD_SEQUENCE	2
#define OLD_ALIGN	8

#define old_data	u2.u_data
#define old_event	u2.u_event

struct OLD_list_s; typedef struct OLD_list_s OLD_list_t;

typedef struct OLD_header_s		/* old make object file header	*/
{
	long		magic;		/* magic number			*/
	char		version[32];	/* compiler/loader version	*/
	unsigned char	null;		/* 0 byte for long version's	*/
	unsigned char	sequence;	/* different still compatible	*/
	unsigned char	sizes[10];	/* misc size checks		*/
} OLD_header_t;

typedef struct OLD_trailer_s		/* old make object file trailer	*/
{
	long		magic;		/* magic number			*/
	char*		options;	/* options for set()		*/
	long		lists;		/* number of compiled lists	*/
	long		rules;		/* number of compiled rules	*/
	long		size;		/* total sizeof object file	*/
	long		variables;	/* number of compiled variables	*/
} OLD_trailer_t;

typedef struct OLD_rule_s		/* old rule			*/
{
	char*		name;		/* rule name			*/

	union
	{
	Frame_t*	u_active;	/* active target frame		*/
	unsigned long	u_complink;	/* compilation link		*/
	Rule_t*		u_freelink;	/* free list link		*/
	}		u1;

	union
	{
	char*		u_uname;	/* unbound name			*/
	char*		u_data;		/* state value			*/
	unsigned long	u_event;	/* state rule event time	*/
	}		u2;

	OLD_list_t*	prereqs;	/* prerequisites		*/
	char*		action;		/* update action		*/
	unsigned long	time;		/* modify time			*/

	long		attribute;	/* external named attributes	*/
	long		dynamic;	/* dynamic properties		*/
	long		property;	/* stable properties		*/

#define noswap		scan		/* 0 or char elts after here	*/

	unsigned char	scan;		/* file scan strategy index	*/
	unsigned char	semaphore;	/* semaphore + count		*/
	unsigned char	status;		/* disposition			*/
	unsigned char	view;		/* view bind index		*/

#if BINDINDEX
	unsigned char	source;		/* source bind index		*/
#else
	unsigned char	spare_1;	/* spare			*/
#endif
	unsigned char	preview;	/* min prereq view		*/

	unsigned short	must;		/* cancel if == 0		*/

	char*		runtime;	/* run time info		*/
} OLD_rule_t;

typedef struct OLD_var_s		/* old variable			*/
{
	char*		name;		/* name				*/
	char*		value;		/* value			*/
	long		property;	/* static and dynamic		*/
	long		length;		/* maximum length of value	*/
} OLD_var_t;

struct OLD_list_s			/* old rule cons cell		*/
{
	OLD_list_t*	next;		/* next in list			*/
	OLD_rule_t*	rule;		/* list item			*/
};

static OLD_header_t	old_stamp =	/* old object header is fixed	*/
{
	OLD_MAGIC,
	OLD_VERSION,
	0,
	OLD_SEQUENCE,
	OLD_ALIGN,
	CHAR_BIT,
	sizeof(char),
	sizeof(short),
	sizeof(long),
	sizeof(char*),
	sizeof(OLD_list_t),
	sizeof(OLD_rule_t),
	sizeof(OLD_var_t),
	0,
};

/*
 * initialize the object ccode tables
 */

void
initcode(void)
{
	if (!object.initialized)
	{
		object.initialized = 1;
		object.a2n = ccmap(CC_ASCII, CC_NATIVE);
		object.n2a = ccmap(CC_NATIVE, CC_ASCII);
	}
}

/*
 * read canonical 0 terminated string from object file
 */

static char*
getstring(Sfio_t* sp)
{
	char*	s;

	if (s = sfgetr(sp, 0, 0))
		ccmapstr(object.a2n, s, sfvalue(sp));
	return s;
}

/*
 * write canonical 0 terminated string to file
 */

static void
putstring(register Sfio_t* sp, register const char* s, int sep)
{
	register int		c;
	register unsigned char*	map;

	if (map = object.n2a)
	{
		while (c = *(unsigned char*)s++)
			sfputc(sp, map[c]);
		if (sep >= 0)
			sfputc(sp, map[sep]);
	}
	else
		sfputr(sp, s, sep);
}

/*
 * recursively mark r and its prerequisites for compilation
 */

static void
markcompile(register Rule_t* r)
{
	register List_t*	p;

	r->dynamic &= ~D_compiled;
	r->mark |= M_compile;
	for (p = r->prereqs; p; p = p->next)
		if (!(p->rule->mark & M_compile))
			markcompile(p->rule);
}

/*
 * mark state file garbage candidates
 */

static void
markgarbage(register Rule_t* r, int garbage)
{
	register List_t*	p;
	register int		i;
	Rule_t*			x;

	r->mark |= M_compile;
	if (garbage)
		r->dynamic |= D_garbage;
	else
		r->dynamic &= ~D_garbage;
	for (p = r->prereqs; p; p = p->next)
		if (!(p->rule->mark & M_compile))
			markgarbage(p->rule, garbage);
	for (i = RULE; i <= STATERULES; i++)
		if ((x = staterule(i, r, NiL, 0)) && !(x->mark & M_compile))
			markgarbage(x, garbage);
}

/*
 * compile a string
 */

static void
compstring(register Compstate_t* cs, register char* s)
{
	register int		c;
	register unsigned char*	map;

	if (s)
	{
		c = strlen(s) + 1;
		cs->strings += c;
		sfputu(cs->fp, c);
		if (map = object.n2a)
			while (c = *s++)
				sfputc(cs->fp, map[c]);
		else
			sfwrite(cs->fp, s, c - 1);
	}
	else
		sfputu(cs->fp, 0);
}

/*
 * initialize rules for compilation
 * if h!=0 then all rules marked compiled
 */

static int
compinit(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;

	NoP(s);
	r->complink = 0;
	r->mark &= ~M_compile;
	if (r->dynamic & D_alias)
	{
		state.compnew = compinit;
		state.comparg = h;
		mergestate(makerule(r->name), r);
		state.compnew = 0;
	}
	if (h || (r->property & P_internal))
		r->dynamic |= D_compiled;
	return 0;
}

/*
 * mark selected rules and immediate prereqs for compilation
 *
 * NOTE: remember to clear r->mark (from markcompile())
 */

static int
compselect(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;
	register char*		select = (char*)h;

	NoP(s);
	if (!(r->mark & M_compile) && (r->dynamic & D_compiled))
	{
		state.frame->target = r;
		expand(internal.met, select);
		if (sfstrtell(internal.met))
		{
			sfstrseek(internal.met, 0, SEEK_SET);
			markcompile(r);
		}
	}
	return 0;
}

/*
 * mark rules so that only state vars and immediate prereqs will be compiled
 *
 * NOTE: remember to clear r->mark (from markgarbage())
 */

static int
compstate(const char* s, char* v, void* h)
{
	register Rule_t*		r = (Rule_t*)v;

	NoP(s);
	NoP(h);
	r->dynamic |= D_compiled;
	if (r->dynamic & D_garbage)
	{
		if (!object.garbage)
			r->dynamic &= ~D_garbage;
		else if (!(r->mark & M_compile))
			markgarbage(r, 1);
	}
	return 0;
}

/*
 * mark prerequisites for compilation
 *
 * NOTE: remember to clear r->mark (from markcompile())
 */

static int
compmark(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;
	register List_t*	p;

	NoP(s);
	NoP(h);
	r->complink = 0;
	if (state.stateview == 0)
	{
		if ((r->property & P_state) && !r->view && !(r->dynamic & (D_garbage|D_lower)))
		{
			r->dynamic &= ~D_compiled;
			for (p = r->prereqs; p; p = p->next)
				p->rule->dynamic &= ~D_compiled;
		}
	}
	else if (!(r->dynamic & D_compiled) && !(r->mark & M_compile))
		markcompile(r);
	return 0;
}

/*
 * weed out the real garbage
 */

static int
compkeep(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;

	NoP(s);
	NoP(h);
	if (!(r->mark & M_compile) && !(r->dynamic & D_garbage))
		markgarbage(r, 0);
	return 0;
}

/*
 * compile an individual rule
 */

static int
comprule(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;
	register List_t*	p;
	register Compstate_t*	cs = (Compstate_t*)h;
	Rule_t			x;

	NoP(s);

	/*
	 * compile each rule only once
	 */

	if ((r->dynamic & D_compiled) || s != r->name && !(r->dynamic & D_alias))
		return 0;
	r->dynamic |= D_compiled;
	r->mark |= M_compile;
#if DEBUG
	if (r->property & P_internal)
		error(1, "internal rule %s should not be compiled", r->name);
#endif

	/*
	 * set the current rule index for prerequisite list compilation
	 */

	r->complink = cs->rules++;
	x = *r;
	r = &x;

	/*
	 * make sure the unbound rule name is compiled
	 */

	if (!(r->property & P_state))
	{
		if (state.stateview == 0)
		{
			r->prereqs = 0;
			r->action = 0;
			r->dynamic &= ~D_compiled;
		}
		if (r->uname && !(r->property & P_metarule))
		{
			r->name = r->uname;
			r->uname = 0;
		}
		r->time = 0;
#if BINDINDEX
		r->view = 0;
#endif
	}
#if !BINDINDEX
	r->view = 0;
#endif

	/*
	 * compile the fields
	 */

	sfputu(cs->fp, r->property);
	sfputu(cs->fp, r->dynamic & ~D_CLEAROBJECT);
	sfputu(cs->fp, r->attribute);
	sfputu(cs->fp, (r->semaphore<<16)|(r->view<<8)|(r->scan));
	if (p = r->prereqs)
	{
		sfputu(cs->fp, cs->lists);
		do cs->lists++; while (p = p->next);
	}
	else
		sfputu(cs->fp, 0);
	sfputu(cs->fp, tmxsec(r->time));

	/*
	 * 2004-12-01
	 */

	sfputu(cs->fp, tmxnsec(r->time));
	sfputu(cs->fp, (r->property & P_staterule) ? tmxnsec(r->event) : 0);

	compstring(cs, r->name);
	compstring(cs, r->action);
	if (r->property & P_staterule)
		sfputu(cs->fp, tmxsec(r->event));
	else
		compstring(cs, r->statedata);
	return 0;
}

/*
 * a final pass before complist() to catch any prereqs
 * that eluded comprule()
 */

static int
compcheck(const char* s, char* v, void* h)
{
	register Rule_t*		r = (Rule_t*)v;
	register List_t*		p;
	register Rule_t*		a;

	/*
	 * ignore aliases and rules not set up by comprule()
	 */

	if (!r->complink || !(r->mark & M_compile) || s != r->name && !(r->dynamic & D_alias) || state.stateview == 0 && !(r->property & P_state))
		return 0;
	if (p = r->prereqs)
	{
		do
		{
			for (r = p->rule; !r->complink || !(r->dynamic & D_compiled); r = a)
				if ((!(a = getrule(r->name)) || a == r) && ((r->property & P_state) || !r->uname || !(a = getrule(r->uname)) || a == r))
				{
					if (state.warn)
						error(1, "forcing %s %s prerequisite %s", s, a ? "duplicate" : "dangling", r->name);
					r->dynamic &= ~D_compiled;
					comprule(r->name, (char*)r, h);
					if (!p->rule->complink)
						p->rule->complink = r->complink;
					break;
				}
		} while (p = p->next);
	}
	return 0;
}

/*
 * compile the prerequisite list for r
 */

static int
complist(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;
	register List_t*	p;
	register Compstate_t*	cs = (Compstate_t*)h;

	/*
	 * ignore aliases and rules not set up by comprule()
	 */

	if (!r->complink || !(r->mark & M_compile) || s != r->name && !(r->dynamic & D_alias) || state.stateview == 0 && !(r->property & P_state))
		return 0;
	r->mark &= ~M_compile;
	if (p = r->prereqs)
	{
		do sfputu(cs->fp, p->rule->complink); while (p = p->next);
		sfputu(cs->fp, 0);
	}
	return 0;
}

/*
 * compile an individual variable
 */

static int
compvar(const char* s, char* u, void* h)
{
	register Var_t*		v = (Var_t*)u;
	register Compstate_t*	cs = (Compstate_t*)h;
	char*			t;
	unsigned long		property;
	char*			value;

	/*
	 * compile each variable only once
	 * don't compile command arg variable definitions
	 */

	if ((v->property & V_compiled) || state.stateview < 0 && !(v->property & V_frozen) && ((v->property & V_import) || (v->property & (V_oldvalue|V_readonly)) == V_readonly) || state.stateview == 0 && !(v->property & V_retain))
		return 0;
	v->property |= V_compiled;
	property = v->property;
	value = v->value;

	/*
	 * check for possible old value
	 *
	 * if v->oldvalue is set in load() then the
	 * variable is frozen and the frozen value
	 * is different than the makefile value
	 */

	if (state.stateview == 0)
		property &= ~V_CLEAROBJECT;
	else
	{
		property &= ~V_CLEARSTATE;
		if (property & V_oldvalue)
		{
			if (t = getold(v->name))
			{
				if (!(property & V_frozen))
				{
					value = t;
					property &= ~V_oldvalue;
				}
				else if (streq(value, t))
					property &= ~V_oldvalue;
			}
#if DEBUG
			else
				error(PANIC, "%s->oldvalue set but not in table.oldvalue", s);
#endif
		}
		else if ((property & (V_frozen|V_readonly)) == (V_frozen|V_readonly))
			property |= V_oldvalue;
	}

	/*
	 * write the variable fields
	 */

	sfputu(cs->fp, property);
	compstring(cs, v->name);
	compstring(cs, value);
	cs->variables++;
	return 0;
}

/*
 * clear temporary marks on r
 */

static int
clearmarks(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;

	NoP(s);
	NoP(h);
	r->complink = 0;
	r->mark &= ~M_compile;
	return 0;
}

/*
 * compile the current rules and variables into objfile
 */

void
compile(char* objfile, char* select)
{
	register Sfio_t*	sp;
	List_t*			p;
	List_t*			q;
	Rule_t*			r;
	Compstate_t		cs;

	/*
	 * initialize the object globals
	 */

	zero(cs);
	cs.lists++;
	cs.rules++;

	/*
	 * create the object temporary file
	 */

	sp = sfstropen();
	edit(sp, objfile, KEEP, KEEP, external.tmp);
	state.tmpfile = strdup(sfstruse(sp));
	sfstrclose(sp);
	if (!(cs.fp = sfopen(NiL, state.tmpfile, "brw")))
	{
		error(ERROR_SYSTEM|1, "%s: cannot create temporary object file", state.tmpfile);
		return;
	}

	/*
	 * skip the header until everything else is done
	 */

	if (sfseek(cs.fp, (Sfoff_t)HEADERSIZE, SEEK_SET) != HEADERSIZE)
		error(ERROR_SYSTEM|3, "%s: object file header write error", state.tmpfile);

	/*
	 * write the optional headers
	 */

	if (sp = object.pp)
	{
		object.pp = 0;
		if (!state.base)
		{
			sfputu(sp, COMP_OPTIONS);
			sfputu(sp, 0);
			for (p = internal.preprocess->prereqs; p; p = p->next)
				putstring(sp, p->rule->name, p->next ? ' ' : -1);
			sfputc(sp, 0);
			sfputu(sp, 0);
			sfputu(internal.tmp, HEADER_PREREQS);
			sfputu(cs.fp, sfstrtell(internal.tmp) + sfstrtell(sp));
			sfstrseek(internal.tmp, 0, SEEK_SET);
			sfputu(cs.fp, HEADER_PREREQS);
			sfwrite(cs.fp, sfstrbase(sp), sfstrtell(sp));
		}
		sfstrclose(sp);
	}
	sp = cs.fp;
	sfputu(sp, 0);
	if (sferror(sp))
		error(ERROR_SYSTEM|3, "%s: object file optional header write error", state.tmpfile);

	/*
	 * mark the rules and prerequisites for compilation
	 */

	if (select)
	{
		Sfio_t*	tmp;

		hashwalk(table.rule, 0, compinit, null);
		tmp = sfstropen();
		sfprintf(tmp, "$(<:V:%s)", select);
		r = state.frame->target;
		hashwalk(table.rule, 0, compselect, sfstruse(tmp));
		state.frame->target = r;
		sfstrclose(tmp);
	}
	else
	{
		/*
		 * compile and write the variables
		 */

		hashwalk(table.var, 0, compvar, &cs);
		if (sferror(sp))
			error(ERROR_SYSTEM|3, "%s: object file variable write error", state.tmpfile);
		hashwalk(table.rule, 0, compinit, NiL);
		if (state.stateview == 0)
		{
			/*
			 * check state file garbage collection
			 *
			 * NOTE: only the head of each garbage list is
			 *	 counted so the percentage threshold
			 *	 should probably be low
			 */

			if ((100 * object.garbage / (object.rules ? object.rules : 1) < PCTGARBAGE && !(state.test & 0x00008000)))
				object.garbage = 0;
			hashwalk(table.rule, 0, compstate, &cs);
			if (object.garbage)
			{
				hashwalk(table.rule, 0, clearmarks, &cs);
				hashwalk(table.rule, 0, compkeep, &cs);
			}
#if BINDINDEX
			for (n = 1; n <= state.maxsource; n++)
				markcompile(state.source[n].path);
			for (n = 1; n <= state.maxview; n++)
				markcompile(state.view[n].path);
#endif
		}
	}
	hashwalk(table.rule, 0, compmark, &cs);

	/*
	 * some rules must always be compiled and/or appear first
	 */

	for (p = internal.special->prereqs; p; p = p->next)
		markcompile(p->rule);
	for (p = internal.special->prereqs; p; p = p->next)
		for (q = p->rule->prereqs; q; q = q->next)
			comprule(q->rule->name, (char*)q->rule, &cs);
#if BINDINDEX
	if (state.stateview == 0)
	{
		for (n = 1; n <= state.maxsource; n++)
		{
			x = state.source[n].path;
			comprule(x->name, x);
		}
		for (n = 1; n <= state.maxview; n++)
		{
			x = state.view[n].path;
			comprule(x->name, x);
		}
	}
#endif

	/*
	 * compile and write the rules
	 */

	hashwalk(table.rule, 0, comprule, &cs);
	if (sferror(sp))
		error(ERROR_SYSTEM|3, "%s: object file rule write error", state.tmpfile);

	/*
	 * despite the effort a few elusive prereqs manage to avoid comprule()
	 * the compcheck() pass sets up complink for these prereqs
	 */

	for (p = internal.special->prereqs; p; p = p->next)
		for (q = p->rule->prereqs; q; q = q->next)
			compcheck(q->rule->name, (char*)q->rule, &cs);
	hashwalk(table.rule, 0, compcheck, &cs);

	/*
	 * compile and write the prerequisite lists
	 */

	for (p = internal.special->prereqs; p; p = p->next)
		for (q = p->rule->prereqs; q; q = q->next)
			complist(q->rule->name, (char*)q->rule, &cs);
	hashwalk(table.rule, 0, complist, &cs);
	if (sferror(sp))
		error(ERROR_SYSTEM|3, "%s: object file prerequisite write error", state.tmpfile);

	/*
	 * write the trailer
	 */

	if (state.stateview < 0)
	{
		/*
		 * pre 2004-09-09 will just do "--"
		 */

		putstring(sp, "--", 0);
		if (object.n2a)
		{
			listops(internal.wrk, 'O');
			putstring(sp, sfstruse(internal.wrk), 0);
		}
		else
		{
			listops(sp, 'O');
			sfputc(sp, 0);
		}
	}
	sfputc(sp, 0);

	/*
	 * clear temporary marks
	 */

	hashwalk(table.rule, 0, clearmarks, &cs);

	/*
	 * write the real header
	 */

	sfseek(sp, (Sfoff_t)0, SEEK_SET);
	sfwrite(sp, MAGIC, MAGICSIZE);
	putstring(sp, version, 0);
	sfputu(sp, HEADERSIZE);
	sfputu(sp, SEQUENCE);
	sfputu(sp, 0);
	sfputu(sp, cs.strings);
	sfputu(sp, cs.lists - 1);
	sfputu(sp, cs.rules - 1);
	sfputu(sp, RULENUM - MINRULENUM);
	sfputu(sp, RULESTR - MINRULESTR);
	sfputu(sp, cs.variables);
	sfputu(sp, VARNUM - MINVARNUM);
	sfputu(sp, VARSTR - MINVARSTR);
	sfwrite(sp, MAGIC, MAGICSIZE);
	if (sferror(sp))
		error(ERROR_SYSTEM|3, "%s: temporary object file header write error", state.tmpfile);

	/*
	 * commit to the temporary object and clean up
	 */

	sfclose(sp);
	remove(objfile);
	if (rename(state.tmpfile, objfile))
		error(1, "%s: object file not recompiled", objfile);
	remtmp(0);
	if (state.stateview == 0)
	{
		Stat_t		st;
		Time_t		t;
		Time_t		x;

		/*
		 * set the state file times to the latest
		 * possible event time modulo the file
		 * system time precision
		 */

		t = CURTIME;
		x = tmxsns(tmxsec(t), 999999999);
		if (!tmxtouch(objfile, x, x, TMX_NOTIME, 0) && !stat(objfile, &st))
		{
			t += tmxsns(1,0) - tmxnsec(tmxgetmtime(&st));
			tmxtouch(objfile, t, t, TMX_NOTIME, 0);
		}
	
	}
	r = bindfile(NiL, objfile, BIND_FORCE|BIND_DOT|BIND_RULE);
	r->dynamic |= D_built|D_regular;
	r->view = 0;
	if (state.mam.dynamic || state.mam.regress)
	{
		mampush(state.mam.out, r, P_force);
		sfprintf(state.mam.out, "%sexec %s : compile into %s object\n", state.mam.label, state.mam.dynamic ? mamname(r) : null, error_info.id);
		mampop(state.mam.out, r, 0);
	}
	if (state.stateview == 0 && object.garbage && object.garbage > cs.rules)
		message((-1, "%d%% [%d/%d] state file garbage collection recovery", (object.garbage - cs.rules) * 100 / object.garbage, object.garbage - cs.rules, object.garbage));
}

/*
 * register input file prerequisite
 */

void
compref(Rule_t* r, int type)
{
	if (object.pp)
	{
		if (r)
		{
			/*
			 * COMP_NSEC for subsecond granularity
			 * and bind checks
			 * ignored by old implementations
			 */

			sfputu(object.pp, COMP_NSEC);
			sfputu(object.pp, tmxnsec(r->time));
			putstring(object.pp, r->name, 0);
			sfputu(object.pp, type);
			sfputu(object.pp, tmxsec(r->time));
			putstring(object.pp, unbound(r), 0);
		}
		else
		{
			sfstrclose(object.pp);
			object.pp = 0;
		}
	}
	else if (!r && !state.makefile)
		object.pp = sfstropen();
}

/*
 * promote lower view prereqs of top view state
 */

static int
promote(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;
	register List_t*	p;

	NoP(s);
	NoP(h);
	if (r->mark & M_compile)
	{
		r->mark &= ~M_compile;
		for (p = r->prereqs; p; p = p->next)
		{
			r = p->rule;
			if (r->dynamic & D_lower)
			{
				unviewname(r->name);
				p->rule = makerule(r->name);
				viewname(r->name, r->view);
			}
		}
	}
	return 0;
}

/*
 * associate one rule with each name
 */

static int
atomize(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;
	register List_t*	p;

	NoP(s);
	NoP(h);
	if (isoldrule(r))
	{
#if DEBUG
		error(PANIC, "old rule %s still in table.rule%s", r->name, r == getrule(r->name) ? null : " -- duplicate hash");
#endif
		return 0;
	}
	for (p = r->prereqs; p; p = p->next)
		if (isoldrule(p->rule))
			p->rule = getoldrule(p->rule);
	return 0;
}

/*
 * repair prereq list corruption
 */

static int
repair(const char* s, char* v, void* h)
{
	register Rule_t*	r = (Rule_t*)v;
	register List_t*	p;
	register List_t*	q;

	NoP(s);
	NoP(h);
	p = 0;
	q = r->prereqs;
	while (q)
		if (q->rule)
		{
			p = q;
			q = q->next;
		}
		else if (p)
			p->next = q = q->next;
		else
			r->prereqs = q = q->next;
	return 0;
}

/*
 * return the object file name
 * assumes the main makefile has already been read
 */

char*
objectfile(void)
{
	char*		dir;
	Sfio_t*		sp;
	Stat_t		st;

	if (!state.objectfile && state.makefile && state.writeobject)
	{
		sp = sfstropen();
		dir = DELETE;
		if (streq(state.writeobject, "-") || !stat(state.writeobject, &st) && S_ISDIR(st.st_mode) && (dir = state.writeobject))
			edit(sp, state.makefile, dir, KEEP, external.object);
		else
			expand(sp, state.writeobject);
		state.objectfile = strdup(sfstruse(sp));
		sfstrclose(sp);
	}
	return state.objectfile;
}

/*
 * remove temporary compilation files
 */

void
remtmp(int fatal)
{
	if (fatal && state.stateview == 0 && state.tmpfile)
		error(2, "%s: state file not updated", statefile());
	if (state.tmpfile)
	{
		if (remove(state.tmpfile) && errno != ENOENT)
			error(ERROR_SYSTEM|1, "%s: temporary file not removed", state.tmpfile);
		free(state.tmpfile);
		state.tmpfile = 0;
	}
	if (fatal)
		lockstate(0);
}

/*
 * load a string
 */

static char*
loadstring(Loadstate_t* ls, Sfio_t* sp)
{
	register int	n;
	register char*	s;

	if (!(n = sfgetu(sp)) || sfeof(sp))
		return 0;
	s = ls->sp;
	ls->sp += n--;
	sfread(sp, s, n);
	ccmapstr(object.a2n, s, n);
	return s;
}

/*
 * initialize object state
 */

static void
loadinit(void)
{
	object.garbage = object.rules = 0;
}

/*
 * check if file is loadable object
 * if source!=0 then source prereqs are checked
 */

int
loadable(register Sfio_t* sp, register Rule_t* r, int source)
{
	register List_t*	p;
	char*			s;
	char*			u;
	char*			sn;
	long			n;
	Sfoff_t			off;
	Time_t			t;
	Time_t			tm;
	Time_t			tn;
	int			lowres;
	int			ok = 1;
	long			old = 0;
	Rule_t*			x;
	Stat_t			st;

	loadinit();
	if ((s = sfreserve(sp, 0, 0)) && (n = sfvalue(sp)) >= 0)
	{
		if (n >= MAGICSIZE && !memcmp(s, MAGIC, MAGICSIZE) || n >= sizeof(old) && ((old = OLD_MAGIC, swapop(s, &old, sizeof(old)) >= 0) || (old = OLD_OLD_MAGIC, swapop(s, &old, sizeof(old)) >= 0)))
		{
			if (!source)
				return state.base || !state.forceread || state.global || state.list;

			/*
			 * check previous source prerequisites
			 */

			if (!old && !sfseek(sp, (Sfoff_t)0, SEEK_SET) && sfseek(sp, (Sfoff_t)MAGICSIZE, SEEK_SET) == MAGICSIZE)
			{
				if (getstring(sp) && (off = sfgetu(sp)) && sfgetu(sp))
					while (!sfeof(sp) && sfseek(sp, off, SEEK_SET) == off && (off = sfgetu(sp)))
					{
						off += sfseek(sp, (Sfoff_t)0, SEEK_CUR);
						if (sfgetu(sp) == HEADER_PREREQS)
						{
							/*UNDENT...*/

	state.init++;
	lowres = 1;
	sn = 0;
	tn = 0;
	while (n = sfgetu(sp))
	{
		tm = sfgetu(sp);
		if (!(s = getstring(sp)))
			break;
		if (n & (COMP_BASE|COMP_FILE|COMP_GLOBAL|COMP_INCLUDE))
		{
			if (x = bindfile(NiL, s, BIND_MAKEFILE|BIND_RULE))
			{
				s = x->name;
				t = x->time;

				/*
				 * put bound makefile prereqs in state as a query courtesy
				 */

				if (!(n & COMP_BASE) && *x->name != '/')
					staterule(RULE, x, NiL, 1)->time = t;
				if (!(x->dynamic & D_regular))
					x->dynamic &= ~D_bound;
			}
			else
				t = 0;
			if (!t && sn && !stat(sn, &st))
				t = tmxgetmtime(&st);
			if (!t)
			{
				if (n & COMP_DONTCARE)
					continue;
				error(state.exec || state.mam.out ? -1 : 1, "%s: %s not found", r->name, s);
				break;
			}
			if (tn && t == tmxsns(tm, 0))
				tn = 0;
			tm = (lowres && tm == tmxsec(t)) ? t : tmxsns(tm, tn);

			/*
			 * check prerequisite file time with previous
			 */

			debug((-4, "%s%s%s%s%sprerequisite %s [%s] state [%s]", (n & COMP_DONTCARE) ? "optional " : null, (n & COMP_BASE) ? "base " : null, (n & COMP_FILE) ? "-f " : null, (n & COMP_GLOBAL) ? "-g " : null, (n & COMP_INCLUDE) ? "include " : null, s, timestr(t), timestr(tm)));
			if (t != tm)
			{
				if (sn && !streq(sn, x->name))
					error(state.exec || state.mam.out ? -1 : 1, "%s: binding changed to %s from %s", s, x->name, sn);
				else
					error(state.exec || state.mam.out ? -1 : 1, "%s: out of date with %s", r->name, s);
				break;
			}
			sn = 0;
			tn = 0;

			/*
			 * check that explicit prerequisite still specified
			 */

			if (n & (COMP_FILE|COMP_GLOBAL))
			{
				for (p = ((n & COMP_FILE) ? internal.makefiles : internal.globalfiles)->prereqs; p; p = p->next)
					if (!(p->rule->mark & M_compile))
					{
						if (streq(p->rule->name, s) || p->rule->uname && streq(p->rule->uname, s))
							p->rule->mark |= M_compile;
						else
						{
							error(state.exec || state.mam.out ? -1 : 1, "%s: %sfile %s option order changed", r->name, (n & COMP_GLOBAL) ? "global " : null, s);
							goto nope;
						}
						break;
					}
				if (!p)
				{
					error(state.exec || state.mam.out ? -1 : 1, "%s: %sfile %s was specified last time", r->name, (n & COMP_GLOBAL) ? "global " : null, s);
					break;
				}
			}
			else if ((n & COMP_BASE) && !state.rules)
			{
				if (n & COMP_RULES)
					state.explicitrules = 1;
				state.rules = makerule(s)->name;
			}
		}
		else if (n & COMP_NSEC)
		{
			if (*s)
			{
				sfputr(internal.met, s, -1);
				sn = sfstruse(internal.met);
			}
			tn = tm;
			lowres = 0;
		}
		else if (n & COMP_OPTIONS)
		{
			/*
			 * compare with current preprocessor options
			 */

			for (p = internal.preprocess->prereqs; p; p = p->next)
				sfputr(internal.nam, p->rule->name, p->next ? ' ' : -1);
			u = sfstruse(internal.nam);
			if (!streq(s, u))
			{
				error(state.exec || state.mam.out ? -1 : 1, "%s: options changed%s%s", r->name, *s ? " from " : null, s);
				break;
			}
		}
	}
 nope:
	state.init--;
	if (n)
		ok = 0;

	/*
	 * check for explicit file prereqs not specified last time
	 */

	for (p = internal.globalfiles->prereqs; p; p = p->next)
		if (p->rule->mark & M_compile)
			p->rule->mark &= ~M_compile;
		else if (ok)
		{
			ok = 0;
			if (state.writeobject)
				error(state.exec || state.mam.out ? -1 : 1, "%s: global file %s not specified last time", r->name, p->rule->name);
		}
	for (p = internal.makefiles->prereqs; p; p = p->next)
		if (p->rule->mark & M_compile)
			p->rule->mark &= ~M_compile;
		else if (ok)
		{
			ok = 0;
			if (state.writeobject)
				error(state.exec || state.mam.out ? -1 : 1, "%s: file %s not specified last time", r->name, p->rule->name);
		}
	if (ok && !sfseek(sp, (Sfoff_t)0, SEEK_SET))
		return 1;

							/*...INDENT*/
							break;
						}
					}
				sfseek(sp, (Sfoff_t)0, SEEK_SET);
			}
		}
	}
	return 0;
}

/*
 * load compiled rules and variables from objfile
 * return:
 *	-1	partially loaded => punt
 *	 0	not loaded
 *	 1	loaded
 */

int
load(register Sfio_t* sp, const char* objfile, int ucheck)
{
	register int		n;
	register Rule_t*	r;
	register Var_t*		v;
	register List_t*	d;
	register char*		s;
	char*			p = 0;
	int			promoted = 0;
	int			recompile = 0;
	char*			corrupt = "2005-03-01";
	Rule_t*			oldrules = 0;
	Rule_t*			or;
	Rule_t*			xr;
	Var_t*			ov;
	Var_t*			xv;
	Var_t*			x;
	List_t*			xd;
	List_t*			a;
	int			flags;
	int			strings;
	int			lists;
	int			rules;
	int			rulenum;
	int			rulestr;
	int			variables;
	int			varnum;
	int			varstr;
	int			attrclash;
	int			garbage;
	int			oscan;
	int			scanclash;
	int			sequence;
	int			lowres;
	unsigned long		attr;
	unsigned long		attrclear;
	unsigned long		oattribute;
	unsigned long		ts;
	unsigned long		tn;
	Frame_t*		fp;
	Sfoff_t			off;
	Stat_t			st;
	unsigned long		attrmap[CHAR_BIT * sizeof(unsigned long)];
	unsigned char		scanmap[UCHAR_MAX + 1];
	char			ident[64];
	Loadstate_t		ls;

	int			old;
	int			old_swap;
	long			old_magic;
	OLD_rule_t		old_rule;
	OLD_var_t		old_var;
	OLD_list_t		old_list;
	OLD_header_t		old_header;
	OLD_trailer_t		old_trailer;

	loadinit();
	zero(ls);

	/*
	 * empty object files are ok
	 */

	if (fstat(sffileno(sp), &st))
	{
		error(1, "%s: cannot stat object file", objfile);
		return 0;
	}
	if (!st.st_size)
	{
		if (state.stateview >= 0)
		{
			error(1, "%s: empty state file", objfile);
			return 0;
		}
		return 1;
	}

	/*
	 * check for other users within last ucheck minutes
	 */

	if (ucheck && st.st_uid != geteuid() && (n = CURSECS - st.st_mtime) < ucheck * 60)
		error(1, "%s: another user was here %s ago", state.makefile, fmtelapsed(n, 1));

	/*
	 * check the header
	 */

	if (!(s = sfreserve(sp, MAGICSIZE, 0)))
		goto badmagic;
	errno = 0;
	if (memcmp(s, MAGIC, MAGICSIZE) || !(s = getstring(sp)) || streq(s, OLD_VERSION))
	{
		old = 1;
		if (sfseek(sp, (Sfoff_t)0, SEEK_SET) ||
		    sfread(sp, (char*)&old_header, sizeof(old_header)) != sizeof(old_header) ||
		    sfseek(sp, -(Sfoff_t)sizeof(old_trailer), SEEK_END) == -1 ||
		    sfread(sp, (char*)&old_trailer, sizeof(old_trailer)) != sizeof(old_trailer))
			goto badio;
		if ((old_magic = OLD_MAGIC, (old_swap = swapop(&old_header.magic, &old_magic, sizeof(old_magic)))) < 0 && (old_magic = OLD_OLD_MAGIC, (old_swap = swapop(&old_header.magic, &old_magic, sizeof(old_magic)))) < 0)
			goto badmagic;
		sequence = old_header.sequence;
		old_header.sequence = old_stamp.sequence;
		if (old_swap)
		{
			swapmem(old_swap, &old_header, &old_header, sizeof(old_header));
			swapmem(old_swap, &old_trailer, &old_trailer, sizeof(old_trailer));
		}
		if ((st.st_size - sizeof(old_trailer)) & (OLD_ALIGN - 1))
			goto badversion;
		strcpy(old_stamp.version, OLD_VERSION);
		if (memcmp(((char*)&old_header) + sizeof(old_header.magic), ((char*)&old_stamp) + sizeof(old_stamp.magic), sizeof(old_header) - sizeof(old_header.magic)))
		{
			strcpy(old_stamp.version, OLD_VERSION_2);
			if (memcmp(((char*)&old_header) + sizeof(old_header.magic), ((char*)&old_stamp) + sizeof(old_stamp.magic), sizeof(old_header) - sizeof(old_header.magic)))
				goto badversion;
			sequence = 2;
		}
		if (s = strrchr(old_stamp.version, ' '))
			s++;
		else
			s = old_stamp.version;
		strncopy(ident, s, sizeof(ident));
		if (old_trailer.magic != old_header.magic || old_trailer.size != st.st_size)
			goto badmagic;
		if (state.exec && streq(objfile, state.objectfile))
			return 0;
		lists = old_trailer.lists;
		rules = old_trailer.rules;
		variables = old_trailer.variables;
		off = sizeof(old_header) + rules * sizeof(old_rule) + lists * sizeof(old_list) + variables * sizeof(old_var);
		strings = st.st_size - sizeof(old_trailer) - off;
		if (sfeof(sp) || sfseek(sp, off, SEEK_SET) != off)
			goto badio;
		lowres = state.stateview >= 0;
	}
	else
	{
		old = 0;
		if (s = strrchr(s, ' '))
			s++;
		else
			s = "old";
		strncopy(ident, s, sizeof(ident));
		off = sfgetu(sp);
		sequence = sfgetu(sp);
		flags = sfgetu(sp);
		NoP(flags);
		strings = sfgetu(sp);
		lists = sfgetu(sp);
		rules = sfgetu(sp);
		lowres = (rulenum = sfgetu(sp)) < 2 && state.stateview >= 0;
		rulestr = sfgetu(sp);
		variables = sfgetu(sp);
		varnum = sfgetu(sp);
		varstr = sfgetu(sp);
		if (!(s = sfreserve(sp, MAGICSIZE, 0)) || memcmp(s, MAGIC, MAGICSIZE))
			goto badmagic;

		/*
		 * read the optional headers
		 */

		for (;;)
		{
			if (sfeof(sp) || sfseek(sp, off, SEEK_SET) != off)
				goto badio;
			if (!sequence || !(off = sfgetu(sp)))
				break;
			off += sfseek(sp, (Sfoff_t)0, SEEK_CUR);
		}
	}
	message((-3, "%s sequence=%d lists=%d rules=%d variables=%d strings=%d", ident, sequence, lists, rules, variables, strings));
	if (lowres && !object.lowres)
	{
		object.lowres = 1;
		if (!state.silent)
			error(1, "%s: low time resolution state file -- subsecond differences ignored", objfile);
	}

	/*
	 * allocate strings and structs in one chunk
	 * and compute pointers to the compiled data
	 */

	if (!(p = newof(0, char, lists * sizeof(List_t) + rules * sizeof(Rule_t) + variables * sizeof(Var_t) + strings, 0)))
	{
		error(3, "out of space");
		goto bad;
	}
	r = or = (Rule_t*)p;
	d = (List_t*)((char*)r + rules * sizeof(Rule_t));
	v = ov = (Var_t*)((char*)d + lists * sizeof(List_t));
	s = ((char*)v + variables * sizeof(Var_t));

	/*
	 * read the string table
	 */

	if (!old)
		ls.sp = s;
	else if (sfread(sp, s, strings) != strings)
		goto badio;

	/*
	 * load the variables and check for any frozen
	 * variables that may have changed
	 */

	if (old)
	{
		off = sizeof(old_header) + rules * sizeof(old_rule) + lists * sizeof(old_list);
		if (sfseek(sp, off, SEEK_SET) != off)
			goto badio;
	}
	oattribute = internal.attribute->attribute;
	oscan = internal.scan->scan;
	attrclash = scanclash = 0;
	for (xv = v + variables; v < xv; v++)
	{
		if (old)
		{
			if (sfread(sp, (char*)&old_var, sizeof(old_var)) != sizeof(old_var))
				goto badio;
			if (old_swap)
				swapmem(old_swap, &old_var, &old_var, sizeof(old_var));
			v->property = old_var.property;
			if (old_var.name)
				v->name = s + (unsigned long)old_var.name - 1;
			if (old_var.value)
				v->value = s + (unsigned long)old_var.value - 1;
			switch (sequence)
			{
			case 2: /* 01/24/89 */
				v->property = (v->property & 0x000003ffL);
				break;
			}
		}
		else
		{
			v->property = sfgetu(sp);
#if !_HUH_1993_10_01 /* drop this eventually */
			v->property &= ~V_free;
#endif

			/*
			 * variable number field additions here
			 */

			for (n = varnum; n > 0; n--)
				sfgetu(sp);
			v->name = loadstring(&ls, sp);
			v->value = loadstring(&ls, sp);

			/*
			 * variable string field additions here
			 */

			for (n = varstr; n > 0; n--)
				loadstring(&ls, sp);
		}
		if ((state.exec || !state.base || state.compileonly) && (v->property & V_frozen) && (!(x = getvar(v->name)) && ((v->property & V_oldvalue) || (v->property & V_import) && *v->value) || x && ((x->property & (V_append|V_readonly)) == (V_append|V_readonly) || ((v->property|x->property) & (V_import|V_readonly)) && !streq(v->value, x->value)) || (v->property & V_functional)))
		{
			error((state.exec || state.mam.out) && !state.explain ? -1 : 1, "%s: frozen %svariable %s changed", objfile, ((v->property|(x ? x->property : 0)) & V_import) ? "environment " : ((x ? x->property : 0) & V_readonly) ? "command argument " : null, v->name);
			recompile = 1;
			v->property &= ~V_readonly;
		}
		else
			v->property &= ~(V_oldvalue|V_readonly);
	}
	if (sfeof(sp))
		goto badio;
	if (recompile)
	{
		recompile = 0;
		goto bad;
	}
	recompile = -1;
	p = 0;
	v = ov;

	/*
	 * enter the variables
	 */

	hashclear(table.var, HASH_ALLOCATE);
	for (xv = v + variables; v < xv; v++)
	{
		if (!(x = getvar(v->name)) || !(x->property & (V_readonly|V_restored)) && (!(x->property & V_import) || !state.global))
		{
			putvar(v->name, v);
			if (x)
				freevar(x);
		}
		else
		{
			if ((x->property & (V_append|V_readonly)) == (V_append|V_readonly))
			{
				n = state.reading;
				state.reading = 1;
				setvar(x->name, v->value, 0);
				state.reading = n;
			}
			x->property |= v->property & (V_functional|V_scan);
		}
		if ((v->property & V_retain) && state.stateview >= 0)
			v->property |= V_restored;
	}
	hashset(table.var, HASH_ALLOCATE);

#if BINDINDEX
	/*
	 * initialize the bind index maps
	 */

	for (n = 0; n <= state.maxsource; n++)
		state.source[i].map = 0;
	for (n = 0; n <= state.maxview; n++)
		state.view[i].map = 0;
	state.view[0].map = state.stateview;
#endif

	/*
	 * load and enter the rules
	 */

	if (old)
	{
		off = sizeof(old_header);
		if (sfseek(sp, off, SEEK_SET) != off)
			goto badio;
	}
	garbage = 0;
	hashclear(table.rule, HASH_ALLOCATE);
	for (xr = r + rules; r < xr; r++)
	{
		register Rule_t*	o;

		if (old)
		{
			if (sfread(sp, (char*)&old_rule, sizeof(old_rule)) != sizeof(old_rule))
				goto badio;
			if (old_swap)
				swapmem(old_swap, &old_rule, &old_rule, (char*)&old_rule.noswap - (char*)&old_rule);
			r->property = old_rule.property;
			r->dynamic = old_rule.dynamic;
			r->attribute = old_rule.attribute;
			switch (sequence)
			{
			case 2: /* 01/24/89 */
				r->property =
					((r->property & 0x0000ffffL)) |
					((r->property & 0x3ffb0000L) << 1);
				r->dynamic =
					((r->dynamic & 0x000003ffL)) |
					((r->dynamic & 0x00000800L) << 1) |
					((r->dynamic & 0x00034000L) << 2) |
					((r->dynamic & 0x00040000L) << 4);
				break;
			}
			if (old_rule.name)
				r->name = s + (unsigned long)old_rule.name - 1;
			if (r->property & P_staterule)
			{
				r->dynamic |= D_lowres;
				r->event = old_rule.old_event;
			}
			else if (old_rule.old_data)
				r->statedata = s + (unsigned long)old_rule.old_data - 1;
			if (old_rule.prereqs)
				r->prereqs = d + (unsigned long)old_rule.prereqs / sizeof(old_list) - 1;
			if (old_rule.action)
				r->action = s + (unsigned long)old_rule.action - 1;
			r->scan = old_rule.scan;
			r->semaphore = old_rule.semaphore;
			r->view = old_rule.view;
			r->time = tmxsns(old_rule.time, 0);
			switch (sequence)
			{
			case 0: /* 1989-09-11 */
				if ((r->property & P_attribute) && (r->attribute && !(r->property & P_use) && !streq(r->name, internal.attribute->name) || r->scan && !streq(r->name, internal.scan->name)))
					r->dynamic |= D_index;
				/*FALLTHROUGH*/
			case 1: /* 1991-07-17 */
				if (r->property & P_staterule)
				{
					if (isaltstate(r->name))
					{
						r->property |= P_implicit;
						r->time = 0;
					}
					r->event = r->time;
				}
				/*FALLTHROUGH*/
			}
		}
		else
		{
			r->property = sfgetu(sp);
			r->dynamic = sfgetu(sp);
			r->attribute = sfgetu(sp);
			if (n = sfgetu(sp))
			{
				r->semaphore = (n>>16) & ((1<<8)-1);
				r->view = (n>>8) & ((1<<8)-1);
				r->scan = (n) & ((1<<8)-1);
			}
			if (n = sfgetu(sp))
				r->prereqs = d + n - 1;
			ts = sfgetu(sp);

			/*
			 * 2004-12-01
			 */

			if (n = rulenum)
			{
				n--;
				tn = sfgetu(sp);
			}
			else
				tn = 0;
			r->time = tmxsns(ts, tn);

			/*
			 * 2004-12-01
			 */

			if ((r->property & P_staterule) && n)
			{
				n--;
				tn = sfgetu(sp);
			}
			else
				tn = 0;

			/*
			 * rule number field additions here
			 */

			while (n--)
				sfgetu(sp);
			r->name = loadstring(&ls, sp);
			r->action = loadstring(&ls, sp);
			if (r->property & P_staterule)
			{
				ts = sfgetu(sp);
				r->event = tmxsns(ts, tn);
				if (lowres)
					r->dynamic |= D_lowres;
			}
			else
				r->statedata = loadstring(&ls, sp);

			/*
			 * rule string field additions here
			 */

			for (n = rulestr; n > 0; n--)
				loadstring(&ls, sp);
		}
		if (sfeof(sp))
			goto badio;
		r->preview = state.maxview + 1;
		o = getrule(r->name);
		if (r->dynamic & D_index)
		{
			/*
			 * check for index atom consistency
			 * remap inconsistent state file atoms
			 */

			if (r->scan)
			{
				attr = ~0;
				if (o && (o->property & P_attribute) && o->scan)
				{
					if (r->scan != o->scan)
					{
						error((state.exec || state.mam.out) && !state.explain ? -1 : 1, "%s: %s %s definition changed", objfile, r->name, internal.scan->name);
						if (state.stateview < 0)
							return -1;
						attr = o->scan;
					}
				}
				else
				{
					for (a = internal.scan->prereqs; a; a = a->next)
						if (r->scan == a->rule->scan)
						{
							error((state.exec || state.mam.out) && !state.explain ? -1 : 1, "%s: %s %s definition clashes with %s", objfile, r->name, internal.scan->name, a->rule->name);
							if (state.stateview < 0)
								return -1;
							attr = 0;
							break;
						}
					if (state.stateview >= 0)
						attr = 0;
				}
				if (attr != ~0)
				{
					if (!scanclash)
					{
						scanclash = 1;
						for (n = 0; n < elementsof(scanmap); n++)
							scanmap[n] = n;
					}
					if (!(r->scan = scanmap[r->scan] = attr))
						r->property &= ~P_attribute;
					if (o)
						continue;
				}
			}
			else if (r->attribute)
			{
				attr = ~0;
				if (o && (o->property & P_attribute) && o->attribute)
				{
					if (r->attribute != o->attribute)
					{
						error((state.exec || state.mam.out) && !state.explain ? -1 : 1, "%s: %s %s definition changed", objfile, r->name, internal.attribute->name);
						if (state.stateview < 0)
							return -1;
						attr = o->attribute;
					}
				}
				else
					for (a = internal.attribute->prereqs; a; a = a->next)
						if (r->attribute == a->rule->attribute)
						{
							error((state.exec || state.mam.out) && !state.explain ? -1 : 1, "%s: %s %s definition clashes with %s", objfile, r->name, internal.attribute->name, a->rule->name);
							if (state.stateview < 0)
								return -1;
							attr = 0;
						}
				if (attr != ~0)
				{
					if (!attrclash)
					{
						attrclash = 1;
						attrclear = 0;
						for (n = 0; n < CHAR_BIT * sizeof(unsigned long); n++)
							attrmap[n] = (1<<n);
					}
					attrclear |= r->attribute;
					for (n = 0; n < CHAR_BIT * sizeof(unsigned long); n++)
						if (r->attribute == (1<<n))
						{
							attrmap[n] = attr;
							break;
						}
					continue;
				}
			}
		}
		else
		{
			if (attrclash)
			{
				attr = r->attribute & ~attrclear;
				for (n = 0; n < CHAR_BIT * sizeof(unsigned long); n++)
					if (r->attribute & (1<<n))
						attr |= attrmap[n];
				r->attribute = attr;
			}
			if (scanclash)
				r->scan = scanmap[r->scan];
		}
		if (r->dynamic & D_compiled)
		{
			n = state.stateview > 0 && viewable(r);
			if (n && (o || (state.questionable & 0x00000020)))
			{
				r->dynamic |= D_compiled|D_lower;
				viewname(r->name, state.stateview);
				r->view = state.stateview;
				maprule(r->name, r);
				if (o && (r->property & P_statevar) && !o->time)
					o->time = r->time;
			}
			else
			{
				if (n)
				{
					r->mark |= M_compile;
					promoted = 1;
				}
				if (!o)
				{
					if (state.stateview >= 0 && (r->property & (P_joint|P_target)) == P_target)
					{
						r->dynamic |= D_garbage;
						garbage++;
					}
				}
				else if (state.stateview >= 0 && (r->property & P_statevar) && (r->prereqs || r->action))
				{
					/*
					 * ignore state file prereqs and action
					 */

					if (o->action && r->action && !streq(o->action, r->action))
						r->time = CURTIME;
					o->time = r->time;
					o->statedata = r->statedata;
					setoldrule(r, o);
					continue;
				}
				else if (state.stateview >= 0 && (r->property & P_staterule) && o->event > r->event)
				{
					/*
					 * o was updated after r was saved 
					 */

					setoldrule(r, o);
					continue;
				}
				else if (!isoldrule(o))
				{
					if (o->dynamic & (D_bound|D_scanned))
					{
						r->statedata = o->statedata;
						r->time = o->time;
						r->status = o->status;
						r->view = o->view;
						r->property &= ~(P_parameter|P_state|P_staterule|P_statevar);
						r->property |= o->property & (P_parameter|P_state|P_staterule|P_statevar);
						r->dynamic &= ~(D_bound|D_entries|D_global|D_regular|D_scanned);
						r->dynamic |= o->dynamic & (D_bound|D_entries|D_global|D_regular|D_scanned);
						if (!(o->property & P_state) && o->uname)
						{
							r->uname = maprule(o->uname, r);
							getrule(o->name);
						}
					}
					else if ((r->property & (P_parameter|P_statevar)) == P_statevar && (o->property & (P_parameter|P_statevar)) == (P_parameter|P_statevar))
						r->property |= P_parameter;
					setoldrule(o, r);
				}
				r->name = putrule(0, r);
			}
		}
		else if (o)
		{
			/*
			 * r is just a reference -- keep the old rule
			 * but retain some attributes
			 */

			o->attribute |= r->attribute & internal.retain->attribute;
			o->property |= r->property & (P_dontcare|P_ignore|P_terminal);
			setoldrule(r, o);
		}
		else
		{
			r->name = putrule(0, r);
			if (!(r->property & P_state))
				r->time = 0;
			if (state.stateview >= 0)
			{
				if ((r->property & (P_joint|P_target)) == P_target)
				{
					r->dynamic |= D_garbage;
					garbage++;
				}

				/*
				 * clear reference secondary attributes
				 */

				r->attribute &= internal.retain->attribute;
				r->property &= (P_attribute|P_dontcare|P_ignore|P_parameter|P_state|P_staterule|P_statevar|P_terminal);
				r->dynamic &= D_garbage;
			}
		}
#if BINDINDEX
		if (r->dynamic & D_bindindex)
		{
			if (o)
			{
				if (o->dynamic & D_bindindex)
				{
					if (r->source)
					{
						state.source[r->source].map = o->source;
						if (isoldrule(o))
							state.source[o->source].path = r;
					}
					else if (r->view)
					{
						state.view[r->view].map = o->view;
						if (isoldrule(o))
							state.view[o->view].path = r;
					}
				}
				else if (r->source)
				{
					if (++state.maxsource >= elementsof(state.source))
						error(3, "%s: too many %s directories -- %d max", r->name, internal.source->name, elementsof(state.source));
					state.source[r->source].map = state.maxsource;
					if (isoldrule(o))
						state.source[state.maxsource].path = r;
					else
					{
						o->dynamic |= D_bindindex;
						o->source = state.maxsource;
						state.source[o->source].path = o;
					}
				}
			}
			else if (r->source)
			{
				if (++state.maxsource >= elementsof(state.source))
					error(3, "%s: too many %s directories -- %d max", r->name, internal.source->name, elementsof(state.source));
				state.source[r->source].map = state.maxsource;
				state.source[state.maxsource].path = r;
			}
		}
		r->source = state.source[r->source].map;
		r->view = state.view[r->view].map;
#endif
	}
	hashset(table.rule, HASH_ALLOCATE);
	r = or;

	/*
	 * load the prerequisite lists
	 */

	if (old)
	{
		off = sizeof(old_header) + rules * sizeof(old_rule);
		if (sfseek(sp, off, SEEK_SET) != off)
			goto badio;
	}
	if (lists)
	{
		for (xd = d + lists; d < xd;)
		{
			if (old)
			{
				if (sfread(sp, (char*)&old_list, sizeof(old_list)) != sizeof(old_list))
					goto badio;
				if (old_swap)
					swapmem(old_swap, &old_list, &old_list, sizeof(old_list));
				if (old_list.rule)
					d->rule = r + (unsigned long)old_list.rule / sizeof(old_rule) - 1;
				if (old_list.next)
					d->next = d + 1;
				d++;
			}
			else if (!(n = sfgetu(sp)))
				(d - 1)->next = 0;
			else if (n < 0)
			{
				if (strcmp(ident, corrupt) < 0 && (((lists - (xd - d)) * 100) / lists) >= 90)
				{
					while (d < xd)
					{
						(d - 1)->next = 0;
						d++;
					}
					hashwalk(table.rule, 0, repair, NiL);
					error(1, "%s: pre-%s make object corruption repaired", objfile, corrupt);
					corrupt = 0;
				}
				break;
			}
			else
			{
				d->rule = r + n - 1;
				d->next = d + 1;
				d++;
			}
		}
		if (!old)
		{
			sfgetu(sp);
			(d - 1)->next = 0;
		}
	}
	if (sfeof(sp) && corrupt)
		goto badio;

	/*
	 * collect state file garbage collection stats
	 */

	if (state.stateview >= 0)
	{
		Time_t	t;
		Time_t	q;

		object.garbage += garbage;
		object.rules += rules;

		/*
		 * handle low time resolution by making
		 * sure the current time is at least as
		 * recent as the state file time, previously
		 * set in compile() to take into account
		 * the file system time precision
		 */

		t = tmxgetmtime(&st);
		if (state.tolerance)
			t += tmxsns(state.tolerance, 0);
		q = CURTIME;
		if (q >= t)
			t = 0;
		else if ((t -= q) > tmxsns(5,0))
			t = tmxsns(5,0);
		if (t)
		{
			error(state.tolerance ? 1 : -1, "%s: state time sync delay %lu.%09lu", objfile, tmxsec(t), tmxnsec(t));
			tmxsleep(t);
		}
	}

	/*
	 * readjust the internal rule pointers
	 */

	initrule();

	/*
	 * make sure top view state has top view prereqs
	 */

	if (promoted)
		hashwalk(table.rule, 0, promote, NiL);

	/*
	 * associate one rule with each name
	 */
	
	if (oldrules)
	{
		hashwalk(table.rule, 0, atomize, NiL);
		fp = state.frame;
		for (;;)
		{
			if (isoldrule(fp->target))
			{
				fp->target = getoldrule(fp->target);
				fp->target->active = fp;
			}
			if (fp == fp->parent)
				break;
			fp = fp->parent;
		}
		do
		{
			xr = (Rule_t*)oldrules->prereqs;
			freerule(oldrules);
		} while (oldrules = xr);
	}

	/*
	 * check special indices
	 */

	if (internal.attribute->attribute == 1)
		internal.attribute->attribute = oattribute;
	if (internal.scan->scan == SCAN_USER)
		internal.scan->scan = oscan;

	/*
	 * reset compiled options
	 */

	if (!state.list)
	{
		state.loading = (char*)objfile;
		if (old)
		{
			if (old_trailer.options)
				set(s + (unsigned long)old_trailer.options - 1, 1, NiL);
		}
		else if ((s = getstring(sp)) && *s && (!streq(s, "--") || (s = getstring(sp)) && *s))
			set(s, 1, NiL);
		if (state.stateview < 0)
		{
			/*
			 * check for load time actions
			 *
			 * state.global++ enables setvar() V_import override
			 */

			if (state.global)
				state.global++;
			for (xr = r + rules; r < xr; r++)
				if ((r->property & (P_immediate|P_target)) == (P_immediate|P_target))
					immediate(r);
			if (state.global)
				state.global--;
		}
		state.loading = 0;
	}
	return 1;
 badversion:
	if (strncmp(old_header.version, old_stamp.version, sizeof(old_header.version)))
	{
		/*
		 * old old versions were only numbers
		 */

		if (!isalpha(*old_header.version))
			sfsprintf(old_header.version, sizeof(old_header.version), "%d", *((long*)old_header.version));
		error(1, "%s: old format (%s) incompatible with make loader (%s)", objfile, old_header.version, old_stamp.version);
	}
	else
		error(1, "%s: old format (%s) generated on an incompatible architecture", objfile, old_header.version);
	return 0;
 badio:
	error(ERROR_SYSTEM|2, "%s: object file io error", objfile);
	goto bad;
 badmagic:
	error(1, "%s: not a %s object file", objfile, version);
 bad:
	if (p)
		free(p);
	return recompile;
}
