package s3.services.j2c;
import java.io.PrintWriter;

import org.ovmj.util.Runabout;

import ovm.core.domain.Domain;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Method;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
import ovm.core.repository.JavaNames;
import ovm.core.repository.RepositoryUtils;
import ovm.core.repository.TypeCodes;
import ovm.core.repository.TypeName;
import ovm.core.services.format.CxxFormat;
import ovm.core.services.memory.VM_Address;
import ovm.services.bytecode.Instruction;
import ovm.services.bytecode.SpecificationIR.ArrayAccessExp;
import ovm.services.bytecode.SpecificationIR.ArrayLengthExp;
import ovm.services.bytecode.SpecificationIR.AssignmentExp;
import ovm.services.bytecode.SpecificationIR.BinExp;
import ovm.services.bytecode.SpecificationIR.BlueprintAccessExp;
import ovm.services.bytecode.SpecificationIR.CondExp;
import ovm.services.bytecode.SpecificationIR.ConversionExp;
import ovm.services.bytecode.SpecificationIR.CurrentPC;
import ovm.services.bytecode.SpecificationIR.IfCmd;
import ovm.services.bytecode.SpecificationIR.IfExp;
import ovm.services.bytecode.SpecificationIR.LocalStore;
import ovm.services.bytecode.SpecificationIR.MemExp;
import ovm.services.bytecode.SpecificationIR.ReinterpretExp;
import ovm.services.bytecode.SpecificationIR.ShiftMaskExp;
import ovm.services.bytecode.SpecificationIR.SymbolicConstant;
import ovm.services.bytecode.SpecificationIR.UnaryExp;
import ovm.services.bytecode.SpecificationIR.ValueSource;
import ovm.util.BitSet;
import ovm.util.Iterator;
import s3.core.domain.S3Blueprint;
import s3.core.domain.S3ByteCode;
import s3.core.domain.S3Domain;
import s3.core.domain.S3Method;
import s3.services.j2c.BBSpec.BCExpr;
import s3.services.j2c.BBSpec.BRExpr;
import s3.services.j2c.BBSpec.CatchExpr;
import s3.services.j2c.BBSpec.Expr;
import s3.services.j2c.BBSpec.IRExpr;
import s3.services.j2c.BBSpec.PollCheckExpr;
import s3.services.j2c.BBSpec.SWExpr;
import s3.services.j2c.BBSpec.TryExpr;
import s3.services.j2c.J2cValue.ArrayDataExp;
import s3.services.j2c.J2cValue.BlueprintReference;
import s3.services.j2c.J2cValue.CCastExp;
import s3.services.j2c.J2cValue.ConcreteArray;
import s3.services.j2c.J2cValue.ConcreteScalar;
import s3.services.j2c.J2cValue.CrossDomainCallExp;
import s3.services.j2c.J2cValue.DimensionArrayExp;
import s3.services.j2c.J2cValue.InternalReference;
import s3.services.j2c.J2cValue.InvocationExp;
import s3.services.j2c.J2cValue.J2cDouble;
import s3.services.j2c.J2cValue.J2cFieldAccessExp;
import s3.services.j2c.J2cValue.J2cFloat;
import s3.services.j2c.J2cValue.J2cInt;
import s3.services.j2c.J2cValue.J2cJumpTarget;
import s3.services.j2c.J2cValue.J2cLookupExp;
import s3.services.j2c.J2cValue.J2cNull;
import s3.services.j2c.J2cValue.J2cNumeric;
import s3.services.j2c.J2cValue.J2cReference;
import s3.services.j2c.J2cValue.J2cVoid;
import s3.services.j2c.J2cValue.JValueExp;
import s3.services.j2c.J2cValue.MethodReference;
import s3.services.j2c.J2cValue.PCRefExp;
import s3.services.j2c.J2cValue.SeqExp;
import s3.services.j2c.J2cValue.SharedStateReference;
import s3.services.j2c.J2cValue.ValueAccessExp;
import s3.services.j2c.SpecInstantiation.LineNumberParser;
import s3.services.transactions.Transaction;
import s3.util.PragmaInline;

public class CodeGen extends Runabout {
    static final boolean EMIT_LINE_NUMBERS = true;

    /**
     * If false, avoid emitting a #line directive for the `next'
     * line.  This should make the .cc file smaller, and potentially
     * make compilation faster, but it also makes the .cc file less
     * readable.
     **/
    static final boolean EMIT_ALL_LINE_NUMBERS = true;
    
    /**
     * pc comments don't work well with line numbers and gcc 3.0.2:
     * gcc -E will ignore the newline escape for lines containing
     * comments immediately after #line directives.
     *
     * Anyway, the pc of a push statement is clear from the
     * left-hand-side, and the start pc of a block is clear from its
     * label.
     **/
    static final boolean EMIT_PC_COMMENTS = false;

    static final String TERMINATOR
	= (EMIT_LINE_NUMBERS
	   ? " \\" + System.getProperty("line.separator")
	   : System.getProperty("line.separator"));
	
    static final String rootsField = "ovm_j2cRoots";

    Context ctx;
    Method m;
    PrintWriter w;
    S3Domain dom;
    BBSpec[] blocks;
    int bidx;
    J2cValue synchVar;
    boolean hasGcFrame = false;

    boolean outerTranslate;
    
    static final int BOOT_METHOD = 0;
    static final int THROW_METHOD = 1;
    static final int THROW_WILD_METHOD = 2;
    static final int CODE_FROM_PC_METHOD = 3;
    
    BBSpec curBlock;
    BBSpec nextBlock() { return blocks[bidx + 1]; }

    void ptype(S3Blueprint bp) {
	w.print(J2cFormat.format(bp));
	if (bp.isReference())
	    w.print(" *");
    }

    void ptype(Type t) {
	try { ptype((S3Blueprint) dom.blueprintFor(t)); }  
	catch (LinkageException e) { throw e.unchecked(); }
    }

    boolean fresh = true;

