/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#ifndef lint
static char sccsid[] = "@(#)call.c	5.2 (Berkeley) 7/26/85";
#endif not lint

#include "whoami.h"
#include "0.h"
#include "tree.h"
#include "opcode.h"
#include "objfmt.h"
#ifdef PC
#   include "pc.h"
#   include <pcc.h>
#endif PC
#include "tmps.h"
#include "tree_ty.h"

/*
 * Call generates code for calls to
 * user defined procedures and functions
 * and is called by proc and funccod.
 * P is the result of the lookup
 * of the procedure/function symbol,
 * and porf is PROC or FUNC.
 * Psbn is the block number of p.
 *
 *	the idea here is that regular scalar functions are just called,
 *	while structure functions and formal functions have their results
 *	stored in a temporary after the call.
 *	structure functions do this because they return pointers
 *	to static results, so we copy the static
 *	and return a pointer to the copy.
 *	formal functions do this because we have to save the result
 *	around a call to the runtime routine which restores the display,
 *	so we can't just leave the result lying around in registers.
 *	formal calls save the address of the descriptor in a local
 *	temporary, so it can be addressed for the call which restores
 *	the display (FRTN).
 *	calls to formal parameters pass the formal as a hidden argument 
 *	to a special entry point for the formal call.
 *	[this is somewhat dependent on the way arguments are addressed.]
 *	so PROCs and scalar FUNCs look like
 *		p(...args...)
 *	structure FUNCs look like
 *		(temp = p(...args...),&temp)
 *	formal FPROCs look like
 *		( t=p,( t -> entryaddr )(...args...,t,s),FRTN(t,s))
 *	formal scalar FFUNCs look like
 *		( t=p,temp=( t -> entryaddr )(...args...,t,s),FRTN(t,s),temp)
 *	formal structure FFUNCs look like
 *		(t=p,temp = ( t -> entryaddr )(...args...,t,s),FRTN(t,s),&temp)
 */
