#include "mical.h"

static char *sdi = "~|^`s.sdi.c R1.7 on 6/27/80";

/*
 * module to handle span-dependent instructions, e.g. jbr
 * see CACM Vol 21 No 4 April 1978 for a description of the
 * algorithm for resolving sdi's between the 1st and 2nd pass
 */


struct sdi {	/* information for span dependent instructions (sdi's) */
	struct sdi *sdi_next;           /* next sdi in list */
	struct csect *sdi_csect;	/* csect of the sdi */
	long int sdi_loc;		/* location of the sdi */
	struct sym_bkt *sdi_sym;	/* symbol part of the sdi address */
	long int sdi_off;		/* offset part of the sdi address */
	int sdi_gleng;			/* length of the most general form */
	int sdi_leng;			/* actual length of the sdi */
	long int sdi_span;		/* span of  the sdi */
	long int sdi_base;		/* origin of the span of the sdi */
	struct blist *sdi_bounds;	/* length & bounds info for forms of sdi */
} *sdi_list = 0;	/* linked list of sdi's descriptors */

struct blist {	/* length and bounds information for various forms of sdi's */
	struct blist *b_next;	/* next element in list */
	int b_length;		/* length of this form of the sdi */
	long int b_lbound;	/* lower and upper bound on the span */
	long int b_ubound;	/* for this form of the sdi */
} shortblist = {
  NULL, 2, -128L, 127		/* canonical bounds list for byte offset */
};

/*
 * routine to create a sdi descriptor and insert it into the list
 */
makesdi(op, gleng, base, bounds)
struct oper *op;	/* the operand of the sdi */
int gleng;		/* the length of the most general form of the sdi */
long int base;		/* origin of the the initial span of the sdi, to be */
			/* corrected between pass1 and pass2 by subtracting */
			/* from the value of the symbol of the operand */
			/* i.e. 0 for resolving short absolute address modes */
			/* Dot[+increment] for PC relative addresses */
struct blist *bounds;	/* list of lengths & bounds for the sdi */
{
	register struct sdi *s, **p;
	extern struct csect *Cur_csect;

	if (op->flags_o&O_COMPLEX)	/* not a simple address */
		return;
	if ((s = (struct sdi *)calloc(1,sizeof *s)) == NULL)
		Sys_Error("sdi storage exceeded\n");
	s->sdi_loc = Dot;
	s->sdi_csect = Cur_csect;
	s->sdi_sym = op->sym_o;
	s->sdi_off = op->value_o;
	s->sdi_gleng = gleng;
	s->sdi_base = base;
	s->sdi_span = - base;
	s->sdi_bounds = bounds;
	s->sdi_leng = bounds->b_length;	/* shortest length */
	for (p = &sdi_list; *p; p = &(*p)->sdi_next)
		if (s->sdi_loc < (*p)->sdi_loc)
			break;
	s->sdi_next = *p;
	*p = s;
	return(s->sdi_leng);	/* return the current length */
}

/*
 * insert a new blist element into a blist
 * The blist is sorted by increasing b_length
 */
struct blist *sdi_bound(leng, lbound, ubound, next)
int leng;		/* length of this form of the sdi */
long int lbound;	/* lower bound of span */
long int ubound;	/* upper bound */
struct blist *next;	/* target blist */
{
	register struct blist *b, **p;

	if ((b = (struct blist *)calloc(1,sizeof *b)) == NULL)
		Sys_Error("sdi bound list storage exceeded\n");
	b->b_length = leng;
	b->b_lbound = lbound;
	b->b_ubound = ubound;
	for (p = &next; *p; p = &(*p)->b_next)
		if (leng <= (*p)->b_length)
			break;
	b->b_next = *p;
	*p = b;
	return(*p);
}

/*
 * resolve sdi's between pass1 and pass2
 * basic algorithm is to repeatedly look for sdi that must use the
 * long form, and update the span of other sdi's.
 * When this terminates, all remaining sdi's can use the short form
 */
sdi_resolve()
{
	register struct sdi *s;
	register struct sym_bkt *p;
	register int t;
	int changed;

	for (s = sdi_list; s; s = s->sdi_next) {
		p = s->sdi_sym;
		if (s->sdi_csect == p->csect_s && (p->attr_s & S_DEF))
			s->sdi_span += s->sdi_sym->value_s;
	}

	do {
		changed = 0;
		for (s = sdi_list; s; s = s->sdi_next) {
			if ((t = sdi_len(s) - s->sdi_leng) > 0) {
				longsdi(s, t);
				changed = 1;
			} else if (t < 0)
				Sys_Error("Pathological sdi\n");
		}
	} while (changed);
}

/*
 * update sdi list assuming the specified sdi must be long
 */
longsdi(s, delta)
struct sdi *s;
register int delta;
{
	register struct sdi *t;
	long loc;

	s->sdi_leng += delta;	/* update the length of this sdi */
	/* get the real location of the sdi */
	loc = s->sdi_loc + sdi_inc(s->sdi_csect, s->sdi_loc);
	for (t = sdi_list; t; t = t->sdi_next)
		if (t != s && s->sdi_csect == t->sdi_sym->csect_s) {
			if (t->sdi_span < 0
			 && t->sdi_span+t->sdi_base <= loc
			 && loc < t->sdi_base)
				t->sdi_span -= delta;
			else if (t->sdi_span >= 0
			 && t->sdi_base <= loc
			 && loc < t->sdi_span+t->sdi_base)
				t->sdi_span += delta;
			if (t->sdi_base > loc)
				t->sdi_base += delta;
		}
}

/*
 * compute the length of the specified sdi by searching the bounds list
 */
sdi_len(s)
register struct sdi *s;
{
	register struct blist *b;
	if (!(s->sdi_sym->attr_s & S_DEF) || s->sdi_csect != s->sdi_sym->csect_s)
		return(s->sdi_gleng);
	for (b = s->sdi_bounds; b; b = b->b_next)
		if (b->b_lbound <= s->sdi_span && s->sdi_span <= b->b_ubound)
			return(b->b_length);
	return(s->sdi_gleng);
}

/*
 * return the total number of extra bytes due to long sdi's before
 * the specified offset in the specified csect
 */
sdi_inc(csect, offset)
register struct csect *csect;
long int offset;
{
	register struct sdi *s;
	register int total;

	for (s = sdi_list, total = 0; s; s = s->sdi_next) {
		if (csect == s->sdi_csect) {
			if (offset <= s->sdi_loc)
				break;
			total += s->sdi_leng - s->sdi_bounds->b_length;
		}
	}
	return(total);
}

/*
 * release all sdi descriptors
 */
sdi_free()
{
	register struct sdi *s, *t;
	for (s = sdi_list; s; s = t) {
		t = s->sdi_next;
		b_free(s->sdi_bounds);
		free(s);
	}
	sdi_list = (struct sdi *)0;
}

/*
 * release a bounds list
 */
b_free(p)
register struct blist *p;
{
	register struct blist *q;
	while(p) {
		q = p->b_next;
		free(p);
		p = q;
	}
}