    // Should plain-old terminate set fresh?  I don't know.  problems
    // arise when w.print is called after terminateAndIndent, and
    // fresh goes out of date
    void terminate(String tail) {
	w.print(tail);
	w.print(TERMINATOR);
    }
    void terminateAndIndent(String tail) {
	terminate(tail);
	w.print("\t");
	w.flush();
	fresh = true;
    }

    boolean stackScopeOpened = false;
    /**
     * Ensure that there is a lexical block to hold temps
     * corresponding to the JVM operand stack.
     **/
    private void openStackScope() {
	if (!stackScopeOpened) {
	    terminateAndIndent("{");
	    stackScopeOpened = true;
	}
    }
    /**
     * Close out any lexical block to hold temps correspoding to the
     * JVM operand stack.
     **/
    private void closeStackScope() {
	if (stackScopeOpened) {
	    terminateAndIndent("}");
	    stackScopeOpened = false;
	}
    }

    int lastLine;
    String lastFile;
    boolean emitLineNumber(LineNumberParser parser, int pc) {
	if (EMIT_LINE_NUMBERS) {
	    int line = parser.getLineNumber(pc);
	    String file = parser.getSourceFile(pc);
	    if (line != lastLine || file != lastFile) {
		w.println("");
		if (EMIT_ALL_LINE_NUMBERS || line != lastLine + 1) {
		    w.print("# ");
		    w.print(line < 0 ? 0 : line);
		    w.print(" \"./");
		    w.print(file);
		    w.println('"');
		}
		lastLine = line;
		lastFile = file;
		return true;
	    }
	}
	return false;
    }

