package s3.services.j2c;

import org.ovmj.util.Runabout;

import ovm.core.OVMBase;
import ovm.core.domain.Blueprint;
import ovm.core.repository.ExceptionHandler;
import ovm.core.repository.JavaNames;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeCodes;
import ovm.core.repository.UnboundSelector;
import ovm.services.bytecode.Instruction;
import ovm.services.bytecode.InstructionBuffer;
import ovm.services.bytecode.InstructionVisitor;
import ovm.services.bytecode.JVMConstants;
import ovm.services.bytecode.SpecificationIR.AssignmentExp;
import ovm.services.bytecode.SpecificationIR.BinExp;
import ovm.services.bytecode.SpecificationIR.ConversionExp;
import ovm.services.bytecode.SpecificationIR.IfCmd;
import ovm.services.bytecode.SpecificationIR.LocalStore;
import ovm.services.bytecode.SpecificationIR.MemExp;
import ovm.services.bytecode.SpecificationIR.Value;
import ovm.services.bytecode.SpecificationIR.ValueSource;
import ovm.services.bytecode.analysis.AbstractValue;
import ovm.services.bytecode.analysis.Frame;
import ovm.services.bytecode.analysis.State;
import ovm.services.bytecode.analysis.AbstractValue.JumpTarget;
import ovm.util.ArrayList;
import ovm.util.ListIterator;
import s3.core.domain.S3Blueprint;
import s3.services.bootimage.Ephemeral;
import s3.services.bytecode.analysis.S3Frame;
import s3.services.j2c.J2cValue.ClinitExp;
import s3.services.j2c.J2cValue.ConcreteScalar;
import s3.services.j2c.J2cValue.CrossDomainCallExp;
import s3.services.j2c.J2cValue.InternalReference;
import s3.services.j2c.J2cValue.InvocationExp;
import s3.services.j2c.J2cValue.J2cInt;
import s3.services.j2c.J2cValue.J2cLocalExp;
import s3.services.j2c.J2cValue.J2cReference;
import s3.services.j2c.J2cValue.J2cVoid;
import s3.services.j2c.J2cValue.MethodReference;
import s3.services.j2c.J2cValue.SeqExp;
import s3.services.j2c.J2cValue.ValueAccessExp;
import s3.services.j2c.SpecInstantiation.LineNumberParser;
import s3.services.transactions.Transaction;

/**
 * The specification for a basic block
 *
 * @author <a href=mailto://baker29@cs.purdue.edu> Jason </a>
 **/
