package s3.services.j2c;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.StringTokenizer;

import ovm.core.OVMBase;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.domain.Blueprint;
import ovm.core.domain.Field;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
import ovm.core.domain.ConstantResolvedInstanceMethodref;
import ovm.core.domain.ConstantResolvedStaticMethodref;
import ovm.core.domain.ConstantResolvedStaticFieldref;
import ovm.core.execution.NativeConstants;
import ovm.core.repository.Attribute;
import ovm.core.repository.ConstantPool;
import ovm.core.repository.Descriptor;
import ovm.core.repository.ExceptionHandler;
import ovm.core.repository.Repository;
import ovm.core.repository.RepositoryString;
import ovm.core.repository.RepositoryUtils;
import ovm.core.repository.TypeCodes;
import ovm.core.repository.UnboundSelector;
import ovm.core.repository.Attribute.*;
import ovm.core.services.memory.VM_Address;
import ovm.services.bytecode.Instruction;
import ovm.services.bytecode.InstructionBuffer;
import ovm.services.bytecode.JVMConstants;
import ovm.services.bytecode.SpecificationIR.*;
import ovm.services.bytecode.analysis.AbstractValue;
import ovm.services.bytecode.analysis.Frame;
import ovm.services.bytecode.analysis.Heap;
import ovm.services.bytecode.analysis.State;
import ovm.services.bytecode.analysis.AbstractValue.WidePrimitive;
import ovm.util.BitSet;
import ovm.util.ByteBuffer;
import ovm.util.HashMap;
import ovm.util.OVMRuntimeException;
import s3.core.domain.S3Blueprint;
import s3.core.domain.S3ByteCode;
import s3.core.domain.S3Constants;
import s3.core.domain.S3Domain;
import s3.core.domain.S3Method;
import s3.services.bytecode.analysis.BranchTargetResolver;
import s3.services.bytecode.verifier.VerificationFixpointIterator;
import s3.services.bytecode.verifier.VerificationInterpreter;
import s3.services.bytecode.analysis.S3Frame;
import s3.services.bytecode.analysis.S3Heap;
import s3.services.bytecode.analysis.FixpointIterator;
import ovm.services.bytecode.analysis.WorkList;
import s3.services.j2c.J2cValue.*;
import s3.services.bootimage.Analysis;
import ovm.core.domain.Method;
import s3.services.bootimage.NativeCallGenerator;
import ovm.services.bytecode.JVMConstants.Throwables;

/**
 * A base class for bytecode analysis and code gen.  This class allows
 * us to track things like constant values and shared state objects as
 * they move through the stack.
 *
 * @author <a href=mailto://baker29@cs.purdue.edu> Jason </a>
 */