    /**
     * Compile a java method
     **/
    CodeGen(PrintWriter w, S3Method meth, S3ByteCode cf,
	    BBSpec[] blocks,
	    BitSet storedLocals, LocalAllocator allocator, 
	    Context ctx)
    {
	this.w = w;
	this.blocks = blocks;
	this.ctx = ctx;
	this.m = meth;
	this.outerTranslate = (allocator.hasXDCalls &&
			       !allocator.hasExceptions);
	Domain d = meth.getDeclaringType().getContext().getDomain();
	dom = (S3Domain) d;
	J2cValue[] uninitLocals = null;

	emitLineNumber(blocks[0].parser, blocks[0].pcDelta[1]);
	// emit method declaration
	w.print("static ");
	if (PragmaInline.declaredBy(meth.getSelector(),
				    ctx.blueprintFor(meth.getDeclaringType())))
	    w.print("inline ");
	try { ptype(meth.getReturnType()); }
	catch (LinkageException e) { throw e.unchecked(); }
	w.print(" ");
	w.print(J2cFormat.format(meth));
	terminateAndIndent("(");
	BitSet argLocals = new BitSet();
	J2cValue thisVar = allocator.findLocal(TypeCodes.OBJECT, 0);
	if (ctx.gcSupport || storedLocals.get(0) || thisVar == null) {
	    ptype(meth.getDeclaringType());
	    w.print("_self");
	}
	else {
	    argLocals.set((0*LocalAllocator.N_VAR_FLAVORS)
			  + LocalAllocator.REF_VAR);
	    w.print(thisVar.getType());
	    w.print(" ");
	    w.print(thisVar.name);
	}
	

	for (int i = 0, index=1; i < meth.getArgumentCount(); i++) {
	    terminateAndIndent(",");
	    try {
		Type t = meth.getArgumentType(i);
		int tag = t.getUnrefinedName().getTypeTag();
		S3Blueprint bp = ctx.blueprintFor(t);
		J2cValue var = allocator.findLocal(bp, index++);
                if (tag == TypeCodes.DOUBLE || tag == TypeCodes.LONG)
                    index++;
		if (var == null
		    // if a parameter's type matches the type of the
		    // local slot exactly, we can avoid copying it
		    // into another variable.  This could make the
		    // generated C++ simpler and make the whole thing
		    // faster.
		    || (var.flavor == LocalAllocator.REF_VAR
			&& (ctx.gcSupport || storedLocals.get(var.index)))
		    || (LocalAllocator.flavorTag[var.flavor]  != tag))
		{
		    ptype(meth.getArgumentType(i));
		    w.print(" _arg" + i);
		} else {
		    argLocals.set((var.index*LocalAllocator.N_VAR_FLAVORS)
				  + var.flavor);
		    w.print(var.getType());
		    w.print(" ");
		    w.print(var.getName());
		}
	    }
	    catch (LinkageException e) { throw e.unchecked(); }
	}
	terminate(")");
	terminateAndIndent("{");

	if (m.getMode().isSynchronized()) {
	    synchVar = (storedLocals.get(0)
			? allocator.saveThis()
			: allocator.findLocal(TypeCodes.OBJECT, 0));
            if (synchVar == null)
                synchVar = allocator.saveThis();
	}

	int endLocalFlavors = (ctx.gcSupport
			       ? LocalAllocator.REF_VAR
			       : LocalAllocator.N_VAR_FLAVORS);
	
	// declare local variable slots and initialize parameter slots
	for (int fl = 0; fl < endLocalFlavors; fl++) {
	    for (Iterator it = allocator.varIterator(fl); it.hasNext(); ) {
		J2cValue var = (J2cValue) it.next();
		if (argLocals.get(var.index*LocalAllocator.N_VAR_FLAVORS
				     + fl))
		    continue;
		w.print(var.getType());
		w.print(" ");
		w.print(var.getName());
		terminateAndIndent(";");
	    }
	}
	if (ctx.gcSupport)
pushFrame: {
	    int cnt = 0;
	    Iterator it = allocator.varIterator(LocalAllocator.REF_VAR);
	    // Skipping a frame is tough because of
	    // Activation.caller(), and because of the need to restore
	    // a frame on catch
// 	    if (!it.hasNext())
// 		break pushFrame;
	    uninitLocals = allocator.findLocals(LocalAllocator.REF_VAR);
	    hasGcFrame = true;
	    terminateAndIndent("struct Frame: GCFrame {");
	    while (it.hasNext()) {
		J2cValue var = (J2cValue) it.next();
		w.print("\t");
		w.print(var.getType());
		w.print(" ");
		w.print(var.getName());
		terminateAndIndent(";");
		cnt++;
	    }
	    terminateAndIndent("};");

	    terminateAndIndent("Frame frame;");
	    w.print("frame.length = ");
	    w.print(cnt);
	    terminateAndIndent(";");
	    terminateAndIndent("frame.next = currentContext->gcTop;");
	    terminateAndIndent("currentContext->gcTop = &frame;");
	}
	
        if (synchVar != null || outerTranslate)
            terminateAndIndent("try {");

        if (!argLocals.get((0*LocalAllocator.N_VAR_FLAVORS)
			      + LocalAllocator.REF_VAR)
	    && thisVar != null) {
	    if (hasGcFrame)
		uninitLocals[0] = null;
	    visit(thisVar);
            terminateAndIndent(" = _self;");
	}
        if (synchVar != null && synchVar.index != 0) {
	    if (hasGcFrame)
		uninitLocals[synchVar.index] = null;
            visit(synchVar);
            if (synchVar.getBlueprint(ctx.domain) == ctx.OopBP)
                terminateAndIndent(" = (e_ovm_core_domain_Oop *) _self;");
            else
                terminateAndIndent(" = _self;");
        }
	for (int i = 0, index = 1; i < meth.getArgumentCount(); i++) {
	    try {
		Type t = meth.getArgumentType(i);
		int tag = t.getUnrefinedName().getTypeTag();
		S3Blueprint bp = ctx.blueprintFor(t);
		J2cValue var = allocator.findLocal(bp, index++);
                if (tag == TypeCodes.DOUBLE || tag == TypeCodes.LONG)
                    index++;
                if (var == null)
                    continue;       // unused parameter                    
		if (!argLocals.get(var.index*LocalAllocator.N_VAR_FLAVORS
                                   + var.flavor)) {
		    visit(var);
		    w.print((bp == ctx.VM_AddressBP || bp == ctx.VM_WordBP)
			    ? " = (jint) _arg"
			    : " = _arg");
		    if (hasGcFrame)
			uninitLocals[var.index] = null;
		    w.print(i);
		    terminateAndIndent(";");
		}
	    }
	    catch (LinkageException e) { throw e.unchecked(); }
	}
	terminateAndIndent("");
	for (int i = 0; hasGcFrame && i < uninitLocals.length; i++)
	    if (uninitLocals[i] != null) {
		visit(uninitLocals[i]);
		terminateAndIndent(" = 0;");
	    }
	
	// declare phi stack variables
	for (int i = 0; i < blocks.length; i++) {
	    J2cValue[] in = blocks[i].stackInputs;
	    for (int j = 0; j < in.length; j++) {
		J2cValue v = in[j];
		if (v == null)
		    continue;
		if (v.isConcrete()) {
		    v = in[j] = null;
		    continue;
		}
		v.allocate(allocator.genSym("_phi"),
			   J2cValue.MERGED_STACK_SLOT,
			   -1, j);
		w.print(v.getType());
		w.print(" ");
		w.print(v.getName());
		terminateAndIndent(";");
	    }
	}
	terminate("");

	w.print("\t");
	if (m.getMode().isSynchronized()) {
	    if (!(outerTranslate || dom.isExecutive()))
		w.print("try { ");
	    w.print(J2cFormat.format(ctx.monitorEnter));
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    w.print(", ");
	    compileWithType(synchVar, ctx.OopBP);
	    if (outerTranslate || dom.isExecutive())
		terminateAndIndent(");");
	    else {
		terminateAndIndent("); }");
		w.print("catch (e_java_lang_Throwable *e) { ");
		if (hasGcFrame)
		    w.print("currentContext->gcTop = &frame; ");
		w.print("j2c_throw(");
		w.print(J2cFormat.format(ctx.translateThrowable));
		w.print("(");
		visitAppropriate(ctx.CSAvalue);
		terminateAndIndent(", e)); }");
	    }
	}

	// emit code for basic blocks.  Wrap try blocks in try/catch.
	for (bidx = 0; bidx < blocks.length; bidx++) {
	    curBlock = blocks[bidx];
	    int pc = curBlock.getStartPC();
	    int dix = 0;
	    int j = 0;

	    emitLineNumber(curBlock.parser, pc);
	    // Hmm.  We have both leading catches and trailing
	    // catches.  Why?
	    while (curBlock.code.length > j
		   && curBlock.code[j] instanceof CatchExpr) {
		fresh = false;
		visitAppropriate(curBlock.code[j++]);
	    }

	    w.print("_pc");
	    w.print(pc);
	    terminateAndIndent(":;");
	    for ( ; j < curBlock.code.length; j++) {
		if (curBlock.pcDelta[dix] == j) {
		    pc = curBlock.pcDelta[dix+1];
		    if (emitLineNumber(curBlock.parser, pc))
			w.print("\t");
		    if (EMIT_PC_COMMENTS)
			terminateAndIndent("/* PC " + pc + " */");
		    dix += 2;
		}
		Expr e = curBlock.code[j];
		if (e.dest != null) {
		    openStackScope();
		    w.print(e.dest.getType());
		    w.print(" ");
		    w.print(e.dest.getName());
		    w.print(" = ");
		}
		fresh = false;
		visitAppropriate(e);
		if (!fresh)
		    terminateAndIndent(";");
	    }
	    if (bidx+1 < blocks.length && curBlock.fallsThrough())
		enter(blocks[bidx+1], curBlock);
	    for (j = 0; j < curBlock.catches.length; j++) {
		fresh = false;
		visitAppropriate(curBlock.catches[j]);
	    }
	    closeStackScope();
	}
	if (outerTranslate || synchVar != null)
	    terminateAndIndent("}");
	if (outerTranslate) {
	    terminateAndIndent("catch (e_java_lang_Throwable *e) {");
	    if (hasGcFrame)
		terminateAndIndent("currentContext->gcTop = &frame;");
	    if (synchVar != null) {
		w.print("try { ");
		w.print(J2cFormat.format(ctx.monitorExit));
		w.print("(");
		visitAppropriate(ctx.CSAvalue);
		w.print(", ");
		compileWithType(synchVar, ctx.OopBP);
		terminateAndIndent("); }");
		w.print("catch (e_java_lang_Throwable *e) { ");
		if (hasGcFrame)
		    w.print("currentContext->gcTop = &frame; ");
		w.print("j2c_throw(");
		w.print(J2cFormat.format(ctx.translateThrowable));
		w.print("(");
		visitAppropriate(ctx.CSAvalue);
		terminateAndIndent(", e)); }");
	    }
	    w.print("j2c_throw(");
	    w.print(J2cFormat.format(ctx.translateThrowable));
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    terminateAndIndent(", e));");
	    terminateAndIndent("}");
	}
	if (synchVar != null) {
	    w.print("catch (");
	    w.print(J2cFormat.format(ctx.throwableBP));
	    terminateAndIndent(" *_) { ");
	    if (hasGcFrame)
		terminateAndIndent("currentContext->gcTop = &frame; ");
	    if (!dom.isExecutive())
		w.print("try {");
	    w.print(J2cFormat.format(ctx.monitorExit));
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    w.print(", ");
	    compileWithType(synchVar, ctx.OopBP);
	    if (dom.isExecutive())
		terminateAndIndent(");");
	    else {
		terminateAndIndent("); }");
		w.print("catch (e_java_lang_Throwable *e) { ");
		if (hasGcFrame)
		    w.print("currentContext->gcTop = &frame; ");
		w.print("j2c_throw(");
		w.print(J2cFormat.format(ctx.translateThrowable));
		w.print("(");
		visitAppropriate(ctx.CSAvalue);
		terminateAndIndent(", e)); }");
	    }
	    terminateAndIndent("throw;");
	    terminateAndIndent("}");
	}
	terminate("}");
	// terminate("");
    }