struct nl *
call(p, argv_node, porf, psbn)
	struct nl *p;
	struct tnode	*argv_node;	/* list node */
	int porf, psbn;
{
	register struct nl *p1, *q, *p2;
	register struct nl *ptype, *ctype;
	struct tnode *rnode;
	int i, j, d;
	bool chk = TRUE;
 	struct nl	*savedispnp;	/* temporary to hold saved display */
#	ifdef PC
	    int		p_type_class = classify( p -> type );
	    long	p_type_p2type = p2type( p -> type );
	    bool	noarguments;
		/*
		 *	these get used if temporaries and structures are used
		 */
	    struct nl	*tempnlp;
	    long	temptype;	/* type of the temporary */
	    long	p_type_width;
	    long	p_type_align;
	    char	extname[ BUFSIZ ];
	    struct nl	*tempdescrp;
#	endif PC

         if (p->class == FFUNC || p->class == FPROC) {
 	    /*
 	     * allocate space to save the display for formal calls
 	     */
	    savedispnp = tmpalloc( (long) sizeof display , NLNIL , NOREG );
 	}
#	ifdef OBJ
	    if (p->class == FFUNC || p->class == FPROC) {
 		(void) put(2, O_LV | cbn << 8 + INDX ,
 			(int) savedispnp -> value[ NL_OFFS ] );
		(void) put(2, PTR_RV | psbn << 8+INDX, (int)p->value[NL_OFFS]);
	    }
	    if (porf == FUNC) {
		    /*
		     * Push some space
		     * for the function return type
		     */
		    (void) put(2, O_PUSH, leven(-lwidth(p->type)));
	    }
#	endif OBJ
#	ifdef PC
		/*
		 *	if this is a formal call,
		 *	stash the address of the descriptor
		 *	in a temporary so we can find it
		 *	after the FCALL for the call to FRTN
		 */
	    if ( p -> class == FFUNC || p -> class == FPROC ) {
		tempdescrp = tmpalloc((long) (sizeof( struct formalrtn *)),
					NLNIL, REGOK );
		putRV((char *) 0 , cbn , tempdescrp -> value[ NL_OFFS ] ,
			tempdescrp -> extra_flags , PCCTM_PTR|PCCT_STRTY );
		putRV((char *) 0 , psbn , p -> value[ NL_OFFS ] ,
			p -> extra_flags , PCCTM_PTR|PCCT_STRTY );
		putop( PCC_ASSIGN , PCCTM_PTR | PCCT_STRTY );
	    }
		/*
		 *	if we have to store a temporary,
		 *	temptype will be its type,
		 *	otherwise, it's PCCT_UNDEF.
		 */
	    temptype = PCCT_UNDEF;
	    if ( porf == FUNC ) {
		p_type_width = width( p -> type );
		switch( p_type_class ) {
		    case TSTR:
		    case TSET:
		    case TREC:
		    case TFILE:
		    case TARY:
			temptype = PCCT_STRTY;
			p_type_align = align( p -> type );
			break;
		    default:
			if ( p -> class == FFUNC ) {
			    temptype = p2type( p -> type );
			}
			break;
		}
		if ( temptype != PCCT_UNDEF ) {
		    tempnlp = tmpalloc(p_type_width, p -> type, NOREG);
			/*
			 *	temp
			 *	for (temp = ...
			 */
		    putRV((char *) 0 , cbn , tempnlp -> value[ NL_OFFS ] ,
			    tempnlp -> extra_flags , (int) temptype );
		}
	    }
	    switch ( p -> class ) {
		case FUNC:
		case PROC:
			/*
			 *	... p( ...
			 */
		    sextname( extname , p -> symbol , BLOCKNO(p -> nl_block) );
		    putleaf( PCC_ICON , 0 , 0 , p2type( p ) , extname );
		    break;
		case FFUNC:
		case FPROC:

			    /*
			     *	... ( t -> entryaddr )( ...
			     */
			    /* 	the descriptor */
			putRV((char *) 0 , cbn , tempdescrp -> value[ NL_OFFS ] ,
				tempdescrp -> extra_flags , PCCTM_PTR | PCCT_STRTY );
			    /*	the entry address within the descriptor */
			if ( FENTRYOFFSET != 0 ) {
			    putleaf( PCC_ICON , FENTRYOFFSET , 0 , PCCT_INT , 
						(char *) 0 );
			    putop( PCC_PLUS , 
				PCCM_ADDTYPE(
				    PCCM_ADDTYPE( PCCM_ADDTYPE( p2type( p ) , PCCTM_FTN ) ,
					    PCCTM_PTR ) ,
					PCCTM_PTR ) );
			}
			    /*
			     *	indirect to fetch the formal entry address
			     *	with the result type of the routine.
			     */
			if (p -> class == FFUNC) {
			    putop( PCCOM_UNARY PCC_MUL ,
				PCCM_ADDTYPE(PCCM_ADDTYPE(p2type(p -> type), PCCTM_FTN),
					PCCTM_PTR));
			} else {
				/* procedures are int returning functions */
			    putop( PCCOM_UNARY PCC_MUL ,
				PCCM_ADDTYPE(PCCM_ADDTYPE(PCCT_INT, PCCTM_FTN), PCCTM_PTR));
			}
			break;
		default:
			panic("call class");
	    }
	    noarguments = TRUE;
#	endif PC
	/*
	 * Loop and process each of
	 * arguments to the proc/func.
	 *	... ( ... args ... ) ...
	 */
	ptype = NIL;
	for (p1 = plist(p); p1 != NLNIL; p1 = p1->chain) {
	    if (argv_node == TR_NIL) {
		    error("Not enough arguments to %s", p->symbol);
		    return (NLNIL);
	    }
	    switch (p1->class) {
		case REF:
			/*
			 * Var parameter
			 */
			rnode = argv_node->list_node.list;
			if (rnode != TR_NIL && rnode->tag != T_VAR) {
				error("Expression given (variable required) for var parameter %s of %s", p1->symbol, p->symbol);
				chk = FALSE;
				break;
			}
			q = lvalue( argv_node->list_node.list,
					MOD | ASGN , LREQ );
			if (q == NIL) {
				chk = FALSE;
				break;
			}
			p2 = p1->type;
			if (p2 == NLNIL || p2->chain == NLNIL || p2->chain->class != CRANGE) {
			    if (q != p2) {
				error("Parameter type not identical to type of var parameter %s of %s", p1->symbol, p->symbol);
				chk = FALSE;
			    }
			    break;
			} else {
			    /* conformant array */
			    if (p1 == ptype) {
				if (q != ctype) {
				    error("Conformant array parameters in the same specification must be the same type.");
				    goto conf_err;
				}
			    } else {
				if (classify(q) != TARY && classify(q) != TSTR) {
				    error("Array type required for var parameter %s of %s",p1->symbol,p->symbol);
				    goto conf_err;
				}
				/* check base type of array */
				if (p2->type != q->type) {
				    error("Base type of array not identical to that of conformant array parameter %s of %s", p1->symbol, p->symbol);
				    goto conf_err;
				}
				if (p2->value[0] != q->value[0]) {
				    error("Subscript number mismatch on conformant array parameter %s of %s", p1->symbol, p->symbol);
				    /* Don't process array bounds & width */
conf_err:			    if (p1->chain->type->class == CRANGE) {
					d = p1->value[0];
					for (i = 1; i <= d; i++) {
					    /* for each subscript, pass by
					     * bounds and width
					     */
					    p1 = p1->chain->chain->chain;
					}
				    }
				    ptype = ctype = NLNIL;
				    chk = FALSE;
				    break;
				}
				/*
				 * Save array type for all parameters with same
				 * specification.
				 */
				ctype = q;
				ptype = p2;
				/*
				 * If at end of conformant array list,
				 * get bounds.
				 */
				if (p1->chain->type->class == CRANGE) {
				    /* check each subscript, put on stack */
				    d = ptype->value[0];
				    q = ctype;
				    for (i = 1; i <= d; i++) {
					p1 = p1->chain;
					q = q->chain;
					if (incompat(q, p1->type, TR_NIL)){
					    error("Subscript type not conformable with parameter %s of %s", p1->symbol, p->symbol);
					    chk = FALSE;
					    break;
					}
					/* Put lower and upper bound & width */
#					ifdef OBJ
					if (q->type->class == CRANGE) {
					    putcbnds(q->type);
					} else {
					    put(2, width(p1->type) <= 2 ? O_CON2
						: O_CON4, q->range[0]);
					    put(2, width(p1->type) <= 2 ? O_CON2
						: O_CON4, q->range[1]);
					    put(2, width(p1->type) <= 2 ? O_CON2
						: O_CON4, aryconst(ctype,i));
					}
#					endif OBJ
#					ifdef PC
					if (q->type->class == CRANGE) {
					    for (j = 1; j <= 3; j++) {
						p2 = p->nptr[j];
						putRV(p2->symbol, (p2->nl_block
						    & 037), p2->value[0],
						    p2->extra_flags,p2type(p2));
						putop(PCC_CM, PCCT_INT);
					    }
					} else {
					    putleaf(PCC_ICON, q->range[0], 0,PCCT_INT,0);
					    putop( PCC_CM , PCCT_INT );
					    putleaf(PCC_ICON, q->range[1], 0,PCCT_INT,0);
					    putop( PCC_CM , PCCT_INT );
					    putleaf(PCC_ICON,aryconst(ctype,i),0,PCCT_INT,0);
					    putop( PCC_CM , PCCT_INT );
					}
#					endif PC
					p1 = p1->chain->chain;
				    }
				}
			    }
			}
			break;
		case VAR:
			/*
			 * Value parameter
			 */
#			ifdef OBJ
			    q = rvalue(argv_node->list_node.list,
					p1->type , RREQ );
#			endif OBJ
#			ifdef PC
				/*
				 * structure arguments require lvalues,
				 * scalars use rvalue.
				 */
			    switch( classify( p1 -> type ) ) {
				case TFILE:
				case TARY:
				case TREC:
				case TSET:
				case TSTR:
				q = stkrval(argv_node->list_node.list,
						p1 -> type , (long) LREQ );
				    break;
				case TINT:
				case TSCAL:
				case TBOOL:
				case TCHAR:
				    precheck( p1 -> type , "_RANG4" , "_RSNG4" );
				q = stkrval(argv_node->list_node.list,
						p1 -> type , (long) RREQ );
				    postcheck(p1 -> type, nl+T4INT);
				    break;
				case TDOUBLE:
				q = stkrval(argv_node->list_node.list,
						p1 -> type , (long) RREQ );
				    sconv(p2type(q), PCCT_DOUBLE);
				    break;
				default:
				    q = rvalue(argv_node->list_node.list,
						p1 -> type , RREQ );
				    break;
			    }
#			endif PC
			if (q == NIL) {
				chk = FALSE;
				break;
			}
			if (incompat(q, p1->type,
				argv_node->list_node.list)) {
				cerror("Expression type clashed with type of value parameter %s of %s", p1->symbol, p->symbol);
				chk = FALSE;
				break;
			}
#			ifdef OBJ
			    if (isa(p1->type, "bcsi"))
				    rangechk(p1->type, q);
			    if (q->class != STR)
				    convert(q, p1->type);
#			endif OBJ
#			ifdef PC
			    switch( classify( p1 -> type ) ) {
				case TFILE:
				case TARY:
				case TREC:
				case TSET:
				case TSTR:
					putstrop( PCC_STARG
					    , p2type( p1 -> type )
					    , (int) lwidth( p1 -> type )
					    , align( p1 -> type ) );
			    }
#			endif PC
			break;
		case FFUNC:
			/*
			 * function parameter
			 */
			q = flvalue(argv_node->list_node.list, p1 );
			/*chk = (chk && fcompat(q, p1));*/
			if ((chk) && (fcompat(q, p1)))
			    chk = TRUE;
			else
			    chk = FALSE;
			break;
		case FPROC:
			/*
			 * procedure parameter
			 */
			q = flvalue(argv_node->list_node.list, p1 );
			/* chk = (chk && fcompat(q, p1)); */
			if ((chk) && (fcompat(q, p1)))
			    chk = TRUE;
			else chk = FALSE;
			break;
		default:
			panic("call");
	    }
#	    ifdef PC
		    /*
		     *	if this is the nth (>1) argument,
		     *	hang it on the left linear list of arguments
		     */
		if ( noarguments ) {
			noarguments = FALSE;
		} else {
			putop( PCC_CM , PCCT_INT );
		}
#	    endif PC
	    argv_node = argv_node->list_node.next;
	}
	if (argv_node != TR_NIL) {
		error("Too many arguments to %s", p->symbol);
		rvlist(argv_node);
		return (NLNIL);
	}
	if (chk == FALSE)
		return NLNIL;
#	ifdef OBJ
	    if ( p -> class == FFUNC || p -> class == FPROC ) {
		(void) put(2, PTR_RV | psbn << 8+INDX, (int)p->value[NL_OFFS]);
 		(void) put(2, O_LV | cbn << 8 + INDX ,
 			(int) savedispnp -> value[ NL_OFFS ] );
		(void) put(1, O_FCALL);
		(void) put(2, O_FRTN, even(width(p->type)));
	    } else {
		(void) put(2, O_CALL | psbn << 8, (long)p->value[NL_ENTLOC]);
	    }
#	endif OBJ
#	ifdef PC
		/*
		 *	for formal calls: add the hidden argument
		 *	which is the formal struct describing the
		 *	environment of the routine.
		 *	and the argument which is the address of the
		 *	space into which to save the display.
		 */
	    if ( p -> class == FFUNC || p -> class == FPROC ) {
		putRV((char *) 0 , cbn , tempdescrp -> value[ NL_OFFS ] ,
			tempdescrp -> extra_flags , PCCTM_PTR|PCCT_STRTY );
		if ( !noarguments ) {
		    putop( PCC_CM , PCCT_INT );
		}
		noarguments = FALSE;
 		putLV((char *) 0 , cbn , savedispnp -> value[ NL_OFFS ] ,
 			savedispnp -> extra_flags , PCCTM_PTR | PCCT_STRTY );
 		putop( PCC_CM , PCCT_INT );
	    }
		/*
		 *	do the actual call:
		 *	    either	... p( ... ) ...
		 *	    or		... ( t -> entryaddr )( ... ) ...
		 *	and maybe an assignment.
		 */
	    if ( porf == FUNC ) {
		switch ( p_type_class ) {
		    case TBOOL:
		    case TCHAR:
		    case TINT:
		    case TSCAL:
		    case TDOUBLE:
		    case TPTR:
			putop( ( noarguments ? PCCOM_UNARY PCC_CALL : PCC_CALL ) ,
				(int) p_type_p2type );
			if ( p -> class == FFUNC ) {
			    putop( PCC_ASSIGN , (int) p_type_p2type );
			}
			break;
		    default:
			putstrop( ( noarguments ? PCCOM_UNARY PCC_STCALL : PCC_STCALL ),
				(int) PCCM_ADDTYPE( p_type_p2type , PCCTM_PTR ) ,
				(int) p_type_width ,(int) p_type_align );
			putstrop(PCC_STASG, (int) PCCM_ADDTYPE(p_type_p2type, PCCTM_PTR),
				(int) lwidth(p -> type), align(p -> type));
			break;
		}
	    } else {
		putop( ( noarguments ? PCCOM_UNARY PCC_CALL : PCC_CALL ) , PCCT_INT );
	    }
		/*
		 *	( t=p , ... , FRTN( t ) ...
		 */
	    if ( p -> class == FFUNC || p -> class == FPROC ) {
		putop( PCC_COMOP , PCCT_INT );
		putleaf( PCC_ICON , 0 , 0 , PCCM_ADDTYPE( PCCTM_FTN | PCCT_INT , PCCTM_PTR ) ,
			"_FRTN" );
		putRV((char *) 0 , cbn , tempdescrp -> value[ NL_OFFS ] ,
			tempdescrp -> extra_flags , PCCTM_PTR | PCCT_STRTY );
 		putLV((char *) 0 , cbn , savedispnp -> value[ NL_OFFS ] ,
 			savedispnp -> extra_flags , PCCTM_PTR | PCCT_STRTY );
 		putop( PCC_CM , PCCT_INT );
		putop( PCC_CALL , PCCT_INT );
		putop( PCC_COMOP , PCCT_INT );
	    }
		/*
		 *	if required:
		 *	either	... , temp )
		 *	or	... , &temp )
		 */
	    if ( porf == FUNC && temptype != PCCT_UNDEF ) {
		if ( temptype != PCCT_STRTY ) {
		    putRV((char *) 0 , cbn , tempnlp -> value[ NL_OFFS ] ,
			    tempnlp -> extra_flags , (int) p_type_p2type );
		} else {
		    putLV((char *) 0 , cbn , tempnlp -> value[ NL_OFFS ] ,
			    tempnlp -> extra_flags , (int) p_type_p2type );
		}
		putop( PCC_COMOP , PCCT_INT );
	    }
	    if ( porf == PROC ) {
		putdot( filename , line );
	    }
#	endif PC
	return (p->type);
}