public class SpecInstantiation extends VerificationInterpreter
    implements JVMConstants
{
    HashMap valueMap = new HashMap();
    Temp[] temps = new Temp[2];	  // FIXME: max temps + 1
    J2cValue[] evals = new J2cValue[4]; // FIXME: max evals + 1

    S3Blueprint.Scalar homeBp;
    S3Method s3Method;
    S3Constants dcp;
    ExceptionHandler[] handler;
    boolean[] reachable;
    BitSet storedVariables;

    Context ctx;
    
    LineNumberParser parser;
    LocalVariableTable vars;

    S3Blueprint resolveTypeName(TypeName.Compound n) {
	return resolveTypeName(homeBp, n);
    }

    static S3Blueprint resolveTypeName(S3Blueprint homeBp,
				       TypeName.Compound n) {
	try {
	    // FIXME: how should catch types be resolved.  Do I
	    // need to walk up a chain of contexts?
	    Type.Context loader = homeBp.getType().getContext();
	    return (S3Blueprint) loader.getDomain().blueprintFor(n, loader);
	} catch (LinkageException e) {
	    throw e.unchecked();
	}
    }

    String getVariableName(int idx, int pc) {
        String ret = null;
        if (vars != null) {
	    int utf = vars.getVariableNameIndex(idx, pc);
	    if (utf != -1)
		ret = J2cFormat.getUTF(utf);
	}
        return ret == null ? ("_local" + idx) : J2cFormat.encode(ret);
    }
    
    int getLine() {
	return parser.getLineNumber(getPC());
    }
    String getSourceFile() {
	return parser.getSourceFile(getPC());
    }

    abstract class LineNumberParser {
	public abstract int getLineNumber(int pc);
	public abstract String getSourceFile(int pc);
    }
    
    private static String neuterFileName(String file) {
	    for(int i = 0; i < file.length(); i++) {
		char c = file.charAt(i);
		if (Character.isISOControl(c))
		    file = file.replace(c, ' ');
	    }
	    return file;
	}
	    
    LineNumberParser makeLineNumberParser(Type.Scalar t,
					  SourceFile srcFile,
					  final LineNumberTable table)
    {
	if (srcFile == null || table == null)
	    return new LineNumberParser() {
		    public int getLineNumber(int pc) {
			return 0;
		    }
		    public String getSourceFile(int pc) {
			return "<unknown>";
		    }
		};
	
	String srcFileName = J2cFormat.getUTF(srcFile.getSourceFileNameIndex());
	TypeName.Scalar tn = t.getName().asScalar();
	String dir = J2cFormat.getUTF(tn.getPackageNameIndex());
	if (srcFileName.indexOf(';') != -1) {
	    // AspectJ line numbers: source file format is
	    // <original-name>{;<fully-qualified-name>(<offset/1000>k)}*
	    StringTokenizer tok = new StringTokenizer(srcFileName, ";");
	    int nf = tok.countTokens();
	    final int[] offset = new int[nf];
	    final String[] file = new String[nf];
	    offset[0] = 0;
	    file[0] = dir + "/" + tok.nextToken();
	    for (int i = 1; i < nf; i++) {
		String nameAndOffset = tok.nextToken();
		int pivot = nameAndOffset.indexOf('(');
		file[i] = neuterFileName(nameAndOffset.substring(0, pivot));
		int off = 0;
		for (int j = pivot + 1;
		     nameAndOffset.charAt(j) != 'k';
		     j++)
		    off = 10*off + (nameAndOffset.charAt(j) - '0');
		offset[i] = 1000 * off;
	    }
	    return new LineNumberParser() {
		    public int getLineNumber(int pc) {
			int ajLine = table.getLineNumber(pc);
			int next = 1;
			while (next < offset.length) {
			    if (offset[next] > ajLine)
				break;
			    next++;
			}
			return ajLine - offset[next - 1];
		    }
		    public String getSourceFile(int pc) {
			int ajLine = table.getLineNumber(pc);
			int next = 1;
			while (next < offset.length) {
			    if (offset[next] > ajLine)
				break;
			    next++;
			}
			return file[next - 1];
		    }
		};
	} else {
	    final String fqName = dir + "/" + neuterFileName(srcFileName);
	    return new LineNumberParser() {
		    public int getLineNumber(int pc) {
			return table.getLineNumber(pc);
		    }
		    public String getSourceFile(int _) {
			return fqName;
		    }
		};
	}
    }

    public J2cValue[] getIns() { return (J2cValue[]) inputRegister; }
    public J2cValue[] getOuts() { return (J2cValue[]) outputRegister; }

    // FIXME: We can't get an upper bound on the stack height
    // of rewritten code until the dangling symbolic
    // references are cleaned up.
    public int getMaxStack() {
        S3ByteCode b = (S3ByteCode) s3Method.getCode(S3ByteCode.KIND);
        return (b.getMaxStack() + 3) * 2;
    }
    public int getMaxLocals() {
        S3ByteCode b = (S3ByteCode) s3Method.getCode(S3ByteCode.KIND);
        return b.getMaxLocals() + 1;
    }
    
    SpecInstantiation(Context ctx, 
		      S3Method m, 
		      ExceptionHandler[] handlers) {
	super(InstructionBuffer.wrap(m.getCode(S3ByteCode.KIND)),
	      J2cValue.bogusFactory);
	this.handler = handlers;
	if (handlers == null)
	    throw new NullPointerException("handlers may not be null");

	clearLog();
	inputRegister = new J2cValue[inputRegister.length];
	outputRegister = new J2cValue[outputRegister.length];
	this.s3Method = m;
	this.ctx = ctx;
	S3ByteCode cf
	    = (S3ByteCode) m.getCode(S3ByteCode.KIND);
	reachable = new boolean[cf.getBytes().length];
	// FIXME: not in Method, overly wide return type?
	Type.Scalar declaringType = (Type.Scalar) m.getDeclaringType();
	homeBp = (S3Blueprint.Scalar) blueprintFor(declaringType);
	if (homeBp.isSharedState())
	    homeBp = (S3Blueprint.Scalar) homeBp.getInstanceBlueprint();
	dcp = (S3Constants) cf.getConstantPool();
	storedVariables = new BitSet(getMaxLocals());

        Attribute[] att = declaringType.getRepositoryClass().getAttributes();
	SourceFile sourceFile 
            = (SourceFile) getAttribute(ctx, "SourceFile", att);
        att = cf.getAttributes();
        vars = (LocalVariableTable) getAttribute(ctx, "LocalVariableTable", att);
	LineNumberTable lineNo = 
            (LineNumberTable) getAttribute(ctx, "LineNumberTable", att);
	parser = makeLineNumberParser(declaringType, sourceFile, lineNo);
    }

    Attribute getAttribute(Context _, String name, Attribute[] att) {
        int idx = Context.getUTF(name);
        for (int i = 0; i < att.length; i++)
            if (att[i] != null && att[i].getNameIndex() == idx) {
        	return att[i];
            }
        return null;
    }

    public class SIFrame extends S3Frame {
	public Frame make(AbstractValue[] opStack,
			  AbstractValue[] locals,
			  int stackHeight,
			  Heap heap) {
	    return new SIFrame(opStack, locals, stackHeight, heap);
	}
	public SIFrame(int maxStack, int maxLocals,
		       Heap heap) {
	    super(maxStack, maxLocals, heap);
	}
	private SIFrame(AbstractValue[] opStack,
			AbstractValue[] locals,
			int stackHeight,
			Heap heap) {
	    super(opStack, locals, stackHeight, heap);
	}
	public void store(int index, AbstractValue v) {
	    super.store(index, (v == null
				? null
				: ((J2cValue) v).allocate(getPC(), index,
							  J2cValue.LOCAL)));
	}
    }

    private static final S3Heap.Factory hfactory 
	= new S3Heap.Factory();
    // A renaming in verifier caused a clash here...
    public class J2cFixpointIterator 
	extends VerificationFixpointIterator {
	
	boolean[] handlerBoundary;
	boolean[] isTarget;
	    
	public J2cFixpointIterator() {
	    super(J2cValue.bogusFactory,
		  SpecInstantiation.this,
		  s3Method.getRepositoryMethod(),
		  InstructionBuffer.wrap(s3Method.getCode(S3ByteCode.KIND)));
	    S3ByteCode b = ((S3ByteCode) s3Method.getCode(S3ByteCode.KIND));
	    handlerBoundary = new boolean[b.getBytes().length+1];
	    for (int i = 0; i < handler.length; i++) {
		handlerBoundary[handler[i].getStartPC()] = true;
		handlerBoundary[handler[i].getEndPC()] = true;
	    }
	    this.isTarget = BranchTargetResolver.findTargets(InstructionBuffer.wrap(s3Method.getCode(S3ByteCode.KIND)));
	    this.isTarget[0] = true; // always store initial state 
	}

	/**
	 * Get the next Instruction and update the Frame and PC of the AbstractInterpreter.
	 */
	public Instruction next() {
	    Frame f = ai.getFrame();
	    if (f != null) 
		f.setLastPC(ai.getPC());
	    WorkList.Entry curr = null;
	    while (worklist.hasNext()) {
		curr = worklist.remove();
		final boolean debugMe = false;
		//boolean debugMe = J2cImageCompiler.debugMe && curr.pc == 125;
		if (debugMe) {
		    State.Iterator it = state.iterator(125);
		    for (Frame old = it.currentFrame();
			 old != null;
			 old = it.nextFrame())
			log(f, "merge with existing state", old);
		}
		if (!isTarget[curr.pc] && (curr.tryMerge == WorkList.NOMERGE)) {
		    if (debugMe) logShort("not branch target");
		    break;
		}
		f = merge(curr.frame, 
			  curr.tryMerge == WorkList.MERGECLONE, 
			  curr.pc);
		if (f == null) {
		    if (debugMe) logShort("nothing to do");
		    curr = null; // do not simulate this one again
		}
		else {
		    curr.frame = f;
		    break;
		}
	    }
	    if (curr == null)
		return null;
	    ai.setFrameAndPC(curr.frame, curr.pc);
	    return buf.get(curr.pc);
	}
	
	
	public void add(int pc, Frame f, int merge) {
	    // Treat the next instruction after an try block as the
	    // start of a basic block
	    super.add(pc, f, (merge == WorkList.NOMERGE && handlerBoundary[pc]
			      ? WorkList.MERGECLONE
			      : merge));
	}
		       
		      
	public State getState() { return state; }

	public Frame makeInitialFrame() {
	   // S3ByteCode rbcf 
	//	= (S3ByteCode) s3Method.getCode(S3ByteCode.KIND);
	    Frame f = new SIFrame(getMaxStack(), getMaxLocals(),
                  		  hfactory.makeHeap());
	    int pos = 0;
	    int cnt = 0;
	    int argCount = s3Method.getArgumentCount();
	    if (!s3Method.getMode().isStatic())
		f.store(pos++,
			new J2cReference(null, homeBp, false, true));
	    else
		f.store(pos++,
			new SharedStateReference
			(null,  homeBp.getSharedState()));
	    while (cnt < argCount) {
		try {
		    Type t = s3Method.getArgumentType(cnt++);
		    f.store(pos++, makeValue(null, t, false));
		    if (t.isWidePrimitive())
			f.store(pos++, null);
		} catch (LinkageException e) { throw e.unchecked(); }
	    }
	    // state.addFrameAt(0, f.cloneFrame());
	    return f;
	}

	public class Merger 
	    extends FixpointIterator.MergeStrategyVisitor {
	    public void visit(Instruction i) {
		if (i instanceof Instruction.ExceptionThrower) 
		    addAllExceptionTargets((Instruction.ExceptionThrower)i);

		if (executionTerminates) {
		    executionTerminates = false;
		    return;
		}

		int npc = ai.getPC()+i.size(ai.getPosition());
		add(npc, ai.getFrame(), WorkList.NOMERGE);
	    }

	    // propagate throws accurately, and with a real J2cValue
	    protected void addAllExceptionTargets(Instruction.ExceptionThrower i) {
		Frame f = ai.getFrame();
		TypeName.Scalar[] ex = i.getThrowables();	    
		//S3ByteCode rbcf
		//    = (S3ByteCode) s3Method.getCode(S3ByteCode.KIND);
		int pc = ai.getPC();
// 		if (i instanceof Instruction.INVOKE_SYSTEM)
// 		    logShort("prop INVOKE_SYSTEM@" + pc);
		for (int j=0;j<ex.length;j++) {
		    S3Blueprint thrown;
		    try { thrown = resolveTypeName(ex[j]); }
		    catch (LinkageException.Runtime _) { continue; }
// 		    if (i instanceof Instruction.INVOKE_SYSTEM)
// 			logShort("prop " + thrown);
		    for (int k=0;k<handler.length;k++) {
			ExceptionHandler rh = handler[k];
			if ( (rh.getStartPC() <= pc) &&
			     (rh.getEndPC() >= pc) ) {
			    S3Blueprint caught = resolveTypeName(rh.getCatchTypeName());
			    f = f.cloneFrame();
			    f.clearStack();
			    f.push(new J2cReference
				   (null,
				    (thrown.isSubtypeOf(caught)
				     ? thrown
				     : caught),
				    false,
				    false));
// 			    logShort(pc + " props to " + rh);
			    add(rh.getHandlerPC(), f, WorkList.MERGE);
			}
		    }
		}
	    }
	}

	public FixpointIterator.MergeStrategyVisitor makeStrategyVisitor() {
	    return new Merger();
	}
    }

    // Must be given a name to be public
    public J2cFixpointIterator getFixpointIterator() {
	return new J2cFixpointIterator();
    }

    /*
     * If NULL flows into the receiver position for an invocation or
     * field read, we can't continue analyzing the current basic
     * block, because we don't know what to put on the stack.  We
     * discover this situation somewhere deep in the recursive
     * ValueSource walk, and are left to deal with the consequences
     * later on.
     */

    boolean executionTerminates = false;
    Instruction cur;

    public void visitAppropriate(Object o, Class c) {
	valueMap.clear();
	reachable[getPC()] = true;
	// Align the current instruction here and in the
	// InstructionBuffer.
	// FIXME: why is this only needed in j2c?
	buf.get(getPC());
	cur = (Instruction) o;
	log("initial frame = ", getFrame());
	Arrays.fill(inputRegister, J2cValue.TERMINATOR);
	Arrays.fill(outputRegister, J2cValue.TERMINATOR);
	Arrays.fill(evals, J2cValue.TERMINATOR);
	super.visitAppropriate(o, c);
    }

    static StringBuffer logBuffer = new StringBuffer(2048);

    void dumpLog(PrintStream stream, Throwable reason, String message) {
	if (message != null)
	    stream.println(message);
	if (J2cImageCompiler.KEEP_LOGS)
	    stream.print(logBuffer.toString());
	if (reason != null)
	    reason.printStackTrace(stream);
    }

    void clearLog() { logBuffer.setLength(0); }

    String where(String message) {
	String sel =  s3Method.getSelector().toString();
	String name = cur.getName();
	return("J2C: " + sel + ": " + name
	       + "@" + getPC()
	       + " (line " + getSourceFile() + ":" + getLine() + "): "
	       + message);
    }
    static String I(int i) {
	return (J2cImageCompiler.KEEP_LOGS
		? Integer.toString(i)
		: null);
    }
    final void logShort(String message) {
	if (J2cImageCompiler.KEEP_LOGS) {
	    logBuffer.append(message);
	    logBuffer.append('\n');
	    // System.err.println(message);
	}
    }
    final void logShort(Object o1, Object o2) {
	if (J2cImageCompiler.KEEP_LOGS)
	    logShort(o1.toString() + o2);
    }
    final void logShort(Object o1, Object o2, Object o3) {
	if (J2cImageCompiler.KEEP_LOGS)
	    logShort(o1.toString() + o2 + o3);
    }
    final void logShort(Object o1, Object o2, Object o3, Object o4) {
	if (J2cImageCompiler.KEEP_LOGS)
	    logShort(o1.toString() + o2 + o3 + o4);
    }
    final void logShort(Object o1, Object o2, Object o3, Object o4,
			Object o5) {
	if (J2cImageCompiler.KEEP_LOGS)
	    logShort(o1.toString() + o2 + o3 + o4 + o5);
    }
    final void logShort(Object o1, Object o2, Object o3, Object o4,
			Object o5, Object o6) {
	if (J2cImageCompiler.KEEP_LOGS)
	    logShort(o1.toString() + o2 + o3 + o4 + o5 + o6);
    }
    

    void log(String message) {
	if (J2cImageCompiler.KEEP_LOGS)
	    logShort(where(message));
    }
    final void log(Object o1, Object o2, Object o3) {
	if (J2cImageCompiler.KEEP_LOGS)
	    log(o1.toString() + o2 + o3);
    }
    final void log(Object o1, Object o2) {
	if (J2cImageCompiler.KEEP_LOGS)
	    log(o1.toString() + o2);
    }

    Error die(String message) { return new Error(where(message));  }
    int iDie(String message)            { throw die(message); }
    AbstractValue avDie(String message) { throw die(message); }
    String sDie(String message)         { throw die(message); }

    S3Method devirtualize(S3Blueprint receiver, Method m) {
	try {
	    return (ctx.anal != null
		    ? (S3Method) ctx.anal.devirtualize(receiver, m)
		    : null);
	} catch (ArrayIndexOutOfBoundsException e) {
	    System.err.println("can't find " + m + " in table for "
			       + receiver);
	    throw e;
	}
    }
    int evalCast(S3Blueprint from, S3Blueprint to) {
 	return (ctx.anal != null
 		? ctx.anal.evalCast(from, to)
 		: Analysis.UNKNOWN);
    }
    Blueprint[] concreteSubtypesOf(Blueprint bp) {
	return (ctx.anal != null
		? ctx.anal.concreteSubtypesOf(bp)
		: (bp.isArray() && elementBP(bp) instanceof Blueprint.Primitive
		   ? new Blueprint[] { bp }
		   : null));
    }

    S3Blueprint elementBP(Blueprint bp) {
	while (bp.isArray())
	    bp = bp.asArray().getComponentBlueprint();
	return (S3Blueprint) bp;
    }

    static int subtypeEq = 0;
    static int subtypeArray = 0;
    static int subtypeScalar = 0;
    static int subtypeFull = 0;

    /*
    static {
	new J2cImageCompiler.StatPrinter() {
	    public void printStats() {
		System.err.println("isSubtypeOf optimization");
		System.err.println("All numbers are somewhat more than "
				   + "twice as large as they should be");
		System.err.println("\t" + subtypeEq
				   + " checks for pointer equality");
		System.err.println("\t" + subtypeArray
				   + " array subtype tests");
		System.err.println("\t" + subtypeScalar
				   + " scalar object subtype tests");
		System.err.println("\t" + subtypeFull
				   + " full subtype tests");
	    }
	};
    }
    */

    public class J2cCanonicalizer extends Canonicalizer {
	protected Value origin;
	protected ValueSource source;

	// Methods for converting a SpecificationIR.Value to a
	// J2cValue.  These methods are called after we have tried to
	// constant-fold the value and computed its new ValueSource in
	// source
	public void visit(ConcreteIntValue v) {
	    if (source != null) {
		System.err.println("concrete int has source " + source);
		result = new J2cInt(source, v.getType(), v.intValue());
	    } else {
		result = J2cValue.makeIntConstant(v.getType(), v.intValue());
	    }
	}
	public void visit(IntValue v) {
	    result = new J2cInt(source, v.getType());
	}
	public void visit(LongValue v) {
	    result = new J2cLong(source, (Number) v.concreteValue(),
				 v.getType());
	}
	public void visit(FloatValue v) {
	    result = new J2cFloat(source, (Number) v.concreteValue());
	}
	public void visit(DoubleValue v) {
	    result = new J2cDouble(source, (Number) v.concreteValue());
	}
	public void visit(NullRefValue v) {
	    result = J2cValue.NULL;
	}
	public void visit(J2cValue v) {
	    result = v;
	}
	public void visit(SecondHalf v) {
	    result = null;
	}

	public void visit(Value v) {
	    OVMBase.OVMassert(source != null);
	    result = new J2cVoid(source);
	}
	// Methods for simplifying SpecificationIR.ValueSource
	// expressions and replacing SpecificationIR operands with
	// J2cValue operands.  If we are able to simplyfy the
	// expression, set the AbstractValue field result to the
	// simplified value, otherwise, store a recursively tranformed
	// copy of the ValueSource object in source.
	public void visit(CurrentPC exp) {
	    // Is ths only called during jsr?  If I implement ret as a
	    // computed goto, I need to put a label here

	    result = J2cValue.makeIntConstant(TypeCodes.INT, getPC());
	}
	public void visit(CPAccessExp exp) {
	    J2cValue value = j2cCanonicalize(exp.value);
	    if (value.isConcrete()) {
		int idx = ((J2cInt) value).intValue();
		byte tag = dcp.getTagAt(idx);
		try {
		    switch (tag) {
		    case CONSTANT_SharedState:
			TypeName.Gemeinsam tn = (TypeName.Gemeinsam) dcp.getConstantAt(idx);
			Type.Context tc = dcp.getType().getContext();
			TypeName.Compound bpn = tn.getInstanceTypeName().asCompound();
			Blueprint ibp;
			
			try {
			    ibp = ctx.domain.blueprintFor(tc.typeFor(bpn));
			} catch ( LinkageException le ) {
			    tc = ctx.domain.getApplicationTypeContext();
			    ibp = ctx.domain.blueprintFor(tc.typeFor(bpn));
			}
			Oop oops =  ibp.getSharedState();
			result = new SharedStateReference(null, oops);
			break;
		    case CONSTANT_ResolvedClass:
			result = new BlueprintReference(dcp.resolveClassAt(idx));
			break;
		    case CONSTANT_Reference:
		    case CONSTANT_Binder:
			Oop oops2 = dcp.resolveConstantAt(idx);
			result =  (oops2.getBlueprint().isSharedState()
				   ? new SharedStateReference(null, oops2)
				   : new ConcreteScalar(null, oops2));
			break;
		    case CONSTANT_String:
			RepositoryString rs1
			    = (RepositoryString) dcp.getConstantAt(idx);
			VM_Address rs
			    = VM_Address.fromObject(rs1,
						    ctx.RepositoryStringBP);
			result = new ConcreteScalar(null, rs.asOop(),
						    ctx.stringBP);
			break;
		    case CONSTANT_Integer:
			result = J2cValue.makeIntConstant(TypeCodes.INT, dcp.getValueAt(idx));
			break;
		    case CONSTANT_Long:
			result = new J2cLong(null, (Number) dcp.getConstantAt(idx), TypeCodes.LONG);
			break;
		    case CONSTANT_Float:
			result = new J2cFloat(null, (Number) dcp.getConstantAt(idx));
			break;
		    case CONSTANT_Double:
			result = new J2cDouble(null, (Number) dcp.getConstantAt(idx));
			break;
		    default:
			throw die("unknown cp tag " + tag);		    
		    }		
		} catch (ConstantPool.AccessException e) {
		    throw new OVMRuntimeException("couldn't get "
						  + tag + " constant "
						  + dcp.getConstantAt(idx),
						  e);
		} catch (LinkageException e) {
		    throw new OVMRuntimeException("couldn't get "
						  + tag + " constant "
						  + dcp.getConstantAt(idx),
						  e);
		}
	    }
	    else {
		logShort("unresolved constant pool index ", value);
		source = new CPAccessExp(value);
	    }
	}
	public void visit(SymbolicConstant exp) {
	    source = exp;
	}
	// FIXME: quickened IR doesn't generate FieldAccessExps, why do
	// I bother visiting them?  I need to convert this to a
	// J2cFieldAccessExp anyway!
	// If it is a static final field with a know value, create a
	// J2cValue with the appropriate static information
	public void visit(FieldAccessExp exp) {
	    J2cValue obj = j2cCanonicalize(exp.obj);
	    source =  new FieldAccessExp(exp.selector, obj);
	}
	// umm...  index can certainly be evaled away.  If the method
	// can be be devirtualized, replace the whole thing with the
	// method's j2cName.
	public void visit(LookupExp exp) {
	    // I'm no longer certain 
	    J2cValue bpv = j2cCanonicalize(exp.bp);
	    J2cValue idx = j2cCanonicalize(exp.index);

	    // If this is an interface lookup on a monotonic
	    // interface, make it a virtual lookup
	    if (!idx.isConcrete())
		throw die("lookup variable index " + idx);
	    int index = ((J2cInt)idx).intValue();

	    S3Blueprint bp = ((BlueprintReference) bpv).blueprintValue();
	    S3Method m = bp.lookupMethod(index, exp.tableName);
	    if (m == null)
		throw new LinkageException
		    ("no method found at " + bp + "." + exp.tableName
		     + "[" + index + "]").unchecked();
	    boolean virtual = !bpv.isConcrete();
 	    if (virtual && devirtualize(bp, m) != null) {
 		virtual = false;
 		m = devirtualize(bp, m);
 	    }
	    if (virtual) {
		source =  new J2cLookupExp(bpv, exp.tableName, idx);
		result = new MethodReference(source, m);
		logShort("virtual call to ", m);
	    } else {
		result = J2cValue.makeNonvirtualReference(m);
		logShort(bpv.isConcrete() ? "nonvirtual " : "devirtualized ",
			 J2cFormat.format(m));
	    }
	}
	// should be evaled away
	public void visit(LinkSetAccessExp exp) {
	    // Link sets are gone now.
	    throw new Error();
	}
	// What's this?  It is only used in TABLESWITCH.  I would much
	// rather implement both switch flavors in terms of C++ switch
	// statements.  On the other hand, it is hard to simplify an
	// atomic expression corresponding to a C++ switch statement!
	public void visit(ListElementExp exp) {
	    // exp.list the array of PC values at the end of a
	    // TABLESWITCH instruction.

	    // exp.index is the switch argument

	    // If we can't deal with TABLESWITCH directly, we might as
	    // well compile it down to a computed goto.  And, if we
	    // can't compile it down to a computed goto, we might as
	    // well deal with it directly.

	    J2cValue index = j2cCanonicalize(exp.index);
	    if (index.isConcrete())
		logShort("I know the switch target, but can't do anything about it");
	    source = new ListElementExp(exp.list, index);
	}

	// umm...I guess I will see these
	public void visit(Temp exp) {
	    result = j2cCanonicalize(exp.init);
	    //source = new Temp(exp.type, exp.name, j2cCanonicalize(exp.init));
	}
	// Should these be left standing, or replaced with references
	// into the frame?  They should certainly pick up type
	// information from the frame!
	public void visit(LocalExp exp) {
	    J2cInt v = (J2cInt) j2cCanonicalize(exp.number);
           // int idx = v.intValue();
            source = J2cValue.makeLocalExp(v.intValue());
	}

	public void visit(J2cLocalExp exp) {
	    source = exp;
	}

	public void visit(BinExp exp) {
	    J2cValue lhs = asPrimitive(exp.lhs);
	    J2cValue rhs = asPrimitive(exp.rhs);

	    char lcode = 0xffff;
	    if (exp.lhs instanceof IntValue)
		lcode = ((IntValue) exp.lhs).getType();
	    if (exp.lhs instanceof LongValue)
		lcode = ((LongValue) exp.lhs).getType();
	    else if (exp.lhs instanceof J2cInt)
		lcode = ((J2cInt) exp.lhs).typeCode;
	    else if (exp.lhs instanceof J2cLong)
		lcode = ((J2cLong) exp.lhs).typeCode;
	    
	    if (origin instanceof IntValue) {
		IntValue iorigin = (IntValue) origin;
		J2cInt ilhs = (J2cInt) lhs;
		J2cInt irhs = (J2cInt) rhs;
		if (lhs.isConcrete() && rhs.isConcrete()) {
		    int lv = ilhs.intValue();
		    int rv = irhs.intValue();
		    String op = exp.operator;
		    if ((op == "/" || op == "%") && rv == 0)
			/* don't simplify */;
		    else {
			int value = (op == "+" ? lv + rv
				     : op == "-" ? lv - rv
				     : op == "*" ? lv * rv
				     : op == "/" ? lv / rv
				     : op == "%" ? lv % rv
				     : op == "|" ? (lv | rv)
				     : op == "&" ? (lv & rv)
				     : op == "^" ? (lv ^ rv)
				     : op == "<<" ? lv << rv
				     : (op == ">>"
					? (lcode == TypeCodes.UINT
					   ? (lv >>> rv)
					   : (lv >> rv))
					: iDie("unkonwn operator " + op)));
			result = J2cValue.makeIntConstant(iorigin.getType(), value);
			logShort("constant folding ", exp);
			return;
		    }
		} else if (lhs.isConcrete() || rhs.isConcrete()) {
		    // Handle some important cases in which we can fix
		    // a tighter bound on input expressions.  We must
		    // be careful not to introduce infinite loops into
		    // the abstract interpereter
		    int known = (lhs.isConcrete()
				 ? ilhs.intValue()
				 : irhs.intValue());
		    J2cInt unknown = (lhs.isConcrete() ? irhs : ilhs);
		    String op = exp.operator;
                    if (op == "&" && known == 0) {
                        result = J2cValue.makeIntConstant(TypeCodes.INT, 0);
                    } else if (op == "&" && (known & 0x80000000) == 0) {
			// int x;  x & 5 => J2cInt[0,5]
			// which is what we want, but
			// char x; (int) x & 0x10000 => J2cInt[0,65535]
			// when it is really 0.
			result = new J2cInt(new BinExp(ilhs, "&", irhs),
					    iorigin.getType(),
					    0, known);
			logShort("bounding size of ", result);
			return;
		    }
		    else if (op == ">>" && unknown == ilhs) {
			// We must beware of sign change!
			int min = (lcode == TypeCodes.UINT
				   ? unknown.min >> known
				   : unknown.min >>> known);
			int max = unknown.max >> known;
			if (lcode == TypeCodes.UINT)
			    lhs = new J2cInt(new ConversionExp(lhs), lcode);
			result = new J2cInt(new BinExp(lhs, ">>", rhs),
					    iorigin.getType(),
					    Math.max(Math.min(0, min),
						     ilhs.min),
					    Math.min(Math.max(min, max),
						     ilhs.max));
			logShort("bounding size of ", result);
			return;
		    }
		    else if (op == "%" && unknown == ilhs) {
			int max = Math.abs(known) - 1;
			int min = ilhs.min >= 0 ? 0 : -max;
			result = new J2cInt(new BinExp(ilhs, "%", irhs),
					    iorigin.getType(),
					    min, max);
			logShort("bounding size of ", result);
			return;
		    }
		}
	    }

	    if (lcode == TypeCodes.UINT)
		lhs = new J2cInt(new ConversionExp(lhs), lcode);
	    else if (lcode == TypeCodes.ULONG)
		lhs = new J2cLong(new ConversionExp(lhs), null, lcode);

	    source = new BinExp(lhs, exp.operator, rhs);
	}
	public void visit(UnaryExp exp) {
	    if (exp.operator == "&")
		source = new UnaryExp(exp.operator, j2cCanonicalize(exp.arg));
	    else {
		J2cValue arg = asPrimitive(exp.arg);
		if (exp.operator == "!" && arg.isConcrete()) {
		    int v = ((J2cInt) arg).intValue();
		    result = J2cValue.makeIntConstant(TypeCodes.INT,
						      v == 0 ? 1 : 0);
		    logShort("!", arg, " => ", result);
		} else {
		    if (exp.operator == "!")
			logShort("not a constant: ", arg);
		    source = new UnaryExp(exp.operator, arg);
		}
	    }
	}
	public void visit(ConversionExp exp) {
	    J2cValue before = j2cCanonicalize(exp.before);
	    // Hmm.  Is this safe?   I certainly need to handle
	    // conversions of contants...
	    if (before instanceof J2cInt && origin instanceof IntValue) {
		J2cInt bint = (J2cInt) before;
		char code = ((IntValue) origin).getType();
		if (bint.isConcrete() &&  bint.inRange(code))
		{
		    result = J2cValue.makeIntConstant(code, before.intValue());
		    logShort("constant type conversion ", result);
		    return;
		} else if (code == TypeCodes.INT) {
		    // FIXME: what exactly does this accomplish?
		    result = new J2cInt(before.source, code,
					bint.min, bint.max);
		    return;
		}
	    }
		source = new ConversionExp(before);
	}
	public void visit(ReinterpretExp exp) {
	    source = new ReinterpretExp(j2cCanonicalize(exp.before));
	}
	public void visit(ShiftMaskExp exp) {
	    if (true || NativeConstants.SHIFTS_NEED_MASK)
		source = new ShiftMaskExp(j2cCanonicalize(exp.exponent),
					  j2cCanonicalize(exp.sizeType));
	    else
		// Don't bother allocating a no-op expression
		result = j2cCanonicalize(exp.exponent);
	}
	// Why isn't this an assignment to a LocalAccess?  Oh well,
    // things may be easier this way...
    public void visit(LocalStore exp) {
        source = new LocalStore(j2cCanonicalize(exp.index),
				j2cCanonicalize(exp.value));
    }
	public void visit(AssignmentExp exp) {
	    J2cValue dest = j2cCanonicalize(exp.dest);
	    J2cValue src = j2cCanonicalize(exp.src);

	    // FIXME: VM_Address.putFloat(), VM_Address.putDouble()
	    // are defined in terms of PUTFIELD_QUICK and
	    // PUTFIELD2_QUICK, which operate on ints and longs.
	    if (dest.source instanceof MemExp
		&& dest instanceof J2cInt
		&& src instanceof J2cFloat)
		dest = new J2cFloat(dest.source, null);
	    else if (dest.source instanceof MemExp
		     && dest instanceof J2cLong
		     && src instanceof J2cDouble)
		dest = new J2cDouble(dest.source, null);
	    source = new AssignmentExp(dest, src);
	}
	// FIXME: I need to contant-fold these tests if I want to get
	// anywhere.  Note that some CondExps (bounds checks in
	// particular) use unsigned comparisons
	public void visit(CondExp exp) {
	    // Handle signed comparisons on VM_Word and VM_Address
	    if (exp.operator == "<" || exp.operator == "<="
		|| exp.operator == ">=" || exp.operator == ">"
		|| exp.lhs.isPrimitive())
	    {
		J2cValue lhs = asPrimitive(exp.lhs);
		J2cValue rhs = asPrimitive(exp.rhs);
		
		if (lhs.isConcrete() && (lhs instanceof J2cInt) &&
		    rhs.isConcrete() && (rhs instanceof J2cInt))
		{
		    J2cInt li = (J2cInt) lhs;
		    J2cInt ri = (J2cInt) rhs;
		    boolean v;
		    if (li.typeCode == TypeCodes.UINT) {
			long lv = li.intValue() & 0xffffffffL;
			long rv = ri.intValue() & 0xffffffffL;
			v = (exp.operator == "<"  ? (lv < rv)  :
			     exp.operator == "<=" ? (lv <= rv) :
			     exp.operator == ">=" ? (lv >= rv) :
			     exp.operator == ">"  ? (lv > rv)  :
			     exp.operator == "==" ? (lv == rv) :
			                            (lv != rv));
		    } else {
			int lv = li.intValue();
			int rv = ri.intValue();
			v = (exp.operator == "<"  ? (lv < rv)  :
			     exp.operator == "<=" ? (lv <= rv) :
			     exp.operator == ">=" ? (lv >= rv) :
			     exp.operator == ">"  ? (lv > rv)  :
			     exp.operator == "==" ? (lv == rv) :
			                            (lv != rv));
		    }
		    result = J2cValue.makeIntConstant(TypeCodes.INT,
						      v ? 1 : 0);
		    logShort(lhs, exp.operator, rhs, " => ", result);
		} else
		    source = new CondExp(lhs, exp.operator, rhs);
	    } else if ((exp.operator == "==" || exp.operator == "!=")
		       && exp.lhs.isReference()
		       && exp.rhs.isReference()) {
		J2cValue lhs = asAddress(exp.lhs);
		S3Blueprint lbp = lhs.getBlueprint(ctx.domain);
		J2cValue rhs = asAddress(exp.rhs);
		S3Blueprint rbp = rhs.getBlueprint(ctx.domain);
		
		if (lhs != J2cValue.NULL
		    && rhs != J2cValue.NULL
		    && !lbp.getType().isInterface()
		    && !rbp.getType().isInterface()
		    && !lbp.isSubtypeOf(rbp)
		    && !rbp.isSubtypeOf(lbp)
		    && lbp != ctx.OopBP
		    && lbp != ctx.VM_AddressBP
		    && rbp != ctx.OopBP
		    && rbp != ctx.VM_AddressBP)
		{
		    System.err.println(where("useless pointer comparison"));
		    result = J2cValue.makeIntConstant
			(TypeCodes.BOOLEAN, exp.operator == "==" ? 0 : 1);
		} else {
		    source = new CondExp(lhs, exp.operator, rhs);
		}
	    } else
		source = new CondExp(j2cCanonicalize(exp.lhs), exp.operator,
				     j2cCanonicalize(exp.rhs));
	}
	public void visit(IfExp exp) {
	    if (!checkIf(exp)) {
		J2cValue ifT = j2cCanonicalize(exp.ifTrue);
		J2cValue ifE = j2cCanonicalize(exp.ifFalse);
		// For checkcast and arraystore, we may get
		// (if (null? p) #t #t).  This optimization is not
		// safe if there are side-effects in the condition.
		if (ifT == ifE) {
		    logShort("(if ", exp.cond, "  X X ) => X, namely ",
			     ifT);
		    result = ifT;
		}
		else
		    source = new IfExp(j2cCanonicalize(exp.cond), ifT, ifE);
	    }
	}
	// We get these for header fields we don't know about, such as
	// the monitor or hashcode.
	public void visit(MemExp exp) {
	    J2cValue v = asAddress(exp.addr);
	    J2cValue idx = j2cCanonicalize(exp.offset);
	    
	    source = new MemExp(v, idx);
	    if (v instanceof J2cReference && idx.isConcrete()) {
		S3Blueprint bp = ((J2cReference) v).getBlueprint();
		int offset = ((J2cInt) idx).intValue();

		if (bp == ctx.VM_AddressBP) {
		    // All VM_Address accessors load and store primitives
		    // at offset 0.  The only other thing you can do
		    // with an address is implicitly convert it to an
		    // Oop.
		    if (offset == 0 && !origin.isReference()) {
			if (origin instanceof IntValue)
			    result = new J2cInt(source,
						((IntValue) origin).getType());
			else if (origin instanceof LongValue)
			    result = new J2cLong(source, null, TypeCodes.LONG);
			else
			    throw new RuntimeException
				("bad VM_Address load type: " + origin);
			log("VM_Address operation =>", result);
			return;
		    }  else if (offset < ctx.headerSize) {
			logShort("VM_Address.asOop() detected");
                        v = new J2cReference
                            (new ValueAccessExp(v), ctx.OopBP);
			source = new MemExp(v, idx);
		    } else {
			logShort("VM_Address used as object?");
			// else we must be compiling code that treats
			// VM_Address as an object
		    }
		}

		Field f = bp.fieldAt(offset);
		if (f == null) {
		    result = makeValue(source, ctx.intBP.getType(), true);
		    return;
		}
		Selector.Field fsel = f.getSelector();
		logShort("sel = ", fsel);
		logShort("field = ", f);
		    
		// Basically, the only pointers to concrete values are
		// blueprints shared state objects, and strings.  What
		// happens when we see a final field dereferenced from
		// one of these objects?  We probably don't want to
		// pull it out and indirect through J2cRoots rather
		// than the field itself, but we can at least realize
		// that we know it's exact type and outermost array
		// dimension.  This could be useful when inlining
		// String methods...

		// FIXME:  We need to take a peek at the field to
		// determine its exact type, outermost array
		// dimension, and value if it is primitive.  How is
		// this to be done?  We can't use VM_Address
		// operations during bootup, because the heap hasn't
		// been copied into image memory yet.
		//boolean isConcrete = v.isConcrete() && f.getMode().isFinal();

		S3Blueprint fbp = blueprintFor(f.getDeclaringType());
		J2cFieldAccessExp ref = new J2cFieldAccessExp(v, fbp, fsel);
		try {
		    Type ft= f.getType();
		    result = makeValue(ref, ft, true);
		} catch (LinkageException e) { throw e.unchecked(); }
		logShort("patched field access = ", result);
	    }
	    else
		throw die("bad memexp " + v + " + " + idx);
	}

	public void visit(ArrayAccessExp exp) {
	    // Surround the array access in an apporpriately typed
	    // node!
	    J2cValue arr = j2cCanonicalize(exp.arr);
	    J2cValue idx = j2cCanonicalize(exp.index);
	    source = new ArrayAccessExp(arr, idx);
	    if (arr instanceof J2cArray) {
		S3Blueprint.Array abp
		    = (S3Blueprint.Array) ((J2cArray) arr).getBlueprint();
		S3Blueprint ebp = (S3Blueprint) abp.getComponentBlueprint();
		char tag = ebp.getType().getUnrefinedName().getTypeTag();
		switch (tag) {
		case TypeCodes.ARRAY:
		    result = new J2cArray(source, ebp);
		    break;
		case TypeCodes.OBJECT:
		    result = new J2cReference(source, ebp);
		    break;
		case TypeCodes.LONG:
		    result = new J2cLong(source, null, TypeCodes.LONG);
		    break;
		case TypeCodes.FLOAT:
		    result = new J2cFloat(source, null);
		    break;
		case TypeCodes.DOUBLE:
		    result = new J2cDouble(source, null);
		    break;
		default:
		    // for xALOAD where x strictly< int, the IR
		    // includes the necessary conversion.
		    result = new J2cInt(source, tag);
		}
		    
	    } else
		logShort("array access on non-array value ", arr);
		
	}
	// Can possibly optimize.  To do something useful, I need to
	// expand CHECK_ARRAY_BOUNDS() into IfCmd(..., throwException())
	public void visit(ArrayLengthExp exp) {
	    J2cValue v = j2cCanonicalize(exp.arr);
	    source = new ArrayLengthExp(v);
	    if (v instanceof J2cArray) {
		J2cInt length = ((J2cArray) v).dims[0];
		if (length.isConcrete()) {
		    // Umm, should I always do this transformation?  I
		    // I could if the array's first dimension somehow
		    // pointed back to the array.  But then, how would I
		    // represent an actual access to the length field;
		    result = length;
		    logShort("constant folding ", v, ".length => ", result);
		    return;
		} else if (length.max < Integer.MAX_VALUE) {
		    logShort("bounding ", v, ".length to ", length);
		}
		// always remember that it is non-negative
		result = new J2cInt(source, TypeCodes.INT,
				    length.min, length.max);
	    } else {
		logShort("not an array: ", v);
	    }
	}
	// Can optimize when exact type known
	public void visit(BlueprintAccessExp exp) {
	    // Hmm.  What happens if r has a monomorphic interface type?
	    J2cReference r = (J2cReference) asAddress(exp.ref);
	    if (r.t.exactTypeKnown()) {
		result = new BlueprintReference(r.getBlueprint());
		logShort("statically found blueprint of ", exp.ref);
	    } else {
		source = new BlueprintAccessExp(r);
		result = new BlueprintReference(source, r.getBlueprint());
		logShort("blueprint for ", r, " unknown");
	    }
	}
	// can optimize constant values, such as in invocations
	public void visit(BitFieldExp exp) {
	    J2cValue v = j2cCanonicalize(exp.word);
	    if (v.isConcrete()) {
		int ival = ((J2cInt) v).intValue();
		ival = (ival >> exp.shift) & exp.mask;
		result = J2cValue.makeIntConstant(((IntValue) origin).getType(), ival);
		logShort("computed bit field value ", exp);
	    } else {
		source = new BitFieldExp(v, exp.shift, exp.mask);
		result = new J2cInt(source, ((IntValue) origin).getType(),
				    0, (exp.mask == -1
					? -1 >>> exp.shift
					: exp.mask));
	    }
	}

	private J2cValue getComponentBlueprint(Blueprint _bp) {
	    Blueprint.Array bp = (Blueprint.Array) _bp;
	    S3Blueprint cbp = (S3Blueprint) bp.getComponentBlueprint();
	    return new BlueprintReference(null, cbp);
	}
	
	// CallExp is a hack: It is used to call functions and macros
	// inside the interpreter such as is_subtype_of,
	// CHECK_ARRAY_BOUNDS, INVOKE, and ROLL.  I'm not sure whether
	// it is worth the trouble to eliminate.  It probably is.  As
	// it stands, we have INVOKE_NATIVE calls, INVOKE_SYSTEM
	// calls, INVOKE_CSA calls and CallExp operations.  This is
	// too much!
	public void visit(CallExp exp) {
	    // rolls are handled by abstract interpretation.  Should I
	    // ever see a ROLL() expression?
	    if (exp.fname == "ROLL") {
		logShort("no-op ROLL()");
		result = J2cValue.NULL;
	    }
	    else if (exp.fname == "is_subtype_of") {
		// FIXME: special case where arg0 is concrete?
		BlueprintReference arg0
		    = (BlueprintReference) j2cCanonicalize(exp.args[0]);
		S3Blueprint bp0 = arg0.blueprintValue();
		BlueprintReference arg1
		    = (BlueprintReference) j2cCanonicalize(exp.args[1]);
		S3Blueprint bp1 = arg1.blueprintValue();

		// FIXME: If there is a more complete analysis.
		// Oop is at the wrong spot in the type hierarchy
		int status = ((bp0 == ctx.OopBP || bp0 == ctx.VM_AddressBP)
			      ? Analysis.UNKNOWN
			      : evalCast(bp0, bp1));
		if (status == Analysis.GOOD_CAST) {
// 		    System.err.println(getSourceFile() + ":" + getLine()
// 				       + " good cast "
// 				       + bp0  + " to " + bp1);
		    result = J2cValue.makeIntConstant(TypeCodes.BOOLEAN, 1);
		} else if (status == Analysis.BAD_CAST
			   && (ctx.optimizeBadCastToArray || !bp1.isArray()))
		{
// 		    System.err.println(getSourceFile() + ":" + getLine()
// 				       + " bad cast "
// 				       + bp0 + " to " + bp1);
		    result = J2cValue.makeIntConstant(TypeCodes.BOOLEAN, 0);
		}
		else {
		    Blueprint[] ctypes = concreteSubtypesOf(bp1);
		    if (ctypes != null && ctypes.length == 1) {
			result = new J2cInt(new CondExp
					    (arg0, "==",
					     new BlueprintReference((S3Blueprint) ctypes[0])),
					    TypeCodes.BOOLEAN);
			subtypeEq++;
		    } else if (false && bp1.isArray()) {
			S3Blueprint.Array abp1 = (S3Blueprint.Array) bp1;
			result = new J2cInt(new InvocationExp("subtype_of_array",
							      arg0, arg1,
							      ctx.booleanBP),
					    TypeCodes.BOOLEAN);
			subtypeArray++;
		    } else if (bp1 instanceof Blueprint.Scalar) {
			result = new J2cInt(new InvocationExp("subtype_of_scalar",
							      arg0, arg1,
							      ctx.booleanBP),
					    TypeCodes.BOOLEAN);
			subtypeScalar++;
		    } else {
			source = new InvocationExp
			    (J2cValue.makeSymbolicReference("is_subtype_of"),
			     new Value[] { arg0, arg1 },
			     null);
			subtypeFull++;
		    }
		}
	    }
	    else if (exp.fname == "ARRAY_STORE_INVALID") {
		// Try to eliminate this check.  If we know the exact
		// type of the array, we can go through the standard
		// is_subtype_of optimizations.  Otherwise, we take a
		// much slower path.
		J2cValue _a = j2cCanonicalize(exp.args[0]);
		if (_a == J2cValue.NULL) {
		    result = J2cValue.makeIntConstant(TypeCodes.INT, 0);
		    return;
		}
		J2cArray a = (J2cArray) _a;
		S3Blueprint abp = a.getBlueprint(null);
		Blueprint[] concreteAtype = concreteSubtypesOf(abp);
		J2cValue _o = j2cCanonicalize(exp.args[1]);
		if (_o == J2cValue.NULL) {
		    result = J2cValue.makeIntConstant(TypeCodes.INT, 0);
		    return;
		}
		J2cReference o = (J2cReference) _o;
		ValueSource os = (o.getTypeInfo().exactTypeKnown()
				  ? null
				  : new BlueprintAccessExp(o));
		J2cValue obpr
		    = new BlueprintReference(os, o.getBlueprint(null));
		J2cInt subtypeTest = null;
		if (a.getTypeInfo().exactTypeKnown()) {
		    // We can optimize the test because we know
		    // exactly which blueprints are involved
		    J2cValue arg2 = getComponentBlueprint(abp);
		    subtypeTest = 
			(J2cInt) j2cCanonicalize(new IntValue
						 (TypeCodes.BOOLEAN,
						  new CallExp("is_subtype_of",
							      obpr,
							      arg2)));
		} else if (concreteAtype != null
			   && concreteAtype.length == 1) {
		    J2cValue arg2
			= getComponentBlueprint(concreteAtype[0]);
		    subtypeTest = 
			(J2cInt) j2cCanonicalize(new IntValue
						 (TypeCodes.BOOLEAN,
						  new CallExp("is_subtype_of",
							      obpr,
							      arg2)));
		}
		if (subtypeTest != null) {
		    if (o.getTypeInfo().includesNull()) {
			J2cValue one =
			    J2cValue.makeIntConstant(TypeCodes.BOOLEAN, 1);
			ValueSource vs = new IfExp(o, subtypeTest, one);
			subtypeTest = (J2cInt) j2cCanonicalize
			    (new IntValue(TypeCodes.BOOLEAN, vs));
		    }
		    result = j2cCanonicalize
			(new IntValue(TypeCodes.BOOLEAN,
				      new UnaryExp("!", subtypeTest)));
		} else {
		    source = new InvocationExp("ARRAY_STORE_INVALID",
					       a, o, null);
		}
	    }
	    else if (exp.fname == "INVOKE") {
		J2cValue target = j2cCanonicalize(exp.args[1]);
		J2cValue[] args = new J2cValue[getArgumentRegisterCount()];
		for (int i = 0; i < args.length; i++)
		    args[i] = (J2cValue) getArgumentRegisterAt(i);
		S3Method m = ((MethodReference) target).method;
		try {
		    Type rt = m.getReturnType();
		    S3Domain d = (S3Domain) rt.getContext().getDomain();
		    S3Blueprint rbp = (S3Blueprint) d.blueprintFor(rt);
		    S3Blueprint[] at = new S3Blueprint[args.length];
		    
		    // devirtualization may lead to wider receiver types!
		    try {
			at[0] = (S3Blueprint) d.blueprintFor(m.getDeclaringType());
		    }
		    catch (LinkageException e) { throw e.unchecked(); }
		    for (int ati = 1, ai = 0;
			 ati < args.length;
			 ati++) {
			// don't fill in type for 2nd half values
			if (args[ati] == null)
			    continue;
			try {
			    Type t = m.getArgumentType(ai++);
			    at[ati] = (S3Blueprint) d.blueprintFor(t);
			} catch (RuntimeException e) {
			    logShort("error getting arguments to ", m);
			    throw e;
			}
		    }
		    if (m == ctx.processThrowable)
			source = new InvocationExp("j2c_throw", args[1], rbp);
		    else
			source = new InvocationExp(target, args, rbp, at);
		    result = makeValue(source, rt, false);
		} catch (LinkageException e) { throw e.unchecked(); }
	    }
	    else if (false &&  exp.fname == "CHECK_NULL") {
		// Can I eliminate this null pointer check?
		// Null pointer checks aren't even explicit in the
		// SpecificationIR yet...
		//
		// Most null checks can be eliminated because they
		// immediately preceed some access to the object.
		// Null checks are needed on:
		// * nonvirtual method calls
		// * array accesses that lack other checks
		// * reads if page 0 is readable
		// ...
	    }
	    else if (exp.fname == "GET_CONSTANT_BP_RESOLVED_INSTANCE_METHODREF") {
		J2cValue _idx = j2cCanonicalize(exp.args[0]);
		int idx = ((J2cInt) _idx).intValue();
		try {
		    ConstantResolvedInstanceMethodref rinfo = dcp.resolveInstanceMethod(idx);
                    result = new BlueprintReference((S3Blueprint) rinfo.getStaticDefinerBlueprint());
		} catch (LinkageException e) {
		    throw e.unchecked();
		}
	    }
	    else if (exp.fname == "GET_CONSTANT_SHST_RESOLVED_STATIC_METHODREF") {
		J2cValue _idx = j2cCanonicalize(exp.args[0]);
		int idx = ((J2cInt) _idx).intValue();
		try {
		    ConstantResolvedStaticMethodref rinfo =
			dcp.resolveStaticMethod(idx);
		    result = 
			new SharedStateReference(null,
						 rinfo.getSharedState());
		} catch (LinkageException e) {
		    throw e.unchecked();
		}
	    }
	    else if (exp.fname == "GET_CONSTANT_SHST_RESOLVED_STATIC_FIELDREF") {
		J2cValue _idx = j2cCanonicalize(exp.args[0]);
		int idx = ((J2cInt) _idx).intValue();
		try {
		    ConstantResolvedStaticFieldref rinfo =
			dcp.resolveStaticField(idx);
		    result = 
			new SharedStateReference(null,
						 rinfo.getSharedState());
		} catch (LinkageException e) {
		    throw e.unchecked();
		}
	    }
	    else {
		// general case of internal calls
		Value[] args = new Value[exp.args.length];
		for (int i = 0; i < args.length; i++)
		    args[i] = j2cCanonicalize(exp.args[i]);
		source =  new InvocationExp
		    (J2cValue.makeSymbolicReference(exp.fname), args, null);
	    }
	}

	// FIXME: I should really be caching the methods and
	// devirtualized lookup exps for csa calls, since they
	// happen _quite_ frequently.
	public void visit(CSACallExp exp) {
	    J2cValue[] args = new J2cValue[exp.args.length + 1];
	    args[0] = ctx.CSAvalue;
	    valueMap.put(args[0], args[0]);
	    for (int i = 1; i < args.length; i++)
		args[i] = j2cCanonicalize(exp.args[i-1]);
	    S3Method m = ctx.findCSAMethod(exp.fname);
	    S3Domain e = ctx.executiveDomain;
	    try {
		S3Blueprint rt = (S3Blueprint) e.blueprintFor(m.getReturnType());
		S3Blueprint[] at = new S3Blueprint[args.length];
		at[0] = ctx.CSAbp;
		for (int ai = 1, ati = 0;
		     ai < args.length;
		     ai++) {
		    if (args[ai] == null)
			continue;
		    Type t = m.getArgumentType(ati++);
		    at[ai] = (S3Blueprint) e.blueprintFor(t);
		}
		if (m == ctx.processThrowable)
		    source = new InvocationExp("j2c_throw", args[1],
					       ctx.voidBP);
		else 
		    source = new InvocationExp(J2cValue.makeNonvirtualReference(m),
   					  args, rt, at);
		}
	    catch (LinkageException exn) { throw exn.unchecked(); }
	}
	public void visit(IfCmd exp) {
	    if (!checkIf(exp))
		source = new IfCmd(j2cCanonicalize(exp.cond),
				   j2cCanonicalize(exp.ifTrue),
				   (exp.ifFalse == null
				    ? null
				    : j2cCanonicalize(exp.ifFalse)));
	}

	// eliminate branches
	J2cValue canonicalizeElse(Value elsePart) {
	    return (elsePart == null
		    ? J2cValue.NULL
		    : j2cCanonicalize(elsePart));
	}
	boolean checkIf(IfExp exp) {
	    J2cValue v = j2cCanonicalize(exp.cond);
	    logShort("checkIf(", v, ")");
	    if (v == J2cValue.NULL) {
		result = canonicalizeElse(exp.ifFalse);
		logShort("condition(", v, ") => else(", result, ")");
		return true;
	    } else if (v instanceof J2cReference) {
		if (!((J2cReference) v).t.includesNull()) {
		    result = j2cCanonicalize(exp.ifTrue);
		    logShort("condition(", v, ") => then(", result, ")");
		    return true;
		}
	    } else if (v instanceof J2cInt) {
		J2cInt iv = (J2cInt) v;
		if (iv.min > 0 || iv.max < 0) {
		    result = j2cCanonicalize(exp.ifTrue);
		    logShort("condition(", v, ") => then(", result, ")");
		    return true;
		} else if (iv.isConcrete() && iv.intValue() == 0) {
		    result = canonicalizeElse(exp.ifFalse);
		    logShort("condition(", v, ") => else(", result, ")");
		    return true;
		}
	    }
	    return false;
	}

	// convert a SpecificationIR value to a J2cValue.  We maintain
	// a cache describing how values are mapped.  If the value is
	// found in the cache, return its mapping.  Otherwise,
	// simplify the IR.  On the way, we may discover that this
	// value is actually a constant.  If not, try converting the
	// value directly.
	int depth = 0;
	public AbstractValue convert(Value v) {
	    AbstractValue ret = (AbstractValue) valueMap.get(v);
	    // Man, is this ever fucked up.  I don't see how this can
	    // happen.  Luckily, it seems to be OK to blow out these
	    // values each time we recurse (?)
	    OVMBase.OVMassert(depth > 0 ||
			      (source == null && origin == null && result == null));
	    if (ret == null) {
		if (J2cImageCompiler.KEEP_LOGS)
		    logShort(depth + ": convert " + v);
		depth++;
		ValueSource saveSource = source;
		AbstractValue saveResult = result;
		Value saveOrigin = origin;
		try {
		    if (v.source != null) {
			if (v instanceof J2cValue) {
			    logShort((depth-1) + ": ", v, " already J2cValue");
			    valueMap.put(v, v);
			    return v;
			}
			origin = v;
			visitAppropriate(v.source);
			ret = result;
		    }
		    if (ret == null)
			ret = super.convert(v);
		}
		finally {
		    origin = saveOrigin; 
		    source = saveSource;
		    result = saveResult;
		}
		valueMap.put(v, ret);
		valueMap.put(ret, ret);
		depth--;
		if (J2cImageCompiler.KEEP_LOGS)
		    logShort(depth + ": convert ", v, " => ", ret);
	    } else if (J2cImageCompiler.KEEP_LOGS)
		logShort(depth + ": convert ", v, " => ", ret, " (cached)");
		
	    
	    OVMBase.OVMassert(depth > 0 ||
			      (source == null && origin == null && result == null));
	    return ret;
	}

	public ValueSource mapIr(ValueSource vs) {
	    Value saveOrigin = origin;
	    ValueSource saveSource = source;
	    AbstractValue saveResult = result;
	    if (J2cImageCompiler.KEEP_LOGS)
		logShort(depth + ": mapIr ", vs);
	    depth++;
	    try {
		origin = null;
		source = null;
		result = null;
		visitAppropriate(vs);
		//OVMBase.OVMassert(irMapper.result == null, "too much
		//simplification");
		if (result != null)
		    logShort("mapIr ", vs, " can't go to ", result);
		if (J2cImageCompiler.KEEP_LOGS)
		    logShort((depth-1) + ": mapIr ", vs, " => ", source);
		return source;
	    }
	    finally {
		depth--;
		origin = saveOrigin;
		source = saveSource;
		result = saveResult;
	    }
	}
    }

    J2cValue makeValue(ValueSource source, Type t, boolean promoteInt) {
	return ctx.makeValue(source, t, promoteInt);
    }

    S3Blueprint blueprintFor(Type t) {
	return ctx.blueprintFor(t);
    }
	
    // Do I need this irMapper thing?  Probably, since I need to
    // track an instruction's Temp expressions.  I guess I need to
    // cache ValueSource mappings, as well as Value mappings (or
    // simply wrap Temps in Values?).
    protected J2cCanonicalizer irMapper; // duplicate specMapper value
					 // with more specific type
    protected Canonicalizer makeCanonicalizer() {
	return irMapper = new J2cCanonicalizer();
    }


    // What does this accomplish exactly?
    ValueSource mapIr(ValueSource vs) {
	return irMapper.mapIr(vs);
    }

    // Hmm.  J2cValue.INVALID is not a J2cValue
    J2cValue j2cCanonicalize(Value v) {
	return (J2cValue) irMapper.convert(v);
    }

    J2cValue asPrimitive(Value v) {
	J2cValue ret = j2cCanonicalize(v);
	if (ret instanceof J2cReference)
	    return new J2cInt(new ConversionExp(ret), J2cValue.SIZE_T_CODE);
	else
	    return ret;
    }

    J2cValue asAddress(Value v) {
	J2cValue ret = j2cCanonicalize(v);
	if (ret instanceof J2cInt)
	    return new J2cReference(new ConversionExp(ret), ctx.VM_AddressBP);
	else
	    return ret;		// J2cReference or J2cNull
    }

    // Umm.  Is this needed?
    J2cReference asOop(Value v) {
	J2cValue ret = j2cCanonicalize(v);
	if (ret instanceof J2cInt)
	    return new J2cReference(new ConversionExp(ret), ctx.OopBP);
	else
	    return (J2cReference) ret;
    }

    public void parseImmediates(Instruction insn) {
	// Map SpecificationIR immediate operands to their concrete
	// values
	if (insn instanceof Instruction.Switch)
	    // FIXME: Deal with Padding and IntValueList.  We can
	    // ignore switch instructions for now, since they don't
	    // have any evals.
	    return;
	if (insn.istreamIns.length > 0) {
	    StreamableValue[] imm = insn.istreamIns;
	    int off = ((insn.getOpcode() & JVMConstants.Opcodes.WIDE_OFFSET) == 0
		       ? 1
		       : 2);
	    for (int i = 0; i < imm.length; i++) {
		valueMap.put(imm[i],
			     J2cValue.makeAnyConstant(imm[i].decodeStream(buf, off)));
		log(imm[i], " = ", valueMap.get(imm[i]));
		off += imm[i].bytestreamSize();
	    }
	}
    }

    public void handleBody(Instruction insn) {
	// map temporary expressions to J2c
	int idx;
	for (idx = 0; idx < insn.temps.length; idx++) {
	    temps[idx] = (Temp) mapIr(insn.temps[idx]);
	}
	temps[idx] = null;

	for (idx = 0; idx < insn.evals.length; idx++) {
	    evals[idx] = j2cCanonicalize(insn.evals[idx]);
	}
	evals[idx] = J2cValue.TERMINATOR;
    }
    
    public void popInputs(Instruction insn) {
	parseImmediates(insn);
	super.popInputs(insn);
	//boolean gotANull;
	// map stackIns to popped inputs
	int ridx = 0;
	for (int vidx = 0; vidx < insn.stackIns.length; vidx++)
	    if (!(insn.stackIns[vidx] instanceof SecondHalf)) {
		valueMap.put(insn.stackIns[vidx], inputRegister[ridx]);
		if (insn.stackIns[vidx] instanceof NonnulRefValue
		    && inputRegister[ridx] instanceof J2cNull) {
		    executionTerminates = true;
		    if (J2cImageCompiler.KEEP_LOGS)
			logShort("null argument @" + ridx);
		}
		ridx++;
	    }
	// FIXME: Why is this done here, and repeated in so many
	// instructions below? 
	if (executionTerminates)
	    evals[0] = j2cCanonicalize(Instruction.throwException(T.NULL_POINTER_EXCEPTION));
	else
	    handleBody(insn);
    }

    public void pushOutputs(Instruction i) {
	if (!executionTerminates)
	    super.pushOutputs(i);

	// FIXME: This is SO fucked up.  stackOuts contains weird
	// SecoondHalf values for the second half of wide values, but
	// the S3AbstractInterpreter's output register array doesn't
	// contain either SecondHalf objects or nulls (as found in
	// Frame).  To make things even less consistent, I fill in the
	// argument register array myself for calls, and nulls for
	// second halves are present there.
	int realLen = 0;
	for (int idx = 0; idx < i.stackOuts.length; idx++)
	    if (!(i.stackOuts[idx] instanceof SecondHalf))
		realLen++;
    }

    // The following two methods are really only needed when copying
    // out to basic blocks.  They ensure that the values corresponding
    // to locals and stack slots are distinct.
    public void visit(Instruction.LocalRead i) {
        popInputs(i);
	J2cLocalExp refExp = (J2cLocalExp) mapIr(i.stackOuts[0].source);
	int idx =  refExp.num;
	J2cValue var = (J2cValue) getFrame().load(idx);
	J2cValue refVal = var.copy(refExp);
	outputRegister[0] = refVal;
	if (i.stackOuts.length > 1)
	    getFrame().pushWide(refVal);
	else
	    getFrame().push(refVal);
    }

    public void visit(Instruction.LocalWrite i) {
	popInputs(i);
	handleBody(i);
	J2cValue sv = (J2cValue) inputRegister[0];
	J2cValue rv = sv.copy(null);
	int idx = i.getLocalVariableOffset(buf);
	storedVariables.set(idx);
	getFrame().store(idx, rv);
    }

    public void visit(Instruction.IINC i) {
        // Umm, do I need to do anything special with the input?
	popInputs(i);
	handleBody(i);
	J2cValue rv = new J2cInt(null, TypeCodes.INT);
	int idx = i.getLocalVariableOffset(buf);
	storedVariables.set(idx);
	getFrame().store(i.getLocalVariableOffset(buf), rv);
    }

    public void visit(Instruction.MONITORENTER i) {
	visit((Instruction) i);
	if (true || ctx.domain != ctx.executiveDomain) {
	    // We need an explicit null check before entering the
	    // executive domain.
	    //
	    // We also need to do the check outside of CAS32 for
	    // unoptimized ppc-linux builds.  For some reason, a SEGV
	    // inside the out-of-line CAS32 will screw up C++ stack
	    // walking.  
	    //
	    // FIXME: Hmm, is this a more general problem w/CAS32?
	    J2cReference obj = (J2cReference) inputRegister[0];
	    if (obj.t.includesNull()) {
		evals[1] = evals[0];
		evals[0] = new J2cVoid(new InvocationExp("NULLCHECK",
							 obj,
							 ctx.voidBP));
		evals[2] = J2cValue.TERMINATOR;
	    }
	}
    }

    private J2cValue initializeBlueprint(S3Blueprint ibp) {
	if (ctx.needsInit(ibp) &&
	    // FIXME: shouldn't interfaces be initialized already
	    (ibp.getType().isInterface() || !homeBp.isSubtypeOf(ibp))) {
	    J2cValue shSt = new SharedStateReference(null,
						     ibp.getSharedState());
	    return new J2cVoid(new ClinitExp
			       (ibp, mapIr(new CSACallExp
					   ("initializeBlueprint", shSt))));
	} else {
	    return J2cValue.TERMINATOR;
	}
    }
	
    private void visitResolving(Instruction.ConstantPoolLoad i) {
	popInputs(i);
	Value idx = (Value) i.istreamIns[0];
	ValueSource exp = new CPAccessExp(idx);
	outputRegister[0] = j2cCanonicalize(new Value(exp));
	if (ctx.domain != ctx.executiveDomain) {
	    if (outputRegister[0] instanceof SharedStateReference) {
		S3Blueprint sbp =
		    ((J2cValue) outputRegister[0]).getBlueprint(null);
		evals[0] = initializeBlueprint((S3Blueprint) sbp.getInstanceBlueprint());
	    }
	    else if (((J2cValue) outputRegister[0]).getBlueprint(ctx.domain)
		     == ctx.stringBP) {
		Oop v = ((ConcreteScalar) outputRegister[0]).value;
		RepositoryString rs =
		    (RepositoryString) VM_Address.fromObject(v).asObject();
		ConcreteScalar slot = new ConcreteScalar(null, v, ctx.OopBP);
		IntValue idxVal = ConcreteIntValue.make(rs.getUtf8Index());
		ValueSource alloc1 = mapIr(new CSACallExp("resolveUTF8",
							 idxVal));
		J2cValue alloc = new J2cReference(alloc1,
						  ctx.OopBP, 
						  false,
						  false);
		evals[0]
		    = new J2cVoid(new IfCmd
				  (new J2cInt(new UnaryExp("!", slot),
					      TypeCodes.INT),
				   new J2cVoid(new AssignmentExp(slot, alloc))));
	    }
	}
	getFrame().push(outputRegister[0]);
    }
    public void visit(Instruction.LDC i) {
	visitResolving(i);
    }
    public void visit(Instruction.LDC_W i) {
	visitResolving(i);
    }

    public void visit(Instruction.ConstantPoolLoad i) {
	visit((Instruction) i);
    }

    public void visit(Instruction.AALOAD i) { visit((Instruction) i); }

    public void visit(Instruction.WIDE i) {
	cur = i.specialize(buf);
	super.visit(i);
    }

    // What about local variable accesses?
    
    public void visit(Instruction.ROLL r) {
	Frame frame = getFrame();
	int span = r.getSpan(buf);
	int count = r.getCount(buf);
	if (J2cImageCompiler.KEEP_LOGS)
	    log("roll(" + span + ", " + count + ")");
	setArgumentRegisterCount(span);
	for (int i = span - 1; i >= 0; i--) {
	    setArgumentRegisterAt(i, frame.pop());
	    log("popped " + getArgumentRegisterAt(i));
	}
	for (int i = 0; i < span; i++) {
	    frame.push(getArgumentRegisterAt((i + span - count) % span));
	    log("pushed " + getArgumentRegisterAt((i + span - count) % span));
	}
    }

    // Deal with varargs the same way INVOKE* does in
    // S3AbstractInterpreter 
    public void visit(Instruction.Invocation_Quick i) {
	Frame frame = getFrame();
	// We are going to have to expect nulls in the argument
	// registers!
	parseImmediates(i);
	int argCount = i.getArgumentLengthInWords(buf);
	setArgumentRegisterCount(argCount+1);
	if (J2cImageCompiler.KEEP_LOGS)
	    logShort(argCount + " arg words: "
		     + i.getArgumentCount(buf) + "/"
		     + i.getWideArgumentCount(buf));
	// pops argCount (last) through receiver (0)
	for (int idx = argCount; idx > 0; idx--)
	    setArgumentRegisterAt(idx, frame.pop());
	AbstractValue r = frame.pop();
	setArgumentRegisterAt(0, r);
	valueMap.put(i.stackIns[1], r);

	if (r instanceof J2cNull) {
	    evals[0] = j2cCanonicalize(Instruction.throwException(T.NULL_POINTER_EXCEPTION));
	    logShort("null receiver");
	    executionTerminates = true;
	} else {
	    handleBody(i);
	    if (!(evals[0] instanceof J2cVoid)) {
		J2cValue v = evals[0];
		evals[0] = J2cValue.TERMINATOR;
		outputRegister[0] = v;
		if (v instanceof WidePrimitive)
		    frame.pushWide(v);
		else
		    frame.push(v);
	    }
	}
    }

    // Fix LinkSetAccess!
    S3Blueprint linkedBP(IntValue iv) {
	J2cValue v = j2cCanonicalize(iv);
	if (v.isConcrete())
	    try {
		return dcp.resolveClassAt(((J2cInt) v).intValue());
	    } catch (LinkageException le) {
		throw die("can't resolve blueprint " + v);
	    }
	else
	    throw die("can't resolve blueprint " + v);
    }
    S3Blueprint linkedBP(Instruction.ConstantPoolRead i) {
	return linkedBP((IntValue)i.istreamIns[0]);
    }

    J2cValue typeOutput(Instruction i, S3Blueprint bp) {
	if (executionTerminates)
	    return null;
	// S3Blueprint bp = linkedBP(i);
	J2cValue v = typeOutput(i.stackOuts[0].source, bp);
	valueMap.put(i.stackOuts[0].source, v);
	return v;
    }

    J2cValue typeOutput(ValueSource exp, S3Blueprint bp) {
	J2cValue v = makeValue(mapIr(exp), bp.getType(), false);
	outputRegister[0] = v;
	if (v instanceof WidePrimitive)
	    getFrame().pushWide(v);
	else
	    getFrame().push(v);
	return v;
    }

    // FIXME: set up canBeNull, isExact, outermost dimension
    public void visit(Instruction.NEW_QUICK i) {
	popInputs(i);
	S3Blueprint bp = linkedBP(i);
	ValueSource vs = mapIr(i.stackOuts[0].source);
	outputRegister[0] = new J2cReference(vs, bp, true, false);
	getFrame().push(outputRegister[0]);
    }

    public void visit(Instruction.NEW i) {
	popInputs(i);
	TypeName.Scalar tn = i.getClassName(buf, cp);
	S3Blueprint.Scalar bp;
	Type.Context tc = homeBp.getType().getContext();
	try { bp = (S3Blueprint.Scalar) ctx.domain.blueprintFor(tn, tc); }
	catch (LinkageException e) { throw e.unchecked(); }
	OVMBase.OVMassert(evals[0] == J2cValue.TERMINATOR);
	evals[0] = initializeBlueprint(bp);
	ValueSource alloc = new CSACallExp("allocateObject",
					   new BlueprintReference(bp));
	outputRegister[0] = new J2cReference(mapIr(alloc), bp, true, false);
	getFrame().push(outputRegister[0]);
    }

    public void visit(Instruction.LOAD_SHST_FIELD i) {
	try {
	    int cpindex = i.getCPIndex(buf);
	    ConstantResolvedStaticFieldref rinfo =
		dcp.resolveStaticField(cpindex);
	    Oop shst = rinfo.getSharedState();
	    if (ctx.domain != ctx.executiveDomain) {
		evals[0] = initializeBlueprint
		    ((S3Blueprint)
		     shst.getBlueprint().getInstanceBlueprint());
	    }
	    SharedStateReference shstRef =
		new SharedStateReference(null, shst);
	    outputRegister[0] = shstRef;
	    getFrame().push(shstRef);
	} catch (LinkageException e) { 
	    throw e.unchecked(); 
	}
    }

    public void visit(Instruction.LOAD_SHST_METHOD i) {
	try {
	    int cpindex = i.getCPIndex(buf);
	    ConstantResolvedStaticMethodref rinfo =
		dcp.resolveStaticMethod(cpindex);
	    Oop shst = rinfo.getSharedState();
	    if (ctx.domain != ctx.executiveDomain) {
		evals[0] = initializeBlueprint
		    ((S3Blueprint)
		     shst.getBlueprint().getInstanceBlueprint());
	    }
	    SharedStateReference shstRef =
		new SharedStateReference(null, shst);
	    outputRegister[0] = shstRef;
	    getFrame().push(shstRef);
	} catch (LinkageException e) { 
	    throw e.unchecked(); 
	}
    }

    public void visit(Instruction.ANEWARRAY_QUICK i) {
	popInputs(i);
	S3Blueprint bp = linkedBP(i);
	ValueSource alloc = mapIr(i.stackOuts[0].source);
	outputRegister[0] = new J2cArray(alloc,
					 TypeInfo.make(bp, true, false),
					 (J2cInt) inputRegister[0]);
	getFrame().push(outputRegister[0]);

    }
    public void visit(Instruction.MULTIANEWARRAY_QUICK i) {
	parseImmediates(i);

	int count = i.getDimensions(buf);
	setArgumentRegisterCount(count);
	Frame frame = getFrame();
	for (int idx = count - 1; idx >= 0; idx--)
	    setArgumentRegisterAt(idx, frame.pop());
	Value dims = new J2cArray(new DimensionArrayExp(argumentRegisters,
							count),
				  ctx.intArrayBP,
				  J2cValue.makeIntConstant(TypeCodes.INT, count));
	S3Blueprint rt = linkedBP(i);
	ValueSource call = new CSACallExp("allocateMultiArray",
					  new BlueprintReference(rt),
					  J2cValue.makeIntConstant(TypeCodes.INT, 0),
					  dims);
	outputRegister[0] = new J2cArray(mapIr(call),
					 TypeInfo.make(rt, true, false),
					 (J2cInt) getArgumentRegisterAt(0));
	frame.push(outputRegister[0]);
    }
    public void visit(Instruction.CHECKCAST_QUICK i) {
	popInputs(i);
	// OK.  How do I indicate that the stack type of
	// inputRegister[0] has changed?
	S3Blueprint bp = linkedBP(i);
	J2cValue v;
	ValueSource vs = new ConversionExp((J2cValue) inputRegister[0]);

	if (bp instanceof Blueprint.Array)
	    v = new J2cArray(vs, bp);
	else
	    v = new J2cReference(vs, bp);
	outputRegister[0] = v;
	getFrame().push(v);
    }

    public void visit(Instruction.AFIAT i) {
	popInputs(i);
	// FIXME: double resolution
	S3Blueprint bp = resolveTypeName(i.getResultTypeName(buf, cp).asCompound());
	ValueSource vs = mapIr(i.stackOuts[0].source);
	J2cValue v;

	if (bp instanceof Blueprint.Array)
	    v = new J2cArray(vs, bp);
	else
	    v = new J2cReference(vs, bp);
	outputRegister[0] = v;
	getFrame().push(v);
    }

    // INSTANCEOF_QUICK is an important one for 
//     public void visit(Instruction.INSTANCEOF_QUICK i) {
//     }
//     public void visit(Instruction.REF_PUTFIELD_QUICK i) {
//     }

    void bt(Selector.Method s) {
	throw new LinkageException
	    ("calls undefined method: " + s).unchecked();
    }
    void bt(Selector.Field s) {
	throw new LinkageException
	    ("references udefined field: " + s).unchecked();
    }
    void bt(TypeName t) {
	throw new LinkageException
	    ("references udefined type: " + t).unchecked();
    }
    
    public void visit(Instruction.GETFIELD i)  { bt(i.getSelector(buf, cp)); }
    public void visit(Instruction.GETSTATIC i) { bt(i.getSelector(buf, cp)); }
    public void visit(Instruction.PUTFIELD i)  { bt(i.getSelector(buf, cp)); }
    public void visit(Instruction.PUTSTATIC i) { bt(i.getSelector(buf, cp)); }
    protected void visitInvocation(Instruction.Invocation i) {
	bt(i.getSelector(buf, cp));
    }
    public void visit(Instruction.INVOKESTATIC i) { bt(i.getSelector(buf, cp)); }
    public void visit(Instruction.CHECKCAST i)    { bt(i.getResultTypeName(buf, cp)); }
    public void visit(Instruction.INSTANCEOF i)   { bt(i.getTypeName(buf, cp)); }
    public void visit(Instruction.ANEWARRAY i)    { bt(i.getClassName(buf, cp)); }
    public void visit(Instruction.MULTIANEWARRAY i) {
	bt(i.getClassName(buf, cp));
    }

    public J2cValue callV(InvocationExp exp) {
	return new J2cVoid(exp);
    }
    public J2cValue callI(ValueSource exp) {
	return new J2cInt(exp, TypeCodes.INT);
    }

    J2cValue jlongToType(ValueSource callExpr, char t, TypeName n) {
	ValueSource callJValue
	    = new InvocationExp("to_"+J2cValue.typeCodeToCtype[t],
				new InternalReference(callExpr),
				null);
	switch (t) {
	case TypeCodes.ARRAY:
	case TypeCodes.OBJECT:
	    try {
		S3Blueprint bp  = (S3Blueprint) ctx.domain.blueprintFor
		    (n.asCompound(),
		     ctx.domain.getSystemTypeContext());
		if (t == TypeCodes.OBJECT)
		    return new J2cReference(callJValue, bp);
		else
		    return new J2cArray(callJValue, bp);
	    } catch (LinkageException e) { throw e.unchecked(); }
	case TypeCodes.FLOAT:
	    return new J2cFloat(callJValue, null);
	case TypeCodes.DOUBLE:
	    return new J2cDouble(callJValue, null);
	case TypeCodes.LONG:
	    return new J2cLong(callJValue, null, TypeCodes.LONG);
	default:
	    return new J2cInt(callJValue, t);
	}
    }

    private interface S extends InvokeSystemArguments { }
    private interface W extends WordOps { }
    private interface D extends DereferenceOps { }
    private interface T extends Throwables { }
    
    // Umm, what are the argument and return types for these guys?
    // Certainly word ops are easy: they can be expressed in terms of
    // the IR.
    public void visit(Instruction.INVOKE_SYSTEM i) {
	int pc = getPC();
	Frame frame = getFrame();
	int call = i.getMethodIndex(buf);
	int sub = i.getOpType(buf);
	int iidx = 0;
	int eidx = 0;
	int oidx = 0;
	//ValueSource fakeExp = new SymbolicConstant("0");
	
	switch (call) {
	case S.ACTIVE_CONTEXT:
	case S.SWITCH_CONTEXT:
	    logShort("call " + call + " not implemented in interpreter");
	    break;
	case S.NEW_CONTEXT:
	    inputRegister[iidx++] = frame.pop(); // Context
	    outputRegister[oidx++]
		= callI(new InvocationExp("newNativeContext",
					  inputRegister, iidx, true,
					  ctx.blueprintFor(ctx.domain.INT)));
	    break;
	case S.RUN:
	    inputRegister[iidx++] = frame.pop(); // int (J2cContext*)
	    inputRegister[iidx++] = frame.pop(); // int
	    evals[eidx++] = callV(new InvocationExp
				  ("run", inputRegister, iidx, true,
				   ctx.blueprintFor(ctx.domain.VOID)));
	    break;
	case S.GET_CONTEXT:
	    inputRegister[iidx++] = frame.pop(); // int
	    outputRegister[oidx++]
		= callI(new InvocationExp("getContext",
					  (J2cValue) inputRegister[0],
					  ctx.blueprintFor(ctx.domain.INT)));
	    break;
	case S.DESTROY_NATIVE_CONTEXT:
	    inputRegister[iidx++] = frame.pop(); // int (J2cContext*)
	    evals[eidx++]
		= callI(new InvocationExp("destroyNativeContext",
					  inputRegister, iidx, true,
					  ctx.blueprintFor(ctx.domain.INT)));
	    break;
	case S.SETPOLLCOUNT:	// (J)J
	    logShort("ignoring setpollcount");
	    break;
	case S.EMPTY_CSA_CALL:
	    evals[eidx++]
		= j2cCanonicalize(new Value(new CSACallExp("emptyCall")));
	    break;
	case S.WORD_OP: {
	    if (sub == W.uI2L) {
		inputRegister[iidx++] = frame.pop();
		outputRegister[oidx++]
		    = new J2cLong(new ConversionExp
				  (new J2cInt(new ConversionExp
					      ((J2cValue) inputRegister[0]),
					      TypeCodes.UINT)),
				  null, TypeCodes.LONG);
		outputRegister[oidx++] = null;
		break;
	    }
	    inputRegister[iidx++] = frame.pop(); // VM_Word
	    inputRegister[iidx++] = frame.pop(); // VM_Word
	    char code  = sub == WordOps.sCMP ? TypeCodes.INT : TypeCodes.UINT;
	    J2cValue v1 = new J2cInt(new ConversionExp((J2cValue)inputRegister[0]),
				     code);
	    J2cValue v2 = new J2cInt(new ConversionExp((J2cValue)inputRegister[1]),
				     code);
	    if (sub == W.sCMP || sub == W.uCMP) {
		outputRegister[oidx++]
		    = new J2cInt(Instruction.buildCMP(v2, v1), TypeCodes.INT);
	    } else {
		String op = (sub == W.uLT ? "<"
			     : sub == W.uLE ? "<="
			     : sub == W.uGE ? ">="
			     : sub == W.uGT ? ">"
			     : sDie("bad WORD_OP " + sub));
		outputRegister[oidx++] = new J2cInt(new CondExp(v2, op, v1),
						    TypeCodes.BOOLEAN);
	    }
	    break;
	}
	case S.DEREFERENCE: {
	    char tag = TypeCodes.SHORT;
	    switch (sub) {
	    case D.getByte: tag = TypeCodes.BYTE;
	    case D.getShort:
		inputRegister[iidx++] = frame.pop();	// VM_Address
		outputRegister[oidx++]
		    = new J2cInt(new MemExp((J2cValue) inputRegister[0],
					    J2cValue.makeIntConstant(TypeCodes.INT, 0)),
				 tag);
		break;
	    case D.setByte: tag = TypeCodes.BYTE;
	    case D.setShort:
		inputRegister[iidx++] = frame.pop(); // src
		inputRegister[iidx++] = frame.pop(); // dest
		evals[eidx++] = new J2cVoid
		    (new AssignmentExp
		     (new J2cInt
		      (new MemExp((J2cValue) inputRegister[1],
				  J2cValue.makeIntConstant(TypeCodes.INT, 0)),
		       tag),
		      (J2cValue) inputRegister[0]));
		break;
		    
	    case D.setBlock:
		inputRegister[iidx++] = frame.pop(); // count
		inputRegister[iidx++] = frame.pop(); // src
		inputRegister[iidx++] = frame.pop(); // dest
		evals[eidx++]
		    = callV(new InvocationExp
			    (J2cValue.makeSymbolicReference("memmove"),
			     new Value[] {
				 new InternalReference(new CCastExp("void *", (Value) inputRegister[2])),
				 new InternalReference(new CCastExp("const void *", (Value) inputRegister[1])),
				 new J2cInt(new ConversionExp((J2cValue)inputRegister[0]),
					    TypeCodes.INT)
			     },
			     ctx.blueprintFor(ctx.domain.VOID)));
		break;
	    }
	    break;
	}
	case S.START_TRACING:	// (Z)V
	    logShort("ignore startTracing");
	    inputRegister[iidx++] = frame.pop();
	case S.STOP_TRACING:	// ()V
	    logShort("ignore stopTracing");
	    break;
	case S.CUT_TO_ACTIVATION: // (II)V
	    logShort("ignore CUT_TO_ACTIVATION");
	    inputRegister[iidx++] = frame.pop();
	    inputRegister[iidx++] = frame.pop();
	    break;
	case S.MAKE_ACTIVATION:	// (ILovm/core/domain/Blueprint;Lovm/core/repository/RepositoryByteCodeFragment;[Lovm/core/domain/Oop;)I
	    inputRegister[iidx++] = frame.pop(); //InvocationMessage
	    inputRegister[iidx++] = frame.pop(); // Code
	    inputRegister[iidx++] = frame.pop(); // int
	    outputRegister[oidx++]
		= callI(new InvocationExp("makeActivation",
					  inputRegister, iidx, true,
					  ctx.intBP));
	    break;
	case S.GET_ACTIVATION:	// (I)I
	    inputRegister[iidx++] = frame.pop();
	    outputRegister[oidx++]
		= callI(new InvocationExp("getActivation",
					  inputRegister, iidx, true,
					  ctx.intBP));
	    break;
	case S.INVOKE:
	    inputRegister[iidx++] = frame.pop(); // InvocationMessage
	    inputRegister[iidx++] = frame.pop(); // Code
	    ValueSource callExp = new InvocationExp("j2cInvoke",
						    inputRegister, iidx, true,
						    null);
	    ValueSource callJValue
		= new JValueExp(new InternalReference(callExp));
	    switch (sub) {
	    case TypeCodes.VOID:
		evals[eidx++] = new J2cVoid(callExp);
		break;
	    case TypeCodes.OBJECT:
		outputRegister[oidx++] = new J2cReference(callJValue,
							  ctx.OopBP);
		break;
	    case TypeCodes.FLOAT:
		outputRegister[oidx++] = new J2cFloat(callJValue, null);
		break;
	    case TypeCodes.DOUBLE:
		outputRegister[oidx++] = new J2cDouble(callJValue, null);
                outputRegister[oidx++] = null;
		break;
	    case TypeCodes.LONG:
		outputRegister[oidx++] = new J2cLong(callJValue, null,
						     TypeCodes.LONG);
                outputRegister[oidx++] = null;
		break;
	    default:
		outputRegister[oidx++] = new J2cInt(callJValue, (char) sub);
	    }
	    OVMBase.OVMassert(pc == getPC());
	    break;
	case S.CAS32:
	    inputRegister[iidx++] = asPrimitive((Value) frame.pop()); // new
 	    inputRegister[iidx++] = asPrimitive((Value) frame.pop()); // old
	    inputRegister[iidx++] = asAddress((Value) frame.pop());   // addr;
	    outputRegister[oidx++]
		= callI(new InvocationExp("CAS32",
					  inputRegister, iidx, true,
					  ctx.intBP));
	    break;
	case S.CAS64:
	    inputRegister[iidx++] = frame.pop(); // new
	    frame.pop();			 // 2nd half
 	    inputRegister[iidx++] = frame.pop(); // old
	    frame.pop();			 // 2nd half
	    inputRegister[iidx++] = frame.pop(); // addr;
	    outputRegister[oidx++]
		= callI(new InvocationExp("CAS64",
					  inputRegister, iidx, true,
					  ctx.intBP));
	    break;
	default:
	    logShort("uknown SYSTEM instruction " + call);
	}
	for (int j = 0; j < oidx; j++)
	    if (j < oidx - j && outputRegister[j + 1] == null)
		frame.pushWide(outputRegister[j++]);
	    else
		frame.push(outputRegister[j]);
    }

    static final HashMap inlineNatives = new HashMap();
    static void inlineMe(String name, String sig) {
	if (!sig.endsWith("V"))
	    throw new RuntimeException("Currently only inlining void");
	String sb = name+':'+sig;
	inlineNatives.put(RepositoryUtils.makeUnboundSelector(sb), name);
    }
    static {
	inlineMe("setEnabled", "(Z)V");
	inlineMe("eventsSetEnabled", "(Z)V");
	inlineMe("memcpy", "("+
		 "Lovm/core/services/memory/VM_Address;"+
		 "Lovm/core/services/memory/VM_Address;"+
		 "I)V");
	inlineMe("memmove", "("+
		 "Lovm/core/services/memory/VM_Address;"+
		 "Lovm/core/services/memory/VM_Address;"+
		 "I)V");
	inlineMe("memset", "("+
		 "Lovm/core/services/memory/VM_Address;"+
		 "II)V");
	inlineMe("bzero", "("+
		 "Lovm/core/services/memory/VM_Address;"+
		 "I)V");
	inlineMe("SAVE_REGISTERS", "()V");
    }
    
//     TypeName inlineTN
// 	= OVMBase.repository.makeTypeName("s3/services/j2c", "Inline");

    // I guess I have to get argument and return information from the
    // corresponding method signatures
    public void visit(Instruction.INVOKE_NATIVE insn) {
	Frame frame = getFrame();
	int callNo = insn.getMethodIndex(buf);
	UnboundSelector.Method m =
	    NativeCallGenerator.getNativeArgumentMap()[callNo];
	if (false /*&& m.getDeclaringTypeName() == InlineTN*/) {
	    // handle Inline.C and Inline.asm specially
	    return;
	}
	Descriptor.Method sig = m.getDescriptor();
	int nargs = sig.getArgumentLength() / 4;
	logShort("call to ", m, "( with ", I(nargs), " args)" );

	ValueSource callExpr;

	J2cValue[] origArgs = new J2cValue[nargs];
	for (int idx = nargs - 1; idx >= 0; idx--)
	    origArgs[idx] = (J2cValue) frame.pop();

	
	String name = (String) inlineNatives.get(m);
	if (name != null) {
	    TypeName VM_AddressTN =
		ctx.VM_AddressBP.getType().getUnrefinedName();
	    setArgumentRegisterCount(nargs);
	    // i is the "real" argument index (accounting for wids)
	    // j is the index into the argument list
	    for (int i = 0, j = 0; i < nargs; i++, j++) {
		J2cValue ai = origArgs[i];
		TypeName at = sig.getArgumentType(j);
		if (ai.isWidePrimitive())
		    i++; 
		else if (at == VM_AddressTN)
		    ai = new InternalReference(new CCastExp("void *", ai));
		setArgumentRegisterAt(i, ai);
	    }

	    callExpr = new InvocationExp(name, argumentRegisters,
					 nargs, false, null);
	} else {

	    setArgumentRegisterCount(nargs + 1);
	    setArgumentRegisterAt(0, J2cValue.makeIntConstant(TypeCodes.INT, callNo));
	    for (int i = 0, j = 0; i < nargs; j++, i++) {
		J2cValue ai = origArgs[j];
		if (ai.isWidePrimitive()) {
		    i++; j++;
		    ai = new J2cLong(new InvocationExp("to_jwide", ai,
						       ctx.longBP),
				     null, TypeCodes.LONG);
		}
		else
		    ai = new J2cInt(new InvocationExp("to_jnarrow", ai,
						      ctx.intBP),
				    TypeCodes.INT);
		setArgumentRegisterAt(nargs - i, ai);
	    }

	    callExpr = new InvocationExp("invoke_native",
					 argumentRegisters,
					 nargs + 1, false, null);
	}
	TypeName n = sig.getType();
	char t = n.getTypeTag();
	if (t == TypeCodes.VOID)
	    evals[0] = new J2cVoid(callExpr);
	else
	    outputRegister[0] = jlongToType(callExpr, t, n);

	if (outputRegister[0] instanceof Value.WidePrimitive)
	    frame.pushWide(outputRegister[0]);
	else
	    frame.push(outputRegister[0]);
    }
}