    /**
     * Compile global helper functions.  Mostly, these functions are
     * used to assign well-known names to java methods (whose c++
     * names have a weird suffix).
     **/
    CodeGen(final PrintWriter w, J2cImageCompiler cmp) {
	this.w = w;

	S3Blueprint cfBP;
	try {
	    S3Domain exec = (S3Domain) cmp.getExecutiveDomain();
	    TypeName.Scalar cfN
		= RepositoryUtils.makeTypeName("s3/services/j2c",
					       "J2cCodeFragment");
	    S3Blueprint _cfBP = (S3Blueprint)
		exec.blueprintFor(cfN.asCompound(),
				  exec.getSystemTypeContext());
	    cfBP = (S3Blueprint) _cfBP.getSharedState().getBlueprint();
	} catch(LinkageException e) { throw e.unchecked(); }


	// void boot() { S3Executive_startup_xxxx(); }
	Object mainObject = cmp.getHeader("mainObject");
	S3ByteCode mainBC = (S3ByteCode) cmp.getHeader("mainMethod");
	Oop mainObjectOops = VM_Address.fromObject(mainObject).asOop();
	S3Blueprint mainBP= (S3Blueprint) mainObjectOops.getBlueprint();
	Type.Scalar mainType = mainBP.getType().asScalar();
	S3Method mainMethod
	    = (S3Method) mainType.getMethod(mainBC.getSelector());
	w.println("void boot() {");
	// OK.  We could build an expression for the call, but
	// it would be a lot easier to simply generate the string
	// here.
	w.print("\t");
	w.print(J2cValue.makeNonvirtualReference(mainMethod).getName());
	w.println("(&mainObject);");
	w.println("}");

	// j2c_throw(HEADER *ex)
	final TypeName.Scalar tName = JavaNames.java_lang_Throwable;
	// Match return type of CSA.processThrowable.
	w.println("e_java_lang_Error *j2c_throw(HEADER *o) {");
	w.println("\tswitch (HEADER_BLUEPRINT(o)->ovm_ctxNumber << 16 | HEADER_BLUEPRINT(o)->ovm_number) {");
	cmp.new BlueprintWalker() {
		S3Blueprint throwableBP;
		public void beforeDomain(S3Domain d) {
		    Type.Context sCtx = d.getSystemTypeContext();
		    try {
			throwableBP
			    = (S3Blueprint) d.blueprintFor(tName,sCtx);
		    }
		    catch (LinkageException e) { throw e.unchecked(); }
		}
		public void walkBlueprint(S3Blueprint bp) {
		    if (bp.isSubtypeOf(throwableBP) || 
		        bp.asTypeName().equals(Transaction.ABORTED_EXCEPTION)) { //PARBEGIN PAREND FIXME...
			w.print("\tcase ");
			w.print(bp.getCID() << 16 | bp.getUID());
			w.print(": throw (");
			w.print(CxxFormat.format(bp));
			w.println(" *) o;");
		    }
		    }
	    }.forAllDomains();
	// the unreachable abort() suppresses noreturn warnings
	w.println("\tdefault: assert(!\"bad throwable\"); abort();");
	w.println("}");
	w.println("}");

	// throwWilcardException(HEADER *ex)
	int idx = Context.getUTF("throwWildcardException");
	S3Method met = Context.findMethodObject(cfBP, idx);
	w.println("void throwWildcardException(HEADER *oop) {");
	w.print("\t");
	w.print(J2cValue.makeNonvirtualReference(met).getName());
	w.println("(0, (e_ovm_core_domain_Oop *) oop);");
	// the unreachable abort() suppresses noreturn warnings
	w.println("\tassert(!\"unreachable\"); abort();");
	w.println("}");


	// generateThrowable(int)
	idx = Context.getUTF("generateThrowable");
	met = Context.findMethodObject(cfBP, idx);
	w.println("void generateThrowable(int code) {");
	w.println("\tsigprocmask(SIG_SETMASK, &j2cNormalMask, 0);");
	w.print("\t");
	w.print(J2cValue.makeNonvirtualReference(met).getName());
	w.println("(0, code);");
	// the unreachable abort() suppresses noreturn warnings
	w.println("\tassert(!\"unreachable\"); abort();");
	w.println("}");

	// codeFromPc(jint)
// 	    idx = Context.getUTF("fromPC");
// 	    met = Context.findMethodObject(cfBP, idx);
// 	    w.println("e_s3_services_j2c_J2cCodeFragment *" +
// 		      "codeFromPC(jint pc) {");
// 	    w.print("\treturn ");
// 	    w.print(J2cFormat.format(met));
// 	    w.println("(0, pc);");
// 	    w.println("}");
    }