rvlist(al)
	register struct tnode *al;
{

	for (; al != TR_NIL; al = al->list_node.next)
		(void) rvalue( al->list_node.list, NLNIL , RREQ );
}

    /*
     *	check that two function/procedure namelist entries are compatible
     */
bool
fcompat( formal , actual )
    struct nl	*formal;
    struct nl	*actual;
{
    register struct nl	*f_chain;
    register struct nl	*a_chain;
    extern struct nl	*plist();
    bool compat = TRUE;

    if ( formal == NLNIL || actual == NLNIL ) {
	return FALSE;
    }
    for (a_chain = plist(actual), f_chain = plist(formal);
         f_chain != NLNIL;
	 f_chain = f_chain->chain, a_chain = a_chain->chain) {
	if (a_chain == NIL) {
	    error("%s %s declared on line %d has more arguments than",
		parnam(formal->class), formal->symbol,
		(char *) linenum(formal));
	    cerror("%s %s declared on line %d",
		parnam(actual->class), actual->symbol,
		(char *) linenum(actual));
	    return FALSE;
	}
	if ( a_chain -> class != f_chain -> class ) {
	    error("%s parameter %s of %s declared on line %d is not identical",
		parnam(f_chain->class), f_chain->symbol,
		formal->symbol, (char *) linenum(formal));
	    cerror("with %s parameter %s of %s declared on line %d",
		parnam(a_chain->class), a_chain->symbol,
		actual->symbol, (char *) linenum(actual));
	    compat = FALSE;
	} else if (a_chain->class == FFUNC || a_chain->class == FPROC) {
	    /*compat = (compat && fcompat(f_chain, a_chain));*/
	    if ((compat) && (fcompat(f_chain, a_chain)))
		compat = TRUE;
	    else compat = FALSE;
	}
	if ((a_chain->class != FPROC && f_chain->class != FPROC) &&
	    (a_chain->type != f_chain->type)) {
	    error("Type of %s parameter %s of %s declared on line %d is not identical",
		parnam(f_chain->class), f_chain->symbol,
		formal->symbol, (char *) linenum(formal));
	    cerror("to type of %s parameter %s of %s declared on line %d",
		parnam(a_chain->class), a_chain->symbol,
		actual->symbol, (char *) linenum(actual));
	    compat = FALSE;
	}
    }
    if (a_chain != NIL) {
	error("%s %s declared on line %d has fewer arguments than",
	    parnam(formal->class), formal->symbol,
	    (char *) linenum(formal));
	cerror("%s %s declared on line %d",
	    parnam(actual->class), actual->symbol,
	    (char *) linenum(actual));
	return FALSE;
    }
    return compat;
}

char *
parnam(nltype)
    int nltype;
{
    switch(nltype) {
	case REF:
	    return "var";
	case VAR:
	    return "value";
	case FUNC:
	case FFUNC:
	    return "function";
	case PROC:
	case FPROC:
	    return "procedure";
	default:
	    return "SNARK";
    }
}

struct nl *plist(p)
    struct nl *p;
{
    switch (p->class) {
	case FFUNC:
	case FPROC:
	    return p->ptr[ NL_FCHAIN ];
	case PROC:
	case FUNC:
	    return p->chain;
	default:
	    {
		panic("plist");
		return(NLNIL); /* this is here only so lint won't complain
				  panic actually aborts */
	    }

    }
}

linenum(p)
    struct nl *p;
{
    if (p->class == FUNC)
	return p->ptr[NL_FVAR]->value[NL_LINENO];
    return p->value[NL_LINENO];
}
