/*
 *	Copyright 1989 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/*
 * This is an almost-standalone optimizer for the Shell pseudo-code.
 * It usually improves execution time and code space by 20-40%.
 */

#include "hostenv.h"
#include <stdio.h>
#include "sh.h"
#include "shconfig.h"

#ifdef DEBUG
#define ASSERT_INRANGE(val,low,high) ({if (val < low || val > high) abort();})
#else
#define ASSERT_INRANGE(val,low,high)
#endif

extern char *progname;

#define	LNEXT(x)	*((x)+1)
#define	NEXT(x)	(OutputTokens)LNEXT(x)
#define	LNEXTNEXT(x)	*((x)+2)
#define	NEXTNEXT(x)	(OutputTokens)LNEXTNEXT(x)
		/* NEXTNEXT only makes sense if nargs of NEXT(pc) is 0 */
#define	LNEXTNEXTNEXT(x)	*((x)+3)
#define	NEXTNEXTNEXT(x)	(OutputTokens)LNEXTNEXTNEXT(x)
		/* NEXTNEXTNEXT only makes sense if (as above) */
#define	CODE(x) (OutputTokens)(code[(x)])
#define	JUMPADDRESS(x)	(k = code[(x)+1], k <<= 8, k |= code[(x)+2], k <<= 8, k |= code[(x)+3], k <<= 8, k |= code[(x)+4], k)

/*
 * Shell pseudo-code optimizer.
 */