    /**
     * Generate per-domain helper methods, currently the just
     * &lt;domain&gt;_signalEvent().  This noninlinable function is
     * called whenever CHECK_EVENTS() returns true, and is responsible
     * for setting up native-level event handling state and calling
     * pollingEventHook() on the appropriate CoreServicesAccess object
     **/
    public CodeGen(PrintWriter w, Context ctx) {
	this.w = w;
	this.ctx = ctx;
	this.dom = ctx.domain;
	String dname = J2cFormat.format(dom);
	w.print("static void ");
	w.print(dname);
	w.println("_signalEvent() __attribute__((noinline));");

	w.print("static void ");
	w.print(dname);
	w.println("_signalEvent() {");
	w.println("\tassert(eventPollcheck());");
	MethodReference hook =
	    J2cValue.makeNonvirtualReference(ctx.pollingEventHook); 
	if (dom.isExecutive()) {
	    w.print("\t");
	    w.print(hook.getName());
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    terminateAndIndent(");");
	} else {
	    if (ctx.gcSupport)
		w.println("\tGCFrame *gcTop = currentContext->gcTop;");
	    w.print("\ttry {");
	    w.print(hook.getName());
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    terminateAndIndent("); }");
	    w.println("catch (e_java_lang_Throwable *ex) {");
	    if (ctx.gcSupport)
		w.print("\t\tcurrentContext->gcTop = gcTop;\n");
	    w.print("\t\tj2c_throw(");
	    w.print(J2cValue.makeNonvirtualReference(ctx.translateThrowable).getName());
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    w.println(", ex));");
	    w.println("\t}");
	}
	w.println("}");
    }

    void callCSA(S3Method m) {
	w.print(J2cFormat.format(m));
	w.print("(");
	visitAppropriate(ctx.CSAvalue);
	w.print(")");
    }

    /**
     * Coerce an expression of type `from' to type `to'.  This may or
     * may not involve a cast.  Return a string that should be printed
     * immediately after the expression, which will be either empty or
     * contain `)' depending on whether a cast was required.
     * 
     * @param from type to convert from (may be null)
     * @param to   type to convert to (may be null)
     *
     * @return a string to be printed after the expression
     *
     * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
     **/
    String coerce(S3Blueprint from, S3Blueprint to) {
	boolean needCast;

	if (from == to)
	    needCast = false;
	else if (from instanceof S3Blueprint.Primitive)
	    needCast = !(to instanceof S3Blueprint.Primitive);
	else if (from == ctx.OopBP || to == ctx.OopBP
		 || from == ctx.VM_AddressBP || to == ctx.VM_AddressBP
		 || from == ctx.VM_WordBP || to == ctx.VM_WordBP
		 || (to instanceof S3Blueprint.Array)
		 || (to instanceof S3Blueprint.Primitive))
	    needCast = true;
	else if (to.getType().isScalar() && !to.getType().isInterface())
	    // May need to downcast receiver on devirtualized call
	    needCast = !from.isSubtypeOf(to);
	else
	    needCast = false;

	if (needCast) {
	    w.print ("((");
	    w.print(CxxFormat.format(to));
	    if (to.isReference())
		w.print(" *");
	    w.print(" )");
	    return ")";
	} else
	    return "";
    }
	
    void compileWithType(J2cValue v, S3Blueprint t) {
	String tail = coerce(v.getBlueprint(ctx.domain), t);
	visitAppropriate(v);
	w.print(tail);
    }

    protected void visitDefault(Object o) {
    }

    /*
     * Handle SpecificationIR that is (mostly) shared with the
     * interpreter generator
     */
    public void visit(IRExpr exp) {
	outerValue = exp.dest;
// 	if (outerValue != null) {
// 	    try {
// 		// getJavaType may throw an error, in this case, assume
// 		// that we don't need a cast :)
// 		Type t = exp.dest.getJavaType(ctx.domain);
// 		S3Blueprint bp = ctx.blueprintFor(t);
// 		compileWithType(exp.source, bp);
// 		return;
// 	    }
// 	    catch (Error _) { }
//	}
	if (exp.source == null) {
	    w.print("0");
	} else {
	    visitAppropriate(exp.source);
	}
    }

    /*
     * Handle Values that appear within SpecIR expressions
     */
    public void visit(J2cNull _) {
	w.print("0");
    }

    public void visit(SharedStateReference v) {
	S3Blueprint ibp = (S3Blueprint)v.getBlueprint().getInstanceBlueprint();
	w.print("(&static_" + J2cFormat.format(ibp) + ")");
    }

    public void visit(BlueprintReference v) {
	if (v.isConcrete())
	    w.print("(&" + J2cFormat.formatBP(v.blueprintValue()) + ")");
	else
	    // FIXME: What exactly am I doing here.  Why is a
	    // reference to the blueprint of an object whose concrete
	    // type is unknown an instance of ConcreteScalar?
	    visit((J2cReference) v);
    }

    public void visit(ConcreteScalar v) {
	visitWithBP(v.value, v.getBlueprint());
    }
    public void visit(ConcreteArray v) {
	visitWithBP(v.value, v.getBlueprint());
    }
    public void visitWithBP(Oop pointerConst, S3Blueprint t) {
	int idx = FieldMagic._.findRoot(dom, pointerConst);
	// j2cRoots is already an Oop[], so we don't need to cast a
	// root to Oop
	if (t != ctx.OopBP)
	    w.print("((" + CxxFormat.format(t) + "*)");
	else
	    w.print("(");
	w.print(CxxFormat.format(dom) + "." + rootsField + "->values[" + idx + "])");
    }

    public void visit(J2cInt v) {
	if (v.isConcrete()) 
	    w.print("((" + v.getType() + ") 0x" +
		    Integer.toHexString(v.intValue())
		    + ")");
	else
	    visit((J2cValue) v);
    }

