/* $Id$ */
/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 */
/*  I N T E R M E D I A T E   C O D E
 *
 *  I C _ A U X . C
 */



#include <em_pseu.h>
#include <em_spec.h>
#include <em_mnem.h>
#include "../share/types.h"
#include "../share/global.h"
#include "../share/debug.h"
#include "../share/def.h"
#include "../share/utils.h"
#include "ic.h"
#include "ic_io.h"
#include "ic_lookup.h"
#include "../share/alloc.h"
#include "ic_aux.h"



/* opr_size */

offset opr_size(short instr)
{
	switch(instr) {
		case op_loe:
		case op_ste:
		case op_ine:
		case op_dee:
		case op_zre:
			return (offset) ws;
		case op_lde:
		case op_sde:
			return (offset) 2*ws;
		case op_lae:
		case op_fil:
		case op_gto:
			return (offset) UNKNOWN_SIZE;
		default:
			error("illegal operand of opr_size: %d", instr);
	}
	/* NOTREACHED */
}



/* dblockdef */

STATIC offset argsize(arg)
	arg_p arg;
{
	/* Compute the size (in bytes) that the given initializer
	 * will occupy.
	 */

	offset s;
	argb_p argb;

	switch(arg->a_type) {
		case ARGOFF:
			/* See if value fits in a short */
			if ((short) arg->a_a.a_offset == arg->a_a.a_offset) {
				return ws;
			} else {
				return 2*ws;
			}
		case ARGINSTRLAB:
		case ARGOBJECT:
		case ARGPROC:
			return ps;  /* pointer size */
		case ARGSTRING:
			/* strings are partitioned into pieces */
			s = 0;
			for (argb = &arg->a_a.a_string; argb != (argb_p) 0;
			   argb = argb->ab_next) {
				s += argb->ab_index;
			}
			return s;
		case ARGICN:
		case ARGUCN:
		case ARGFCN:
			return arg->a_a.a_con.ac_length;
		default:
			assert(FALSE);
		}
		/* NOTREACHED */
}


STATIC offset blocksize(byte pseudo, arg_p args)
{
	/* Determine the number of bytes of a datablock */

	arg_p	arg;
	offset	sum;

	switch(pseudo) {
	   case DHOL:
	   case DBSS:
		if (args->a_type != ARGOFF) {
			error("offset expected");
		}
		return args->a_a.a_offset;
	   case DCON:
	   case DROM:
		sum = 0;
		for (arg = args; arg != (arg_p) 0; arg = arg->a_next) {
			/* Add the sizes of all initializers */
			sum += argsize(arg);
		}
		return sum;
	   default:
		assert(FALSE);
	}
	/* NOTREACHED */
}


STATIC arg_p copy_arg(arg)
	arg_p arg;
{
	/* Copy one argument */

	arg_p new;

	assert(arg->a_type == ARGOFF);
	new = newarg(ARGOFF);
	new->a_a.a_offset = arg->a_a.a_offset;
	return new;
}



STATIC arg_p copy_rom(args)
	arg_p args;
{
	/* Make a copy of the values of a rom,
	 * provided that the rom contains only integer values,
	 */

	arg_p arg, arg2, argh;

	for (arg = args; arg != (arg_p) 0; arg = arg->a_next) {
		if (arg->a_type != ARGOFF) {
			return (arg_p) 0;
		}
	}
	/* Now make the copy */
	arg2 = argh = copy_arg(args);
	for (arg = args->a_next; arg != (arg_p) 0; arg = arg->a_next) {
		arg2->a_next = copy_arg(arg);
		arg2 = arg2->a_next;
	}
	return argh;
}



void dblockdef(db,n,lnp)
	dblock_p db;
	int	 n;
	line_p	 lnp;
{
	/* Process a data block defining occurrence */

	byte m;

	switch(n) {
		case ps_hol:
			m = DHOL;
			break;
		case ps_bss:
			m = DBSS;
			break;
		case ps_con:
			m = DCON;
			break;
		case ps_rom:
			m = DROM;
			break;
		default:
			assert(FALSE);
	}
	db->d_pseudo = m;
	db->d_size = blocksize(m, ARG(lnp));
	if (m == DROM) {
		/* We keep the values of a rom block in the data block
		 * table if the values consist of integers only.
		 */
		db->d_values = copy_rom(ARG(lnp));
	}
}



/* combine */

void combine(dblock_p db, line_p l1, line_p l2, byte pseu)
{
	/* Combine two successive ROMs/CONs (without a data label
	 * in between into a single ROM. E.g.:
	 *    xyz
	 *     rom 3,6,9,12
	 *     rom 7,0,2
	 * is changed into:
	 *    xyz
	 *     rom 3,6,9,12,7,0,2
	 */

	arg_p v;

	db->d_size += blocksize(pseu,ARG(l2));
	/* db is the data block that was already assigned to the
	 * first rom/con. The second one is not assigned a new
	 * data block of course, as the two are combined into
	 * one instruction.
	 */
	if (pseu == DROM && db->d_values != (arg_p) 0) {
		/* The values contained in a ROM are only copied
		 * to the data block if they may be useful to us
		 * (e.g. they certainly may not be strings). In our
		 * case it means that both ROMs must have useful
		 * arguments.
		 */
		for (v = db->d_values; v->a_next != (arg_p) 0; v = v->a_next);
		/* The first rom contained useful arguments. v now points to
		 * its last argument. Append the arguments of the second
		 * rom to this list. If the second rom has arguments that are
		 * not useful, throw away the entire list (we want to copy
		 * everything or nothing).
		 */
		if ((v->a_next = copy_rom(ARG(l2))) == (arg_p) 0) {
			oldargs(db->d_values);
			db->d_values = (arg_p) 0;
		}
	}
	for (v = ARG(l1); v->a_next != (arg_p) 0; v = v->a_next);
	/* combine the arguments of both instructions. */
	v->a_next = ARG(l2);
	ARG(l2) = (arg_p) 0;
}