u_char *
optimize(print, code, eocodep)
	int	print;
	u_char	*code, **eocodep;
{
	char	*arg1, *bitmap;
	u_char	*pc, *spc, *eocode = *eocodep;
	u_char	*npc, *nspc, *ncode;
	int	*addrmap;
	int	coderange = (eocode - code)+1;
	int	cmd, argi1, i, j, k, scopetop = -1, commandtop = -1, quotenext;
	short	scope[MAXNSCOPES], command[MAXNCOMMANDS];
	u_char	*scopeaddr[MAXNSCOPES], *commandaddr[MAXNCOMMANDS];

	quotenext = 0;
	/* The  ncode[]  array may REPLACE the  code[]  array, thus it
	   MUST be  malloc()ed buffer.. */
	if ((ncode = (u_char *)malloc(coderange)) == NULL) {
		fprintf(stderr, "%s: can't malloc %d bytes\n",
			progname, *eocodep - code);
		return code;
	}
#ifdef	USE_ALLOCA
	if ((addrmap = (int *)alloca((coderange) * sizeof(int))) == NULL
	    || (bitmap = (char *)alloca((coderange)/8+1)) == NULL) {
		fprintf(stderr, "%s: can't malloc %d bytes\n",
			progname, *eocodep - code);
		return code;
	}
	bzero(bitmap,(coderange)/8+1);
#else /* Not  USE_ALLOCA */
	if ((addrmap = (int *)malloc((coderange) * sizeof(int))) == NULL
	    || (bitmap = (char *)calloc((coderange)/8+1, 1)) == NULL) {
		fprintf(stderr, "%s: can't malloc %d bytes\n",
			progname, coderange);
		if (addrmap) free(addrmap);
		if (bitmap) free(bitmap);
		/* If those previous ones succeeded, it was ncode that
		   failed.. no need to test and free it.. */
		return code;
	}
#endif

	/* shut up compilers */
	arg1 = NULL; spc = NULL; argi1 = 0;

again:
	for (pc = code, npc = ncode; pc < eocode; ++pc) {
		if (*pc >= ncommands) {	/* unsigned; don't need neg. test */
			fprintf(stderr, "%s: unknown opcode %d at %d\n",
				progname, *pc, pc - code);
			fprintf(stderr, "%s: previous opcode was %d\n",
				progname, *(pc-1));
			break;
		}
		cmd = *pc;
		if (print)
			printf("%d:\t%s", pc-code, TOKEN_NAME(cmd));
		spc = pc;
		switch (TOKEN_NARGS(cmd)) {
		case 1:
			arg1 = (char *)++pc;
			while (*pc != '\0')
				++pc;
			if (print)
				printf("(%s)", arg1);
			break;
		case -1:
			argi1 = *++pc;
			argi1 <<= 8;
			argi1 |= *++pc;
			argi1 <<= 8;
			argi1 |= *++pc;
			argi1 <<= 8;
			argi1 |= *++pc;
if (argi1 < 0 || argi1 > coderange-1)
printf("argi1 @%d outside coderange! %d (%d..%d)\n",
       pc-code,argi1,0,coderange-1);
			if (print)
				printf("(%d)", argi1);
			break;
		}
		nspc = npc;
ASSERT_INRANGE(spc-code,0,coderange-1);
		addrmap[spc-code] = npc-ncode;
		while (spc <= pc)
			*npc++ = *spc++;
		if (print) {
			putchar('\n');
			continue;
		}
		if (commandtop >= 0)
			++command[commandtop];
		switch ((OutputTokens)cmd) {
		case sBufferSet:
			if (quotenext == 0 && *arg1 == '\0') {
				if (NEXT(pc) == sBufferAppend) {
					npc = nspc;
					LNEXT(pc) = (u_char)sBufferSet;
				} else if (NEXT(pc) == sBufferSet) {
					npc = nspc;
				} else if ((NEXT(pc) == sDollarExpand
					    || NEXT(pc) == sBufferQuote)
					   && NEXTNEXT(pc) == sBufferAppend) {
					npc = nspc;
					LNEXTNEXT(pc) = (u_char)sBufferSet;
				} else if (NEXT(pc) == sDollarExpand
					&& NEXTNEXT(pc) == sBufferQuote
					&& NEXTNEXTNEXT(pc) == sBufferAppend) {
					npc = nspc;
					LNEXTNEXTNEXT(pc) = (u_char)sBufferSet;
				}
			} else if (NEXT(pc) == sVariablePop
				   || NEXT(pc) == sBufferSet
				   || (NEXT(pc) == sIOsetOut
				       && NEXTNEXT(pc) == sBufferSet))
				npc = nspc;
			break;
		case sBufferQuote:
			quotenext = 1;
			continue;
		case sBufferAppend:
		case sBufferExpand:
		case sBufferSetFromArgV:
		case sArgVpush:
		case sArgList:
		case sVariableCdr:
		case sVariablePush:
		case sVariablePop:
		case sVariableBuffer:
		case sVariableAppend:
		case sVariableLoopAttach:
			break;
		case sCommandPush:
			if (NEXT(pc) == sCommandPop) {
				npc = nspc;	/* skip the CommandPush */
				++pc;		/* skip the CommandPop */
			} else {
				if (commandtop >= 0)
					--command[commandtop];
				command[++commandtop] = 0;
				commandaddr[commandtop] = nspc;
			}
			break;
		case sCommandPop:
			--command[commandtop];
			if (command[commandtop] == 0) {
				/* this command push/pop pair is superfluous */
				*commandaddr[commandtop] = (u_char)sNoOp;
				npc = nspc;	/* superfluous commandPop */
			}
			--commandtop;
			break;
		case sCommandCarryBuffer:
		case sIOopen:
		case sIOopenString:
		case sIOopenPortal:
		case sIOintoBuffer:
		case sIOclose:
		case sIOdup:
			break;
		case sIOsetIn:
		case sIOsetInOut:
		case sIOsetOut:
		case sIOsetAppend:
			if (NEXT(pc) == sIOdup)
				npc = nspc;	/* skip the sIOset{In,Out} */
			break;
		case sIOsetDesc:
		case sAssign:
		case sAssignTemporary:
			break;
		case sFunction:
		case sJump:
			while (CODE(argi1) == sJump)
				argi1 = JUMPADDRESS(argi1);
			if (argi1 == pc-code+1)
				npc = nspc;	/* superfluous Jump */
			else {
				*(npc-1) = argi1&0xff;
				*(npc-2) = (argi1>>8)&0xff;
				*(npc-3) = (argi1>>16)&0xff;
				*(npc-4) = (argi1>>24)&0xff;
			}
			if (npc != nspc /* jump wasn't superfluous */
				&& NEXT(pc) == sJump) {
				/* superfluous jump because it is cascaded */
ASSERT_INRANGE(pc-code+1,0,coderange-1);
				addrmap[pc-code+1] = npc-ncode;
				pc += 5;	/* skip next Jump command */
			}
			if (npc != nspc) {
				if (argi1 < pc-code) {
ASSERT_INRANGE(argi1,0,coderange-1);
					i = addrmap[argi1];
				} else {
ASSERT_INRANGE(npc-4-ncode,0,coderange-1);
					i = -argi1, BITSET(bitmap,npc-4-ncode);
				}
				*(npc-1) = i&0xff;
				*(npc-2) = (i>>8)&0xff;
				*(npc-3) = (i>>16)&0xff;
				*(npc-4) = (i>>24)&0xff;
			}
			break;
		case sBranchOrigin:
			fprintf(stderr, "%s: unpatched branch at %d\n",
					progname, pc-code);
			break;
		case sJumpFork:
			while (CODE(argi1) == sJump)
				argi1 = JUMPADDRESS(argi1);
			if (argi1 < pc-code) {
ASSERT_INRANGE(argi1,0,coderange-1);
				i = addrmap[argi1];
			} else {
ASSERT_INRANGE(npc-4-ncode,0,coderange-1);
				i = -argi1, BITSET(bitmap,npc-4-ncode);
			}
			*(npc-1) = i&0xff;
			*(npc-2) = (i>>8)&0xff;
			*(npc-3) = (i>>16)&0xff;
			*(npc-4) = (i>>24)&0xff;
			break;
		case sJumpIfFailure:
			while (argi1 < (eocode - code)
			       && (CODE(argi1) == sJump
				       || CODE(argi1) == sJumpIfFailure)) {
				argi1 = JUMPADDRESS(argi1);
			}
			if (argi1 < pc-code) {
ASSERT_INRANGE(argi1,0,coderange-1);
				i = addrmap[argi1];
			} else {
ASSERT_INRANGE(npc-4-ncode,0,coderange-1);
				i = -argi1, BITSET(bitmap,npc-4-ncode);
			}
			*(npc-1) = i&0xff;
			*(npc-2) = (i>>8)&0xff;
			*(npc-3) = (i>>16)&0xff;
			*(npc-4) = (i>>24)&0xff;
			break;
#ifdef	MAILER
		case sSiftPush:
		case sSiftBody:
		case sSiftCompileRegexp:
		case sSiftReevaluate:
		case sSiftPop:
		case sSiftBufferAppend:

		case sTSiftPush:
		case sTSiftBody:
		case sTSiftCompileRegexp:
		case sTSiftReevaluate:
		case sTSiftPop:
		/* case sTSiftBufferAppend: */
			break;
		case sJumpIfRegmatch:
		case sTJumpIfRegmatch:
#endif	/* MAILER */
		case sJumpIfSuccess:
		case sJumpIfNilVariable:
		case sJumpIfFindVarNil:
		case sJumpIfOrValueNil:
		case sJumpLoopBreak:
		case sJumpLoopContinue:
			if (argi1 < pc-code) {
ASSERT_INRANGE(argi1,0,coderange-1);
				i = addrmap[argi1];
			} else {
ASSERT_INRANGE(npc-4-ncode,0,coderange-1);
				i = -argi1, BITSET(bitmap,npc-4-ncode);
			}
			*(npc-1) = i&0xff;
			*(npc-2) = (i>>8)&0xff;
			*(npc-3) = (i>>16)&0xff;
			*(npc-4) = (i>>24)&0xff;
			break;
		case sJumpIfMatch:
			while (CODE(argi1) == sJumpIfMatch
			       || CODE(argi1) == sJump)
				argi1 = JUMPADDRESS(argi1);
			if (argi1 < pc-code) {
ASSERT_INRANGE(argi1,0,coderange-1);
				i = addrmap[argi1];
			} else {
ASSERT_INRANGE(npc-4-ncode,0,coderange-1);
				i = -argi1, BITSET(bitmap,npc-4-ncode);
			}
			*(npc-1) = i&0xff;
			*(npc-2) = (i>>8)&0xff;
			*(npc-3) = (i>>16)&0xff;
			*(npc-4) = (i>>24)&0xff;
			break;
		case sLocalVariable:
			--command[commandtop];
			/* FALL THROUGH */
		case sParameter:
			scope[scopetop] = 1;
			break;
		case sScopePush:
			scope[++scopetop] = 0;
			scopeaddr[scopetop] = nspc;
			if (commandtop >= 0)
				--command[commandtop];
			break;
		case sScopePop:
			if (scope[scopetop] == 0
			    /*
			     * WARNING!!! the following optimization is on
			     * the assumption that two ScopePop's in a row
			     * will only occur for local variables in a
			     * function, which is usually a benign assumption.
			     * IT MAY BECOME THE SOURCE OF STRANGE BEHAVIOUR!
			     */
			    || NEXT(pc) == sScopePop) {
				/* this scope push/pop pair is superfluous */
				*scopeaddr[scopetop] = (u_char)sNoOp;
				npc = nspc;	/* superfluous scopePop */
			}
			--scopetop;
			if (commandtop >= 0)
				--command[commandtop];
		case sDollarExpand:
		case sPrintAndExit:
		case sLoopEnter:
		case sLoopExit:
		case sBackground:
			break;
		case sNoOp:
			npc = nspc;	/* superfluous command */
			if (commandtop >= 0)
				--command[commandtop];
			break;
		default:
			break;
		}
		quotenext = 0;
	}
	if (print)
		goto done;
ASSERT_INRANGE(spc-code,0,coderange-1);
	addrmap[spc-code] = npc-ncode;	/* in case we jump to after eocode */
	for (i = 0; i <= npc-ncode; i += 8) {	/* yes, the <= is on purpose */
ASSERT_INRANGE(i,0,coderange-1);
		if (bitmap[i/8]) {
			for (j = 0; j < 8; ++j) {
				if (BITTEST(bitmap,i+j)) {
					k = ncode[i+j];
					k <<= 8;
					k |= ncode[i+j+1];
					k <<= 8;
					k |= ncode[i+j+2];
					k <<= 8;
					k |= ncode[i+j+3];
ASSERT_INRANGE(-k,0,coderange-1);
					k = addrmap[-k];
					ncode[i+j] = (k>>24)&0xff;
					ncode[i+j+1] = (k>>16)&0xff;
					ncode[i+j+2] = (k>>8)&0xff;
					ncode[i+j+3] = k&0xff;
				}
			}
			bitmap[i/8] = 0;	/* so we can reuse the bitmap */
		}
	}
	if (npc-ncode < pc-code) {
		*eocodep = eocode = ncode + (npc-ncode);
		spc = ncode;
		ncode = code;
		code = spc;
		goto again;
	}
done:
#ifndef USE_ALLOCA
	free((char *)addrmap);
	free(bitmap);
#endif
	free((char *)ncode);
	return code;
}