    public void visit(J2cNumeric v) {
	if (v.isConcrete()) {
	    String suffix = null;
	    Object printable = v.concreteValue();
	    switch (v.typeCode) {
	    case TypeCodes.LONG:
		suffix = "LL";
		printable
		    = "(jlong) 0x" + Long.toHexString(((Long) printable).longValue());
		break;
	    case TypeCodes.ULONG:
		suffix = "ULL";
		printable
		    = "0x" + Long.toHexString(((Long) printable).longValue());
		break;
	    case TypeCodes.DOUBLE:
		double d = ((Number) v.concreteValue()).doubleValue();
		if (Double.isNaN(d))
		    w.print("_j2c_NaN");
		else if (d == Double.POSITIVE_INFINITY)
		    w.print("HUGE_VAL");
		else if (d == Double.NEGATIVE_INFINITY)
		    w.print("-HUGE_VAL");
		else if (d == -0.0)
		    w.print("_j2c_nzero");
		else {
		    w.print(" ");
		    suffix = "";
		    break;
		}
		return;
	    case TypeCodes.FLOAT:
		float f = ((Number) v.concreteValue()).floatValue();
		if (Float.isNaN(f))
		    w.print("_j2c_NaNf");
		else if (f == Float.POSITIVE_INFINITY)
		    w.print("((float) HUGE_VAL)");
		else if (f == Float.NEGATIVE_INFINITY)
		    w.print("((float) -HUGE_VAL)");
		else if (f == -0.0F)
		    w.print("_j2c_nzerof");
		else {
		    w.print(" ");
		    suffix = "F";
		    break;
		}
		return;
	    }
	    w.print(printable + suffix);
	} else
	    visit((J2cValue) v);
    }

    J2cValue outerValue;
    
    public void visit(J2cValue v) {
	if (v.getName() != null) {
	    if (ctx.gcSupport && v.flavor == LocalAllocator.REF_VAR)
		w.print("frame.");
	    w.print(v.getName());
	} else if (v.source != null) {
	    J2cValue saveOuter = outerValue;
	    outerValue = v;
	    visitAppropriate(v.source);
	    outerValue = saveOuter;
	}
	else
	    throw new Error("can't compile value: " + v);
    }

    public void visit(InternalReference r) {
	J2cValue saveOuter = outerValue;
	outerValue = null;
	visitAppropriate(r.source);
	outerValue = saveOuter;
    }

    public void visit(ValueSource v) {
	throw new Error("can't compile ValueSource " + v);
    }

    public void visit(SymbolicConstant c) {
	w.print(c.name);
    }

    public void visit(CurrentPC _) {
	w.print("&&_pc" + nextBlock().getStartPC());
    }

    public void visit(PCRefExp exp) {
	if (findTarget(exp.target) == null)
	    // FIXME: CrossDomainCallExpr interaction with spill/reload 
	    w.print("0");
	else
	    w.print("&&_pc" + exp.target);
    }

    S3Blueprint curRetBP;
    S3Blueprint[] curArgBP;

    public void visit(J2cLookupExp exp) {
	w.print("((");
	ptype(curRetBP);
	w.print("(*)(");
	String prefix = "";
	for (int i = 0; i < curArgBP.length; i++)
	    if (curArgBP[i] != null) {
		w.print(prefix);
		ptype(curArgBP[i]);
		prefix = ", ";
	    }
	w.print("))");
	
	visitAppropriate(exp.bp);

	String table = ("ovm_j2c"
			+ Character.toUpperCase(exp.tableName.charAt(0))
			+ exp.tableName.substring(1, exp.tableName.length()));
	w.print("->"  + table + "->values[");
	visitAppropriate(exp.index);
	w.print("])");
    }

    public void visit(JValueExp exp) {
	if (outerValue.isPrimitive() || outerValue.isWidePrimitive()) {
	    visitAppropriate(exp.source);
	    w.print(".jv_" + outerValue.getType());
	} else {
	    w.print("((" + outerValue.getType() + ")");
	    visitAppropriate(exp.source);
	    w.print(".jv_jref)");
	}
    }

    public void visit(BinExp exp) {
	Type t = outerValue.getBlueprint(ctx.domain).getType();
	if (exp.operator == "%") {
	    if (t == ctx.domain.FLOAT || t == ctx.domain.DOUBLE) {
		w.print("fmod(");
		visitAppropriate(exp.lhs);
		w.print(", ");
		visitAppropriate(exp.rhs);
		w.print(")");
		return;
	    }
	}
	if ((exp.operator == "/" || exp.operator == "%")
	    && t != ctx.domain.FLOAT && t != ctx.domain.DOUBLE
	    && ((J2cValue) exp.rhs).isConcrete()
	    && exp.rhs.intValue() == 0)
	{
	    /* Work around apple gcc-3.3 bug: it gets stuck trying to
	     * compile division by 0.  This code will have to change
	     * if we ever want to do hardware division by zero checks.
	     */
	    w.print("({ assert(!\"/0 unreachable\"); 0; })");
	    return;
	}
	w.print("(");
	visitAppropriate(exp.lhs);
	w.print(exp.operator);
	visitAppropriate(exp.rhs);
	w.print(")");
    }
    
    public void visit(UnaryExp exp) {
	w.print("(" + exp.operator + " (");
	visitAppropriate(exp.arg);
	w.print("))");
    }

    public void visit(ConversionExp exp) {
	    w.print("((" + outerValue.getType() + ")");
	    visitAppropriate(exp.before);
	    w.print(")");
    }

    public void visit(ReinterpretExp exp) {
	Type type = outerValue.getJavaType(ctx.domain);
	char tag = type.getUnrefinedName().getTypeTag();
	if (outerValue.isReference()
	    && (exp.before instanceof J2cFloat
		|| exp.before instanceof J2cDouble)) {
	    w.print("((" + outerValue.getType() + ") to_jvalue(");
	    visitAppropriate(exp.before);
	    w.print(").jv_ref)");
	} else if (outerValue.isReference()) {
	    w.print("((" + outerValue.getType() + ")");
	    visitAppropriate(exp.before);
	    w.print(")");
	} else {
	    w.print("to_jvalue(");
	    visitAppropriate(exp.before);
	    w.print(").jv_");
	    w.print(J2cValue.typeCodeToCtype[tag]);
	}
    }