/* arglist */

STATIC void arg_string(length,abp)
	offset  length;
	register argb_p abp;
{

	while (length--) {
		if (abp->ab_index == NARGBYTES)
			abp = abp->ab_next = newargb();
		abp->ab_contents[abp->ab_index++] = readchar();
	}
}


line_p arglist(n)
	int n;
{
	line_p	lnp;
	register arg_p ap,*app;
	bool moretocome;
	offset length;


	/*
	 * creates an arglist with n elements
	 * if n == 0 the arglist is variable and terminated by sp_cend
	 */

	lnp = newline(OPLIST);
	app = &ARG(lnp);
	moretocome = TRUE;
	do {
		switch(table2()) {
		default:
			error("unknown byte in arglist");
		case CSTX1:
			tabval2 = (offset) tabval;
		case CSTX2:
			*app = ap = newarg(ARGOFF);
			ap->a_a.a_offset = tabval2;
			app = &ap->a_next;
			break;
		case ILBX:
			*app = ap = newarg(ARGINSTRLAB);
			ap->a_a.a_instrlab = instr_lab((short) tabval);
			app = &ap->a_next;
			break;
		case DLBX:
			*app = ap = newarg(ARGOBJECT);
			ap->a_a.a_obj = object(string,(offset) 0, (offset) 0);
			/* The size of the object is unknown */
			app = &ap->a_next;
			break;
		case sp_pnam:
			*app = ap = newarg(ARGPROC);
			ap->a_a.a_proc = proclookup(string,OCCURRING);
			app = &ap->a_next;
			break;
		case VALX1:
			tabval2 = (offset) tabval;
		case VALX2:
			*app = ap = newarg(ARGOBJECT);
			ap->a_a.a_obj = object(string, tabval2, (offset) 0);
			app = &ap->a_next;
			break;
		case sp_scon:
			*app = ap = newarg(ARGSTRING);
			length = get_off();
			arg_string(length,&ap->a_a.a_string);
			app = &ap->a_next;
			break;
		case sp_icon:
			*app = ap = newarg(ARGICN);
			goto casecon;
		case sp_ucon:
			*app = ap = newarg(ARGUCN);
			goto casecon;
		case sp_fcon:
			*app = ap = newarg(ARGFCN);
		casecon:
			length = get_int();
			ap->a_a.a_con.ac_length = (short) length;
			arg_string(get_off(),&ap->a_a.a_con.ac_con);
			app = &ap->a_next;
			break;
		case sp_cend:
			moretocome = FALSE;
		}
		if (n && (--n) == 0)
			moretocome = FALSE;
	} while (moretocome);
	return(lnp);
}



/* is_datalabel */

bool is_datalabel(l)
	line_p l;
{
	VL(l);
	return (l->l_instr == (byte) ps_sym);
}



/* block_of_lab */

dblock_p block_of_lab(ident)
	char *ident;
{
	dblock_p dbl;

	/* Find the datablock with the given name.
	 * Used for defining occurrences.
	 */

	dbl = symlookup(ident,DEFINING);
	VD(dbl);
	if (dbl->d_pseudo != DUNKNOWN) {
		error("identifier %s redeclared", ident);
	}
	return dbl;
}



/* object */

STATIC obj_p make_object(dbl,off,size)
	dblock_p dbl;
	offset   off;
	offset   size;
{
	/* Allocate an obj struct with the given attributes
	 * (if it did not exist already).
	 * Return a pointer to the found or newly created object struct.
	 */

	obj_p obj, prev, new;

	/* See if the object was already present in the object list
	 *  of the given datablock. If it is not yet present, find
	 * the right place to insert the new object. Note that
	 * the objects are sorted by offset.
	 */
	prev = (obj_p) 0;
	for (obj = dbl->d_objlist; obj != (obj_p) 0; obj = obj->o_next) {
		if (obj->o_off >= off) {
			break;
		}
		prev = obj;
	}
	/* Note that the data block may contain several objects
	 * with the required offset; we also want the size to
	 * be the right one.
	 */
	while (obj != (obj_p) 0 && obj->o_off == off) {
		if (obj->o_size == UNKNOWN_SIZE) {
			obj->o_size = size;
			return obj;
		} else {
			if (size == UNKNOWN_SIZE || obj->o_size == size) {
				return obj;
				/* This is the right one */
			} else {
				prev = obj;
				obj = obj->o_next;
			}
		}
	}
	/* Allocate a new object */
	new = newobject();
	new->o_id     = ++lastoid;	/* create a unique object id */
	new->o_off    = off;
	new->o_size   = size;
	new->o_dblock = dbl;
	/* Insert the new object */
	if (prev == (obj_p) 0) {
		dbl->d_objlist = new;
	} else {
		prev->o_next = new;
	}
	new->o_next = obj;
	return new;
}



obj_p object(char *ident, offset off, offset size)
{
	dblock_p dbl;

	/* Create an object struct (if it did not yet exist)
	 * for the object with the given size and offset
	 * within the datablock of the given name.
	 */

	dbl = (ident == (char *) 0 ? hol0_db : symlookup(ident, OCCURRING));
	VD(dbl);
	return(make_object(dbl,off,size));
}