public class BBSpec extends InstructionVisitor
    implements Ephemeral.Void, JVMConstants
{
    static String I(int i) {
	return (J2cImageCompiler.KEEP_LOGS
		? Integer.toString(i)
		: null);
    }

    public static class Expr {
	J2cValue dest;
	Expr(J2cValue dest) { this.dest = dest; }
	public String toString() {
	    String dstr =  dest == null ? "" : dest + " = ";
	    String cname = getClass().getName();
	    cname = cname.substring(cname.lastIndexOf('$') + 1,
				    cname.length());
	    return cname + ": " + dstr;
	}
    }
    public static class PollCheckExpr extends Expr {
	PollCheckExpr() { super(null); }
    }
    public static PollCheckExpr POLL_CHECK = new PollCheckExpr();
    
    public static class IRExpr extends Expr {
	ValueSource source;
	IRExpr(J2cValue dest, ValueSource source) {
	    super(dest);
	    this.source = source;
	    // OVMBase.OVMassert(source != null);
	}
	public String toString() { return super.toString() + source; }
    }
    public static class BCExpr extends Expr {
	Instruction source;
	J2cValue[] inputs;
	BCExpr(J2cValue dest, Instruction source, J2cValue[] inputs) {
	    super(dest);
	    this.source = source;
	    this.inputs = inputs;
	}
	public String toString() { return super.toString() + source; }
    }
    public static class FlowChangeExpr extends BCExpr {
	FlowChangeExpr(J2cValue dest, Instruction source, J2cValue[] inputs,
		       Frame f) {
	    super(dest, source, inputs);
	}
    }
    
    public static class BRExpr extends FlowChangeExpr {
	int target;
	J2cValue test;
	BRExpr(J2cValue dest, Instruction source, J2cValue[] inputs,
	       Frame f, int target) {
	    super(dest, source, inputs, f);
	    this.target = target;
	}
    }
    public static class SWExpr extends FlowChangeExpr {
	int[] targets;
	int[] keys;
	int dflt;
	SWExpr(J2cValue dest, Instruction source, J2cValue[] inputs, Frame f,
	       int[] targets, int[] keys, int dflt, int delta) {
	    super(dest, source, inputs, f);
	    this.targets = targets;
	    this.keys = keys;
	    this.dflt = dflt + delta;
	    for (int i = 0; i < targets.length; i++)
		targets[i] += delta;
	}
    }
    public static class TryExpr extends Expr {
	TryExpr() { super(null); }
    }
    public static class CatchExpr extends Expr {
	S3Blueprint catchType;
	ExceptionHandler handler;
	boolean first;

	CatchExpr(S3Blueprint catchType,
		  ExceptionHandler handler,
		  boolean first) {
	    super(null);
	    this.catchType = catchType;
	    this.handler = handler;
	    this.first = first;
	}
    }


    J2cValue[] stackInputs = J2cValue.EMPTY_ARRAY;
    J2cValue[] stackOutputs = J2cValue.EMPTY_ARRAY;
    boolean fallsThrough;	// not needed once next filled in
    Expr[] code;
    CatchExpr[] catches;
    int[] next;			// successor PCs

    public boolean fallsThrough() { return fallsThrough; }

    /*
     * For the purpose of debugging, we should really create a giant
     * disassembly file, record both the pc and line number in
     * comments and issue either no #line directives at all, or ones
     * that point to either source or disassembly files.
     *
     * We should also resolve source packages/file names to absolute(?)
     * paths with getResource(), and decode aspectJ file/line
     * information.
     */
    int[] pcDelta;		// (code offset x bytecode PC)
    LineNumberParser parser;	// maps PCs to line numbers
    int startPC;		// may not be pc of first expr, since
				// some instructions are compiled down
				// to nothing
    public int getStartPC() { return startPC; }

    // NOTE: not thread-safe.  We use these static variables to build
    // up each BBSpec
    static final ArrayList codeBuffer = new ArrayList();
    static final ArrayList catchBuffer = new ArrayList();

    boolean isXDCall(ValueSource _exp) {
	if (Transaction.the().ignoreLogCall(_exp)) return false;
	if (ctx.domain != ctx.executiveDomain
	    && _exp instanceof InvocationExp)
	{
	    InvocationExp exp = (InvocationExp) _exp;
	    if (exp.target instanceof MethodReference) {
		MethodReference mr = (MethodReference) exp.target;
		return mr.method.getDeclaringType().getDomain().isExecutive();
	    }
	} 
	return false;
    }
    
    // these static variables are used while building up method's
    // cfg.  After the cfg is built, we no longer need to maintain one
    // copy of these variables, much less one for each BB
    static boolean done = false;
    static SpecInstantiation translator;
    static Context ctx;
    static Frame frame;
    static LocalAllocator allocator;

    static ArrayList spillTemp = new ArrayList();

    AssignmentExp[] spillReferences() {
	if (!ctx.gcSupport)
	    return null;
	
	J2cValue terminator = translator.getOuts()[0];
	int h = frame.getStackHeight();
	int nSpills = 0;

	for (int i = 0, depth = h - 1; i < h; i++, depth--) {
	    J2cValue v = (J2cValue) frame.peek(depth);
	    if (v == null)
		continue;
	    if (v == terminator)
		break;
	    if (!v.isConcrete()
		&& (allocator.bpFlavor(v.getBlueprint(ctx.domain))
		    == LocalAllocator.REF_VAR))
	    {
		J2cValue tmp = allocator.allocateSpill(v, nSpills++);
		addExpr(new IRExpr(null, new AssignmentExp(tmp, v)));
		// System.err.println("spilling " + v + " to " +  tmp);
		spillTemp.add(new AssignmentExp(v, tmp));
	    }
	}
	if (nSpills == 0)
	    return null;
	AssignmentExp[] ret = new AssignmentExp[nSpills];
	spillTemp.toArray(ret);
	spillTemp.clear();
	return ret;
    }

    void reloadReferences(ValueSource[] reloads_) {
	if (reloads_ == null)
	    return;
	for (int i = 0; i < reloads_.length; i++)
	    addExpr(new IRExpr(null, reloads_[i]));
    }
    
    J2cValue findVar(int idx, int pc) {
        J2cValue content = (J2cValue) translator.getFrame().load(idx);
        String name = translator.getVariableName(idx, pc);
        return allocator.allocateLocal(content, idx, name); 
    }

    AssignmentExp[] reloads;

    static final J2cValue j2cInvokeTarget 
        = J2cValue.makeSymbolicReference("j2cInvoke");
    static final J2cValue runTarget = J2cValue.makeSymbolicReference("run");

    J2cValue mungeValue(Value _v) {
	J2cValue v = (J2cValue) _v;
	return v.copy(mungeExpr(v.source));
    }
    ValueSource mungeExpr(ValueSource src) {
	if (src == null) {
	    return null;
        } else if (src instanceof InvocationExp) {
            InvocationExp call = (InvocationExp) src;
            if (call.target instanceof MethodReference
                || call.target == j2cInvokeTarget
                || call.target == runTarget) {
                reloads = spillReferences();
		if (isXDCall(src)) {
		    allocator.hasXDCalls = true;
		    return new CrossDomainCallExp(src);
		}
	    }
        }
	else if (src instanceof CrossDomainCallExp) {
	    reloads = spillReferences();
	}
        else if (src instanceof J2cLocalExp) {
            J2cValue var = findVar(((J2cLocalExp) src).num, translator.getPC());
            return new ValueAccessExp(var);
        }
        else if (src instanceof LocalStore) {
            LocalStore st = (LocalStore) src;
            // local variable ranges start AFTER the initial store instruction
            J2cValue var = findVar(st.index.intValue(), translator.getPC() + 1);
            J2cValue v = ((J2cValue) st.value);
            if (v.kind == J2cValue.NOVAR)
                // FIXME: it must be an IINC.  see below for quick hack
                v = mungeValue(v);
            return new AssignmentExp(var, v);
        }
        else if (src instanceof BinExp) {
            BinExp op = (BinExp) src;
            J2cValue lhs =  (J2cValue) op.lhs;
            if (lhs.kind == J2cValue.NOVAR) {
                return new BinExp(mungeValue(lhs), op.operator, op.rhs);
            }
        }
	// 
	else if (src instanceof MemExp) {
	    MemExp hdrAccess = (MemExp) src;
	    // Special case for getReference().
	    // FIXME: goes away when VM_Address <=> Oop conversions fixed?
	    if (hdrAccess.addr.source instanceof ValueAccessExp) {
		// For whatever reason, we have inferred that a stack
		// slot is a VM_Address, but is is actually an Oop.
		// We wrap the VM_Address value in a ValueAccessExp to
		// give it the appropriate type.
		//
		// FIXME: are there multiple ways to encode a cast?
		return new MemExp(mungeValue(hdrAccess.addr),
				  hdrAccess.offset);
	    }
	}
	else if (src instanceof IfCmd) {
	    // pick out CSA calls such as generateThrowable,
	    // resolveUTF, and initializeBlueprint
	    IfCmd test = (IfCmd) src;
	    if (test.ifFalse == null)
		return new IfCmd(test.cond, mungeValue(test.ifTrue));
	}
	else if (src instanceof SeqExp) {
	    // pick out initializeBlueprint from a conditional block 
	    SeqExp seq = (SeqExp) src;
	    if (seq.v.length == 2) {
		return new SeqExp(mungeValue(seq.v[0]), seq.v[1]);
	    }
	}
	else if (src instanceof AssignmentExp) {
	    AssignmentExp asgn = (AssignmentExp) src;
	    // Pick out string resolution calls inside an IfCmd
	    if (asgn.dest instanceof ConcreteScalar
		&& asgn.src.source instanceof InvocationExp) {
		reloads = spillReferences();
		allocator.hasXDCalls = true;
		return new CrossDomainCallExp(asgn);
	    }
	}
	return src;
    }
    
    void addExpr(Expr e) {
        reloads = null;
	if (allocator != null && e instanceof IRExpr) {
            IRExpr ie = (IRExpr) e;
            ie.source = mungeExpr(ie.source);
        }
	translator.logShort("adding ", e, " to ", I(startPC));
	codeBuffer.add(e);
	reloadReferences(reloads);
    }
    void addCatch(boolean offEnd, CatchExpr e) {
	if (!offEnd)
	    addExpr(e);
	else {
	    translator.logShort("adding ", e, " to ", I(startPC));
	    catchBuffer.add(e);
	}
    }
    
    // FIXME: this is only called in the visit(POLLCHECK) method.  so
    // does this really have to be a separate method?  -- FP
    void addPollCheck() {
	ValueSource[] reloads_ = spillReferences();
	addExpr(POLL_CHECK);
	reloadReferences(reloads_);
    }

    static ArrayList seenCatchTypes = new ArrayList();
    
    BBSpec(SpecInstantiation translator, State st, LocalAllocator allocator,
	   int startPC, int endPC,
	   ListIterator tryStart, ListIterator tryEnd) {
	super(InstructionBuffer.wrap(translator.getCode(),
				     translator.getSelector(),
				     translator.getConstantPool()));
	parser = translator.parser;
	this.startPC = startPC;
	BBSpec.allocator = allocator;
	Frame f = st.getFrameAt(startPC);
	int nf = st.getFrameCountAt(startPC);
	int h = f.getStackHeight();

	if (h > 0) {
	    stackInputs = new J2cValue[h];
	    for (int i = 0; i < h; i++) {
		stackInputs[i] = (J2cValue) f.peek(i);
		// dammit! wide values!
		translator.logShort("input[", I(i), "] = ",
				    stackInputs[i]);
		if (stackInputs[i] == null)
		    continue;
		if (nf > 1 && !(stackInputs[i] instanceof JumpTarget)) {
		    translator.logShort("merging at ",
					translator.getSelector(),
					":", I(startPC));
		    State.Iterator it = st.iterator(startPC);
		    for (Frame f1 = it.currentFrame(); f1 != null;
			 f1 = it.nextFrame()) {
			stackInputs[i] =
			    (J2cValue) stackInputs[i].merge(f1.peek(i));
			translator.logShort("input[", I(i), "] = ",
					    stackInputs[i]);
		    }
		}
		stackInputs[i] = stackInputs[i].allocate
		    (allocator.genSym("stack"), J2cValue.MERGED_STACK_SLOT,
		     -1, i);
	    }
	}

	frame = f = f.cloneFrame();

	if (nf > 1) {
	    int max = ((S3Frame) frame).getMaxLocals();
	    State.Iterator it = st.iterator(startPC);
	    for (Frame f1 = it.currentFrame(); f1 != null;
		 f1 = it.nextFrame()) {
		for (int lidx = 0; lidx < max; lidx++) {
		    AbstractValue v1 = frame.load(lidx);
		    if (v1 == null || v1.isInvalid() || v1.isJumpTarget())
			continue;
		    AbstractValue v2 = f1.load(lidx);
		    if (v2 != null)
			v2 = v1.merge(v2);
		    if (v2 == J2cValue.INVALID)
			v2 = null;
		    frame.store(lidx, v2);
		}
	    }
	}

	// Shit.  Manually merge stack for this frame.
	if (nf > 0 && h > 0) {
	    for (int i = 0; i < h; i++)
		f.pop();
	    for (int i = h - 1; i >= 0; i--)
		f.push(stackInputs[i]);
	}

	// temp array, but we like to log the start pc of a block...
	int[] pcd = new int[2 * (endPC + 1 - startPC)];
	pcd[0] = 0;
	pcd[1] = startPC;	// not strictly needed...
	int pcdix = 2;
	
	buf.position(startPC);
	translator.logShort("EXTRACT block@", I(startPC));
	done = false;
	BBSpec.translator = translator;
	ctx = translator.ctx;
	Instruction last = null;
	ExceptionHandler nextStart = Exceptions.next(tryStart);
	ExceptionHandler nextEnd = Exceptions.next(tryEnd);
	if (nextStart != null || nextEnd != null)
	    translator.logShort("starting block ", I(startPC),
				" next handler start at ", nextStart,
				" next handler end ",  nextEnd);

	while (!done) {
	    Instruction i = buf.get();
	    int pc = buf.getPC();
	    boolean atEnd = pc >= endPC;
	    int off = codeBuffer.size();
	    translator.logShort("EXTRACT ", i, "@", I(pc));
	    if (nextEnd != null && nextEnd.getEndPC() == pc) {
		S3Blueprint bp =
		    translator.resolveTypeName(nextEnd.getCatchTypeName());
		addCatch(atEnd, new CatchExpr(bp, nextEnd, true));
		seenCatchTypes.add(bp);
		int lastStartPC = nextEnd.getStartPC();
		for (nextEnd = Exceptions.next(tryEnd);
		     nextEnd != null && nextEnd.getEndPC() == pc;
		     nextEnd = Exceptions.next(tryEnd)) {
		    boolean isFirst = lastStartPC != nextEnd.getStartPC();
		    bp = translator.resolveTypeName(nextEnd.getCatchTypeName());
		    if (isFirst)
			seenCatchTypes.clear();
		    else if (seenCatchTypes.contains(bp))
			// FIXME: should consider supertypes
			continue;
		    seenCatchTypes.add(bp);

		    addCatch(atEnd, new CatchExpr(bp, nextEnd, isFirst));
		    lastStartPC = nextEnd.getStartPC();
		}
		seenCatchTypes.clear();
	    }
	    if (atEnd)
		break;
	    // Some instructions don't result in any code at all.
	    if (pcd[pcdix-2] == off)
		pcd[pcdix-1] = pc;
	    else {
		pcd[pcdix++] = codeBuffer.size();
		pcd[pcdix++] = pc;
	    }
	    if (nextStart != null && nextStart.getStartPC() == pc) {
		addExpr(new TryExpr());
		int lastEnd = nextStart.getEndPC();
		translator.logShort(getSelector(), ": first try for ",
				    nextStart);
		for (nextStart = Exceptions.next(tryStart);
		     nextStart != null && nextStart.getStartPC() == pc;
		     nextStart = Exceptions.next(tryStart)) {
		    if (nextStart.getEndPC() != lastEnd) {
			translator.logShort(getSelector(), ": next try for ",
					    nextStart);
			addExpr(new TryExpr());
			lastEnd = nextStart.getEndPC();
		    }
		    else
			translator.logShort(translator.getSelector(),
					    ": no try for ",
					    nextStart);
		}
		translator.logShort("advanced start to ", nextStart);
	    }
	    translator.executionTerminates = false;
	    translator.setFrameAndPC(f, buf.getPC());
	    visitAppropriate(i);
	    last = i;
	}
	int pc = buf.getPC();
	// Only close out try blocks at the basic-block boundary if
	// there are no more basic blocks left.  
	if (pc == buf.limit() && nextEnd != null && nextEnd.getEndPC() == pc) {
	    S3Blueprint bp =
		translator.resolveTypeName(nextEnd.getCatchTypeName());
	    boolean offEnd = true;
	    seenCatchTypes.add(bp);
	    addCatch(offEnd, new CatchExpr(bp, nextEnd, true));
	    int lastStartPC = nextEnd.getEndPC();
	    for (nextEnd = Exceptions.next(tryEnd);
		 nextEnd != null && nextEnd.getEndPC() == pc;
		 nextEnd = Exceptions.next(tryEnd)) {
		boolean isFirst = lastStartPC != nextEnd.getStartPC();
		bp = translator.resolveTypeName(nextEnd.getCatchTypeName());
		if (isFirst)
		    seenCatchTypes.clear();
		else if (seenCatchTypes.contains(bp))
		    continue;
		seenCatchTypes.add(bp);
		addCatch(offEnd, new CatchExpr(bp, nextEnd, isFirst));
		lastStartPC = nextEnd.getStartPC();
	    }
	    seenCatchTypes.clear();
	}

	if (nextStart != null)
	    tryStart.previous();
	if (nextEnd != null)
	    tryEnd.previous();
	
	// dump the code
	code = new Expr[codeBuffer.size()];
	codeBuffer.toArray(code);
	codeBuffer.clear();

	catches = new CatchExpr[catchBuffer.size()];
	catchBuffer.toArray(catches);
	catchBuffer.clear();

	// Save PC, file, line information.  Leave half an empty entry
	// for expressions in the middle of endPC
	pcDelta = new int[pcdix + 1];
	System.arraycopy(pcd, 0, pcDelta, 0, pcdix);
	pcDelta[pcdix] = -1;

	if (f.getStackHeight() > 0) {
	    stackOutputs = new J2cValue[f.getStackHeight()];
	    for (int i = 0; i < stackOutputs.length; i++) {
		stackOutputs[i] = (J2cValue) f.peek(i);
		translator.logShort("output[", I(i), "] = ",
				    stackOutputs[i]);
	    }
	}
	fallsThrough = !(translator.executionTerminates
			 || (last instanceof Instruction.UnconditionalJump)
			 || (last instanceof Instruction.Switch)
			 || (last instanceof Instruction.FlowEnd)
			 || (last instanceof Instruction.RET));
	initsHere.clear();
    }

    public String toString() { return "BLOCK@" + pcDelta[1]; }

    public J2cValue[] getInputs(int count) {
	if (count == 0)
	    return J2cValue.EMPTY_ARRAY;
	J2cValue[] ret = new J2cValue[count];
	J2cValue[] in = translator.getIns();
	for (int i = 0; i < count; i++)
	    ret[i] = in[i];
	return ret;
    }

    static final ArrayList initsHere = new ArrayList();
    static final boolean CXX_THROW = true;

    /**
     * J2c really needs to be refactored.  Any number of optimizations
     * are performed inside SpecInstantation, but there are others,
     * such as the throw conversion below, that can only be
     * implemented after SpecInstantiation has been run to a fixpoint.
     **/
    public static class Optimizer extends Runabout {
	ValueSource retVs;

	ValueSource run(ValueSource vs) {
	    //ValueSource outer = retVs;
	    retVs = vs;
	    visitAppropriate(vs);
	    vs = retVs;
	    //retVs = outer;
	    return vs;
	}

	public void visit(ValueSource vs) { }

	public final InternalReference j2c_throw =
	    J2cValue.makeSymbolicReference("j2c_throw");

	public void visit(ClinitExp exp) {
	    if (initsHere.contains(exp.bp))
		retVs = null;
	    else {
		initsHere.add(exp.bp);
		J2cInt cidxv =
		    J2cValue.makeIntConstant(TypeCodes.INT, exp.bp.getCID());
		J2cInt idxv  =
		    J2cValue.makeIntConstant(TypeCodes.INT, exp.bp.getUID());
		Value test = new J2cInt(new InvocationExp("needs_init",
							  cidxv, idxv,
							  ctx.intBP),
					TypeCodes.INT);
		Value go = new J2cVoid(exp.csaCall);
		Value set = new J2cVoid(new InvocationExp("did_init",
							  cidxv, idxv,
							  null));
		Value then = new J2cVoid(new SeqExp(go, set));
		retVs = new IfCmd(test, then);
	    }
	}
	    
	public void visit(InvocationExp exp) {
	    if (CXX_THROW && exp.target == j2c_throw) {
		// Optimize j2c_throw() calls to throw statements
		// if:
		// 1. We know the exact type of the exeception
		//    from local dataflow, or
		// 2. We know the only possible type of the
		//    exception from global analysis.
		// In 
		J2cReference ex = (J2cReference) exp.args[0];
		if (ex.t.exactTypeKnown()) {
		    retVs = new InvocationExp("throw", ex, exp.rt);
		} else if (ctx.anal != null) {
		    Blueprint[] subs
			= ctx.anal.concreteSubtypesOf(ex.getBlueprint());
		    if (subs != null && subs.length == 1) {
			if (subs[0] != ex.getBlueprint())
			    ex = new J2cReference(new ConversionExp(ex),
						  (S3Blueprint) subs[0]);
			retVs = new InvocationExp("throw", ex, exp.rt);
		    }
		}
		if (retVs != exp && ex.t.includesNull()) {
		    // FIXME: Maybe it would be easier to throw in
		    // null checks as needed, and eliminate them
		    // in a recursive call to optimize.
		    J2cValue nullCk
			= new J2cVoid(new InvocationExp
				      ("NULLCHECK", ex, ctx.voidBP));
		    retVs = new SeqExp(nullCk, new J2cVoid(retVs));
		}
	    }
	}
    }

    Optimizer opt = new Optimizer();

    void walkEvals() {
	J2cValue[] eval = translator.evals;
	for (int i = 0; i < eval.length; i++) {
	    // all done
	    if (eval[i] == J2cValue.TERMINATOR)
		break;
	    // SpecInstantiation eliminated this eval
	    if (eval[i] == J2cValue.NULL)
		continue;
	    // check for throw optimization and redundant ClinitExps
	    ValueSource vs = opt.run(eval[i].source);
	    if (vs != null)
		addExpr(new IRExpr(null, vs));
	}
    }

    public void visit(Instruction insn) {
	translator.visitAppropriate(insn);
	//J2cValue[] in_ = translator.getIns();
// 	for (int i = 0; i < in.length; i++) {
// 	    if (in[i] == J2cValue.TERMINATOR)
// 		break;
// 	    else if (in[i].kind != J2cValue.NOVAR)
// 		in[i].source = null;
// 	}
	walkEvals();
	J2cValue[] out = translator.getOuts();
	for (int i = 0; i < out.length; i++) {
	    if (out[i] == J2cValue.TERMINATOR)
		break;
	    else if (out[i] == null)
		continue;
	    else if (out[i].source == null) {
		if (!out[i].isConcrete())
		    OVMBase.fail("abstract value " + out[i]
				 +" has no concrete value or expression");
	    } else if (isXDCall(out[i].source)) {
		J2cValue var = out[i].allocate(allocator.genSym("_stack"),
					       J2cValue.STACK_SLOT,
					       -1, i);
		addExpr(new IRExpr(var, null));
		allocator.hasXDCalls = true;
		addExpr(new IRExpr
			(null,
			 new CrossDomainCallExp
			 (new AssignmentExp(var,
					    var.copy(var.source)))));
	    } else {
		addExpr(new IRExpr(out[i].allocate(allocator.genSym("_stack"),
						   J2cValue.STACK_SLOT,
						   -1, i),
				   out[i].source));
	    }
	}
	done = translator.executionTerminates;
    }

    // pick up <clinit> calls in shared-state loads
    public void visit(Instruction.ConstantPoolLoad insn) {
	translator.visitAppropriate(insn);
	walkEvals();
    }
    // Bookkeeping instructions that need not generate any IR
    public void visit(Instruction.ConstantLoad i) {
	translator.visitAppropriate(i);
    }
    public void visit(Instruction.StackManipulation i) {
	translator.visitAppropriate(i);
    }
    // Instructions that either terminate a basic block, or are not
    // completely described by the IR
    public void visit(Instruction.ReturnValue i) {
	visit((Instruction) i);
	addExpr(new BCExpr(null, i, getInputs(1)));
	done = true;
    }
    public void visit(Instruction.RETURN i) {
	visit((Instruction) i);
	addExpr(new BCExpr(null, i, getInputs(0)));
	done = true;
    }
    public void visit(Instruction.ATHROW i) {
	visit((Instruction) i);
	done = true;
    }
    // This is a bit of a problem.  Subroutines get multiple Frames if
    // they are called with different local variables.  These frames
    // should really be merged, and the abstract value on the stack
    // should tell us what successors we have.
    public void visit(Instruction.RET i) {
	visit((Instruction) i);
	int vidx = i.getLocalVariableOffset(buf);
	J2cValue[] ins = new J2cValue[] {
	    allocator.allocateLocal(translator.getIns()[0],
				    vidx,
				    "_local" + vidx)
	};
	addExpr(new BCExpr(null, i, ins));
	done = true;
    }

    public void visit(Instruction.Switch i) {
	// must decode targets and keys
        int[] target = i.getTargets(buf);
        int dflt = i.getDefaultTarget(buf);
	visit((Instruction) i);
	addExpr(new SWExpr(null, i, getInputs(1),
			   translator.getFrame(),
			   target,
			   i.getIndexForTargets(buf),
			   dflt,
			   getPC()));
	done = true;
    }
    public void visit(Instruction.UnconditionalJump i) {
	visit((Instruction) i);
	addExpr(new BRExpr(null, i, getInputs(0),
			   translator.getFrame(),
			   getPC() + i.getTarget(buf)));
	done = true;
    }

    public void visit(Instruction.ConditionalJump i) {
	visit((Instruction) i);
	J2cValue test = translator.j2cCanonicalize(i.getControlValue());
	addExpr(new BRExpr(null, i, new J2cValue[] { test },
			   translator.getFrame(),
			   getPC() + i.getBranchTarget(buf)));
    }
    
    public void visit(Instruction.POLLCHECK i) {
        addPollCheck();
    }
}