    public void visit(CCastExp exp) {
	w.print("((" + exp.type + ")");
	visitAppropriate(exp.exp);
	w.print(")");
    }

    public void visit(ShiftMaskExp exp) {
	w.print("((");
	visitAppropriate(exp.exponent);
	if (((J2cValue) exp.sizeType) instanceof J2cInt)
	    w.print(") & 0x1f)");
	else
	    w.print(") & 0x3f)");
	
    }

//    public void visit(ListElementExp) { }  used?

    public void visit(LocalStore exp) {
	if (true)
	    throw new Error("LocalStore left lying around!");
 	int i = exp.index.intValue();
	J2cValue val = (J2cValue) exp.value;
	if (val.isPrimitive() || val.isWidePrimitive())
	    w.print("_local" + i + ".jv_" + val.getType() + " = ");
	else
	    w.print("_local" + i + ".jv_jref = ");

	// extra cast because C++ doesn't like void *'s
	if (val.isJumpTarget())
	    w.print(" (HEADER *)");
	
	visitAppropriate(val);
    }

    // used in array store...
    public void visit(AssignmentExp exp) {
	visitAppropriate(exp.dest);
	w.print(" = ");
	if (exp.dest instanceof InternalReference)
	    visitAppropriate(exp.src);
	else
	    compileWithType((J2cValue) exp.src,
			    ((J2cValue) exp.dest).getBlueprint(ctx.domain));
    }

    public void visit(ValueAccessExp exp) {
        compileWithType(exp.v, outerValue.getBlueprint(ctx.domain));
    }
    
    public void visit(CondExp exp) {
	w.print("(");
	// FIXME: I would rather not have to even use the type
	// e_ovm_core_domain_Oop, and simply assign all Oop values the
	// C++ type of HEADER, but that isn't possible.  In many
	// cases, I can work around this by infering an Oop value's
	// real type from its uses, but not here
	if (exp.lhs.isReference() &&
	    ((J2cValue) exp.lhs).getBlueprint(ctx.domain) == ctx.OopBP)
	    w.print("(HEADER*) ");
	visitAppropriate(exp.lhs);
	w.print(exp.operator);
	if (exp.rhs.isReference() &&
	    ((J2cValue) exp.rhs).getBlueprint(ctx.domain) == ctx.OopBP)
	    w.print("(HEADER*) ");
	visitAppropriate(exp.rhs);
	w.print(")");
    }

    public void visit(IfExp ifExp) {
	w.print("(");
	visitAppropriate(ifExp.cond);
	w.print(" ? ");
	visitAppropriate(ifExp.ifTrue);
	w.print(" : ");
	visitAppropriate(ifExp.ifFalse);
	w.print(")");
    }
    public void visit(IfCmd ifCmd) {
	w.print("if (");
	visitAppropriate(ifCmd.cond);
	terminateAndIndent(") {");
	visitAppropriate(ifCmd.ifTrue);
	terminateAndIndent(";");
	if (ifCmd.ifFalse != null) {
	    terminateAndIndent("} else {");
	    visitAppropriate(ifCmd.ifFalse);
	    terminateAndIndent(";");
	}
	w.print("}");
    }

//    public void visit(BitFieldExp exp) { } used?

    public void visit(MemExp exp) {
	w.print("*(" + outerValue.getType() + "*)");
	w.print("((char*)");
	visitAppropriate(exp.addr); 
	w.print(" + "); 
	visitAppropriate(exp.offset); 
	w.print(")"); 
    }

    public void visit(ArrayAccessExp exp) {
	w.print("(");
	visitAppropriate(exp.arr);
	w.print("->values[");
	visitAppropriate(exp.index);
	w.print("])");
    }
    
    public void visit(ArrayLengthExp exp) {
	w.print("(");
	visitAppropriate(exp.arr);
	w.print("->length)");
    }

    public void visit(BlueprintAccessExp exp) {
	w.print("HEADER_BLUEPRINT(");
	visitAppropriate(exp.ref);
	w.print(")");
    }

    public void visit(J2cFieldAccessExp exp) {
	String name = J2cFormat.getUTF(exp.selector.getNameIndex());
	visitAppropriate(exp.obj);
	w.print("->" + CxxFormat.format(exp.declaringBP) + "::"
		+  CxxFormat.encode(name));
    }

    public void visit(InvocationExp exp) {
	curRetBP = exp.rt;
	curArgBP = exp.at;
	
	String rest = (outerValue == null
		       ? ""
		       : coerce(exp.rt, outerValue.getBlueprint(ctx.domain)));
	visitAppropriate(exp.target);
	w.print("(");
	String prefix = "";
	for (int i = 0; i < exp.args.length; i++) {
	    w.print(prefix);
	    w.flush();
	    if (exp.at != null)
		compileWithType((J2cValue) exp.args[i], exp.at[i]);
	    else
		visitAppropriate(exp.args[i]);
	    if (exp.args[i].isWidePrimitive())
		i++;
	    prefix = ", ";
	}
	w.print(")");
	w.print(rest);
    }

    public void visit(CrossDomainCallExp exp) {
	if (outerTranslate)
	    visitAppropriate(exp.call);
	else {
	    w.print("try { ");
	    visitAppropriate(exp.call);
	    terminateAndIndent("; }");
	    w.print("catch (e_java_lang_Throwable *e) { ");
	    if (hasGcFrame)
		w.print("currentContext->gcTop = &frame; ");
	    w.print("j2c_throw(");
	    w.print(J2cFormat.format(ctx.translateThrowable));
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    terminateAndIndent(", e)); }");
	}
    }
    
    public void visit(SeqExp exp) {
	char open;
	char delim;
	String close;

	if (outerValue instanceof J2cVoid) {
	    open = '{';
	    delim = ';';
	    close = "; }";
	} else {
	    open = '(';
	    delim = ',';
	    close = ")";
	}
	char prefix = open;
	for (int i = 0; i < exp.v.length; i++, prefix = delim) {
	    w.print(prefix);
	    w.print(" ");
	    visitAppropriate(exp.v[i]);
	}
	w.print(close);
    }

    public void visit(DimensionArrayExp exp) {
	terminateAndIndent("({");
	terminateAndIndent("e_Array<jint, " + exp.dims.length + ">"
			   + " _dims;");
	terminateAndIndent("_dims.length = " + exp.dims.length + ";");
	// Should stamp with blueprint?
	for (int i = 0; i < exp.dims.length; i++) {
	    w.print("_dims.values[ " + i + "] = ");
	    visitAppropriate(exp.dims[i]);
	    terminateAndIndent(";");
	}
	// e_Array<jint, n> not subtype of e_Array<jint, 1>!
	terminateAndIndent("(e_Array<jint> *) &_dims;");
	w.print("})");
	fresh = false;
    }

    public void visit(ArrayDataExp exp) {
	visitAppropriate(exp.exp);
	w.print("->values");
    }

    /**
     * Instructions whose behavior isn't completely described by
     * SpecIR
     */
    void enter(BBSpec to, BBSpec from) {
	try {
	    for (int i = 0; i < from.stackOutputs.length; i++) {
		if (to.stackInputs[i] == null) {
		    // constant values such as the J2cNull in the
		    // following snippet get nulled out as well
		    // m(null, b ? 1.0 : 0.0);
		    //OVMBase.OVMassert(from.stackOutputs[i] == null);
		    continue;
		}
		w.print(to.stackInputs[i].getName());
		w.print(" = ");
		if (to.stackInputs[i] instanceof J2cJumpTarget)
		    visitAppropriate(from.stackOutputs[i]);
		else
		    compileWithType(from.stackOutputs[i],
				    to.stackInputs[i].getBlueprint(ctx.domain));
		terminateAndIndent(";");
	    }
	} catch (RuntimeException e) {
	    System.err.println("unable to enter block " + to +
			       " from " + from);
	    throw e;
	}
    }

    BBSpec findTarget(int pc) {
	for (int i = 0; i < blocks.length; i++)
	    if (blocks[i].getStartPC() == pc)
		return blocks[i];
	return null;
    }

    void jump(BBSpec from, int target) {
	terminateAndIndent("{");
	enter(findTarget(target), from);
	terminateAndIndent("goto _pc" + target + ";");
	terminateAndIndent("}");
    }
    
    public void visit(PollCheckExpr exp) {
	terminateAndIndent("if (CHECK_EVENTS())");
	w.print("\t");
	w.print(J2cFormat.format(ctx.domain));
	terminateAndIndent("_signalEvent();");
    }

    public void visit(BRExpr e) {
	if (e.inputs.length > 0) {
	    w.print("if (");
	    visitAppropriate(e.inputs[0]);
	    w.print(") ");
	}
	jump(curBlock, e.target);
    }

    public void visit(SWExpr e) {
	w.print("switch (");
	visitAppropriate(e.inputs[0]);
	terminateAndIndent(") {");

	for (int i = 0; i < e.keys.length; i++) {
	    w.print("case " + e.keys[i] + ": ");
	    jump(curBlock, e.targets[i]);
	}
	w.print("default: ");
	jump(curBlock, e.dflt);
	terminateAndIndent("}");
    }

    // Need a whole new BCExpr subclass for multinewarray_quick
    J2cValue[] inputs;

    public void visit(BCExpr exp) {
	inputs = exp.inputs;
	visitAppropriate(exp.source);
    }

    public void visit(Instruction.ReturnValue _) {
	if (m.getMode().isSynchronized()) {
	    w.print(J2cFormat.format(ctx.monitorExit));
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
            w.print(", (e_ovm_core_domain_Oop *)");
            visitAppropriate(synchVar);
	    terminateAndIndent(");");
	}
	if (hasGcFrame)
	    terminateAndIndent("currentContext->gcTop = frame.next;");
	fresh = false;
	w.print("return ");
	try {
	    compileWithType(inputs[0], ctx.blueprintFor(m.getReturnType()));
	} catch (LinkageException e) { throw e.unchecked(); }
    }
    public void visit(Instruction.RETURN _) {
	if (m.getMode().isSynchronized()) {
	    // FIXME: Duplicate code
	    w.print(J2cFormat.format(ctx.monitorExit));
	    w.print("(");
	    visitAppropriate(ctx.CSAvalue);
	    terminateAndIndent(", (e_ovm_core_domain_Oop *) ");
            visitAppropriate(synchVar);
	    terminateAndIndent(");");
	}
	if (hasGcFrame)
	    terminateAndIndent("currentContext->gcTop = frame.next;");
	fresh = false;
	w.print("return");
    }
    public void visit(Instruction.RET _) {
	// inputs[0] isn't an intermediate value that holds a
	// LocalExp, it is the actual value we found in the abstract
	// machine.  Luckily, everything still works
	w.print("goto *");
	visitAppropriate(inputs[0]);
    }

    public void visit(TryExpr e) {
	closeStackScope();
	terminateAndIndent("try {");
    }
    public void visit(CatchExpr e) {
	if (e.first) {
	    closeStackScope();
	    terminateAndIndent("}");
	}
	w.print("catch (");
	w.print(CxxFormat.format(e.catchType));
	w.print(" *c) { ");
	if (hasGcFrame)
	    w.print("currentContext->gcTop = &frame; ");
	int hpc = e.handler.getHandlerPC();
	BBSpec b = findTarget(hpc);
	if (b != null) {
	    J2cValue ex = b.stackInputs[0];
	    visit(ex);
	    w.print(" = (");
	    w.print(CxxFormat.format(b.stackInputs[0].getBlueprint(null)));
	    w.print("*) c; goto _pc");
	    w.print(hpc);
	} else {
	    w.print("assert(!\"unreachable catch\")");
	    System.err.println("J2C: " + m + " contains unreachable catch of "
			       + e.catchType);
	}
	terminateAndIndent("; }");
    }
}
