package s3.services.j2c;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;

import org.ovmj.util.Runabout;

import ovm.core.domain.Blueprint;
import ovm.core.domain.Code;
import ovm.core.domain.Domain;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.Field;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Method;
import ovm.core.domain.ObjectModel;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
import ovm.core.execution.NativeConstants;
import ovm.core.repository.JavaNames;
import ovm.core.repository.RepositoryUtils;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.services.memory.MemoryManager;
import ovm.core.services.memory.VM_Address;
import ovm.core.services.process.HostedProcess;
import ovm.services.bytecode.JVMConstants;
import ovm.services.bytecode.analysis.Frame;
import ovm.services.bytecode.analysis.State;
import ovm.util.Arrays;
import ovm.util.BitSet;
import ovm.util.HashMap;
import ovm.util.Iterator;
import ovm.util.ListIterator;
import ovm.util.Map;
import ovm.util.OVMRuntimeException;
import ovm.util.S3Set;
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.bootimage.Analysis;
import s3.services.bootimage.Ephemeral;
import s3.services.bootimage.ImageObserver;
import s3.util.PragmaInline;
import s3.util.PragmaNoInline;
import s3.util.Walkabout;

public class J2cImageCompiler extends ImageObserver
    implements Ephemeral.Void
{
    // Parameters controlled by makeimage arguments:
    /**
     * Command line argument.  If set, don't explicitly compile all
     * methods in a domain
     **/
    boolean compileDomains; // true;

    /**
     * Command line argument.  Value of CFLAGS to pass to make.
     **/
    /*final*/ String cflags; // "-g"

    /**
     * Command line argument   Should we allocate references GCFrame
     * structures or directly in local variables
     **/
    // non-final to break initialization cycles.  Actually, there may
    // come a point when we can support precise garbage collectors
    // with or without GCFrame structures.
    /*final*/ boolean gcSupport;	// false

    // Some hard-coded parameters.  Edit to taste:

    /**
     * Set this to optimize the bit maps returned by
     * Blueprint.getRefMask(): If no subtype of a field's declared
     * type is ever heap-allocated, the garbage collector need not
     * walk it.
     *
     * It seems to work modulo Oop and Opaque, but it doesn't do as
     * much as I hoped.
     **/
    static final boolean PRUNE_GC_MAPS = false;

    /**
     * Set this to disable try block generation.
     **/
    static final boolean EXCEPTIONS_BROKEN = false;

    /**
     * If true, keep extremely detailed log of the compilation of
     * each method, and dump it in the case of an error.  A lot of
     * work has gone into keeping the logging calls fast when
     * KEEP_LOGS is false, but if KEEP_LOGS stops being a compile-time
     * contant, much of that work will vanish.
     **/
    static final boolean KEEP_LOGS = false;

    /**
     * Keep various statistics that are printed at the end of a
     * compiler run.  New statistics can be printed by allocating
     * a subtype of StatPrinter.  Note that all StatPrinters will be
     * executed regardless of the value of the global KEEP_STATS
     * variable.  KEEP_STATS is just an easy way to turn lots of things
     * on at once.
     **/
    static final boolean KEEP_STATS = false;

    /**
     * J2c is capable of continuing from an exception raised compiling
     * a single method.  But we don't want to continue if every other
     * method crashes.
     **/
    static final int IGNORE_FAILURE_COUNT = 5;
 
    /**
     * If set, we return a non-zero exit status if we crashed
     * compiling any method.
     **/
    static final boolean FAILURES_ARE_FATAL = true;
    
    /**
     * measure execution time inside make
     **/
    static final boolean TIME_MAKE = false;

    /** true if C symbols are prefixed by `_' **/
    static final boolean AS_NEEDS_UNDERSCORE
	= NativeConstants.ASM_NEEDS_UNDERSCORE;

    static final String DIRECTORY = ".";
    static final String MAKE = "make";

   static int compilationFailures = 0;

    HashMap sym2addr = new HashMap();

    PrintWriter w;

    static int make(String target) throws IOException {
	return make(DIRECTORY, target);
    }

    static int make(String[] args) throws IOException {
	return make(DIRECTORY, args);
    }

    static int make(String dir, String arg) throws IOException {
	return make(dir, new String[] { arg });
    }
    
    static int make(String dir, String[] args) throws IOException {
	int offset = TIME_MAKE ? 2 : 1;
	String[] argv = new String[args.length + offset];
	if (TIME_MAKE)
	    argv[0] = "time";
	argv[offset - 1] = "make";
	System.arraycopy(args, 0, argv, offset, args.length);
	return HostedProcess.system(dir, argv);
    }

    static PrintWriter open(String name)
	throws IOException {
	name = DIRECTORY + '/' + name;
 	return new PrintWriter
 	    (new BufferedOutputStream(new FileOutputStream(name)));
    }

    static StatPrinter statPrinters = null;

    /**
     * This class was defined for the purpose of printing various
     * cache statistics, but it is really just a kind of atexit()
     * mechanism.  The printStats method of every StatPrinter
     * allocated will execute at the end of a succesful compiler run.
     * 
     * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
     */
    public abstract static class StatPrinter {
	StatPrinter next;
	public StatPrinter() {
	    next = statPrinters;
	    statPrinters = this;
	}
	public static int countNonNull(Object[] arr) {
	    int ret = 0;
	    for (int i = 0; i < arr.length; i++)
		if (arr[i] != null)
		    ret++;
	    return ret;
	}
	public abstract void printStats();
    }

    abstract class BlueprintWalker extends DomainWalker {
	protected Context ctx;
	
	public abstract void walkBlueprint(S3Blueprint bp);

	public void beforeDomain(S3Domain d) {
	    ctx = findContext(d);
	}

	// return false to skip this domain
	public boolean acceptDomain(S3Domain d) {
	    beforeDomain(d);
	    // return d == getExecutiveDomain();
	    return true;
	}

	public void walkDomain(Domain _d) {
	    S3Domain d = (S3Domain) _d;
	    if (acceptDomain(d))
		for (Iterator it = d.getBlueprintIterator(); it.hasNext(); ) {
		    S3Blueprint bp = (S3Blueprint) it.next();
		    walkBlueprint(bp);
		}
	}
    }
	    
	
    static final String[] headerSymbol = new String[] {
	"coreServicesAccess",
	"mainObject",
	"mainMethod",		// only used at compile-time
	"bootContext"
    };
    
    DomainWalker genExterns = new MethodWalker() {
	    public void walk(Context ctx, S3Method meth, S3ByteCode cf) {
		w.print("extern e_s3_services_j2c_J2cCodeFragment ");
		w.print(J2cFormat.format(meth));
		w.println("_code;");
	    }
	    public void walkDead(Context _, S3Method __) { }
	    
	    public void beforeDomain(S3Domain dom) {
		w.print("extern e_s3_core_domain_");
		w.print(dom.isExecutive()
			? "S3ExecutiveDomain "
			: "S3JavaUserDomain ");
		w.println(J2cFormat.format(dom) + ";");
		super.beforeDomain(dom);
	    }

	    public void walkBlueprint(S3Blueprint bp) {
		w.print("extern e_s3_core_domain_S3Blueprint");
		w.print(bp instanceof Blueprint.Scalar ? "_Scalar "
			: bp instanceof Blueprint.Array ? "_Array "
			: "_Primitive ");
		w.println(J2cFormat.formatBP(bp) + ";");
		if (bp.isSharedState()
		    && ctx.shouldCompile(bp.getInstanceBlueprint()))
		{
		    w.print("extern " + J2cFormat.format(bp) + " ");
		    w.println(J2cFormat.formatShSt(bp) + ";");
		}
		super.walkBlueprint(bp);
	    }

	    public void forAllDomains() {
		for (int i = 0; i < headerSymbol.length; i++) {
		    Object value = getHeader(headerSymbol[i]);
		    Oop oops = VM_Address.fromObject(value).asOop();
		    S3Blueprint bp = (S3Blueprint) oops.getBlueprint();
		    w.print("extern ");
		    w.print(J2cFormat.format(bp));
		    w.print(" ");
		    w.print(headerSymbol[i]);
		    w.println(";");
		}
		super.forAllDomains();
	    }
	};

    DomainWalker genHeapSymbols = new MethodWalker() {
	    void genHeapSymbol(String name, Object _value) {
		if (AS_NEEDS_UNDERSCORE)
		    name = "_" + name;
		int value = VM_Address.fromObject(_value).asInt();
	
		w.print("\t.globl ");
		w.println(name);
		w.print("\t.set ");
		w.print(name);
		w.print(", 0x");
		w.print(Integer.toHexString(value));
		w.println("");
	    }

	    public void walk(Context ctx, S3Method meth, S3ByteCode cf) {
		genHeapSymbol(J2cFormat.format(meth) + "_code",
			      meth.getCode(J2cCodeFragment.KIND));
	    }
	    public void walkDead(Context _, S3Method __) { }

	    public void beforeDomain(final S3Domain dom) {
		new RunInExecutive() {
		    public void run() {
			genHeapSymbol(J2cFormat.format(dom), dom);
		    }
		}.go();
		super.beforeDomain(dom);
	    }

	    public void walkBlueprint(S3Blueprint bp) {
		genHeapSymbol(J2cFormat.formatBP(bp), bp);
		if (bp instanceof S3Blueprint.Scalar && !bp.isSharedState()
		    && ctx.shouldCompile(bp))
		    genHeapSymbol("static_" + J2cFormat.format(bp),
				  bp.getSharedState());
		super.walkBlueprint(bp);
	    }

	    public void forAllDomains() {
		for (int i = 0; i < headerSymbol.length; i++) {
		    Object value = getHeader(headerSymbol[i]);
		    genHeapSymbol(headerSymbol[i], value);
		}
		super.forAllDomains();
	    }
	};

    // FIXME: uncomment the method below to see MemberResolver blow up
//     void addHeader(String name, Object value) {
//     }

    void ptype(Context ctx, Type t) {
	S3Blueprint bp = ctx.blueprintFor(t);
	w.print(J2cFormat.format(bp));
	if (bp.isReference()) {
	    w.print(" *");
	}
    }

    public Context findContext(Domain dom) {
	return Context.findContext(this, dom);
    }
    
    public abstract class MethodWalker extends BlueprintWalker {
	public void walkBlueprint(S3Blueprint bp) {
	    // FIXME: Type.methodIterator() does not work, and
	    // you really want the declared methods anyway
	    if (ctx.shouldCompile(bp)) {
		// System.err.println("J2C: compiling methods of " +
		// bp);
		Type.Class t;
		if (bp.getType() instanceof Type.Class) {
		    t = (Type.Class) bp.getType();
		    forMethodsOf(bp, t, bp.getVTable());
		    forMethodsOf(bp, t, bp.getNVTable());
		}
		t = bp.getType().getSharedStateType();
		bp = ctx.blueprintFor(t);
		forMethodsOf(bp, t, bp.getNVTable());
	    }
	}

	void forMethodsOf(S3Blueprint bp, Type.Class t, Code[] method) {
	    TypeName tn = t.getName();
	    final boolean dbg = false;
	    if (tn instanceof TypeName.Gemeinsam)
		tn = ((TypeName.Gemeinsam) tn).getInstanceTypeName();
	
	    for (int i = 0; i < method.length; i++) {
		S3ByteCode cf = (S3ByteCode) method[i];
		if (cf == null) {
		    if (dbg)
			System.err.println("J2C: abstract method "
					   + bp.getSelectors()[i]);
		    continue;
		}
		Selector.Method msel = cf.getSelector();
		if (!msel.getDefiningClass().equals(tn)
		    && !msel.getDefiningClass().equals(t.getName())) {
		    if (dbg)
			System.err.println("J2C: inherited method " + msel);
		    continue;
		}
		S3Method meth = (S3Method) cf.getMethod();
		if (meth == null) {
		    // FIXME real soon:  When I'm at a good stopping
		    // point, I should comment out this if (dbg) and
		    // see if anything happens
		    if (dbg)
			System.err.println("can't find " + msel + " in " + t);
		    continue;
		}

		if (dbg)
		    System.err.println("handling " + msel);

		if (ctx.shouldCompile(meth))
		    walk(ctx, meth, cf);
		else
		    walkDead(ctx, meth);
	    }
	}

	public abstract void walk(Context ct,
				  S3Method meth, S3ByteCode cf);
	public abstract void walkDead(Context ct,
				      S3Method meth);
    }

    MethodWalker protoPrinter = new MethodWalker() {
	    BitSet[] reflectiveCalls;
	    
	    public void forAllDomains() {
		reflectiveCalls = new BitSet[DomainDirectory.maxContextID()+1];
		for (int i = 0; i < reflectiveCalls.length; i++) {
		    Type.Context tc = DomainDirectory.getContext(i);
		    if (tc != null)
			reflectiveCalls[i] = new BitSet(tc.getMethodCount());
		}
		super.forAllDomains();
	    }
	    public void walkDomain(Domain d) {
		Method[] meth = d.getReflectiveCalls();
		for (int i = 0; i < meth.length; i++) {
		    reflectiveCalls[meth[i].getCID()].set(meth[i].getUID());
		  //  Transaction.the().setReflectiveCalls(reflectiveCalls, d, meth, i);
		}
		super.walkDomain(d);
	    }	   
	    
	    public void walk(Context ct,
			     S3Method meth, S3ByteCode cf)
	    {
		// FIXME: what about finalize?
		if (J2cValue.isMethodNamed(meth)
		    || reflectiveCalls[meth.getCID()].get(meth.getUID())
		    || meth.getSelector().getNameIndex() == ct.clinitUtf
		    || meth.getSelector().getUnboundSelector() == JavaNames.FINALIZE
		    || meth.getSelector().getUnboundSelector() == JavaNames.LOAD_CLASS)
		{
		    // For static methods, this must be a shared state
		    S3Blueprint bp = ct.blueprintFor(meth.getDeclaringType());

		    w.print("static ");
		    if (PragmaInline.declaredBy(meth.getSelector(), bp))
			w.print("inline ");
		    try { ptype(ct, meth.getReturnType()); }
		    catch (LinkageException e) { throw e.unchecked(); }
		    w.print(' ' + J2cFormat.format(meth) + '(');
		    ptype(ct, meth.getDeclaringType());
		    int nargs = meth.getArgumentCount();
		    for (int i = 0; i < nargs; i++) {
			w.print(", ");
			try { ptype(ct, meth.getArgumentType(i)); }
			catch (LinkageException e) { throw e.unchecked(); }
		    }
		    w.print(")");

		    if (PragmaNoInline.declaredBy(meth.getSelector(), bp))
			w.print(" __attribute__((noinline))");

		    // Prevent gcc from removing non-virtual
		    // reflectively called methods.  Gcc does have the
		    // option of inlining non-reflective methods away.
		    if (reflectiveCalls[meth.getCID()].get(meth.getUID())
			|| (ctx.clinitIsLive
			    && (meth.getSelector().getNameIndex()
				== ctx.clinitUtf)))
			w.print("__attribute__((used))");

		    w.println(";");
		}
	    }

	    public void walkDead(Context ct,
				 S3Method meth) {
		if (J2cValue.isMethodNamed(meth)) {
		    w.print("#define ");
		    w.print(J2cFormat.format(meth));
		    w.print(" (");
		    try { ptype(ct, meth.getReturnType()); }
		    catch (LinkageException e) {
			w.print("HEADER *");
		    }
		    catch (LinkageException.Runtime e) {
			w.print("HEADER *");
		    }
		    w.println(" ) j2cFail");
		}
	    }
	    
	};

    static boolean debugMe = false;
    int debugName = -1;
    //{ debugName = Context.getUTF("main"); }
    Selector debugSel = null;

    interface CP extends JVMConstants { }

    /** Hack to allow special casing the image for j2c--jv*/
    public boolean isJ2c() {return true;}
    public boolean shouldQuickify() { return true; }

    public static State run(SpecInstantiation machine) {
	return run(machine, false);
    }
    public static State run(SpecInstantiation machine,
			    boolean showLinkageExceptions) {
       	SpecInstantiation.J2cFixpointIterator it
	    = machine.getFixpointIterator();
	Runabout[] visitors 
	    = new Runabout[] { machine, it.getPostVisitor() };
	Selector.Method msel = machine.s3Method.getSelector();
	// System.err.println("J2C: analyzing method " + msel);
	try {
	    ovm.services.bytecode.analysis.Driver.run(visitors, it);
	} catch (LinkageException.Runtime e) {
	    String msg = e.getMessage();
	    if (debugMe || showLinkageExceptions) {
		machine.dumpLog(System.err, e,
				machine.getSourceFile()
				+ ":" + machine.getLine()
				+ ": method " + msel + " is boot time: "
				+ msg);
		compilationFailures++;
	    }
	    else
		System.err.println(machine.getSourceFile()
				   + ":" + machine.getLine()
				   + ": method " + msel + " is boot time: "
				   + msg);
	    return null;
	}
	catch (RuntimeException e) {
	    machine.dumpLog(System.err, e,
			    "compilation of " + msel + " failed:");
	    compilationFailures++;
	    if (compilationFailures > IGNORE_FAILURE_COUNT)
		System.exit(-1);
	    return null;
	}
	catch (Error e) {
	    machine.dumpLog(System.err, e,
			    "compilation of " + msel + " failed:");
	    compilationFailures++;
	    if (compilationFailures > IGNORE_FAILURE_COUNT)
		System.exit(-1);
	    return null;
	}
	return it.getState();
    }

    MethodWalker methodCompiler = new MethodWalker() {
	public void beforeDomain(S3Domain d) {
	    System.err.println("compiling " + d);
	    super.beforeDomain(d);
	    // Generate per-domain helper functions.
	    new CodeGen(w, ctx);
	}
    
	public void walkDead(Context ct,
			     S3Method meth) {
	}
    
	public void walk(Context ct,
			 S3Method meth, S3ByteCode cf) {
	    int nbytes = cf.getBytes().length;
	    Selector.Method msel = meth.getSelector();
    
	    debugMe = (msel.getNameIndex() == debugName
		       || msel == debugSel);
    
	    Exceptions exInfo = new Exceptions(cf);
    
	    SpecInstantiation machine
		= new SpecInstantiation(ct, meth,
					exInfo.getHandlers());
	    State state = run(machine);
    
	    if (state == null) {
		walkDead(ct, meth);
		ct.dontCompile(meth);
		return;
	    }
	    if (debugMe)
		machine.dumpLog(System.err, null, "analyzing " + msel);
    
	    BitSet blockStart = new BitSet(nbytes + 1);
	    blockStart.set(0);
	    blockStart.set(nbytes);
	    // System.err.println("end of code at " + nbytes);
	    int nblocks = 1;
	    for (int pc = 1; pc < nbytes; pc++) {
		if (state.getFrameCountAt(pc) > 0) {
		    // System.err.println("bb at " + pc);
		    blockStart.set(pc);
		    nblocks++;
		}
	    }
	    machine.clearLog();
	    exInfo.recompute(machine.reachable);
	    BBSpec[] blocks = new BBSpec[nblocks];
	    ListIterator tryStart = exInfo.byStartPC();
	    ListIterator tryEnd = exInfo.byEndPC();
	    LocalAllocator allocator 
		= new LocalAllocator(ct,
				     machine.getMaxLocals(),
				     machine.getMaxStack(),
				     exInfo);
	    try {
		for (int start = 0, end = blockStart.nextSetBit(start + 1), i = 0;
		     end != -1;
		     start = end, end = blockStart.nextSetBit(start + 1), i++) {
		    Frame f = state.getFrameAt(start);
		    machine.logShort("block@" + start + " has frame " + f);
		    blocks[i] = new BBSpec(machine, state, allocator,
					   start, end,
					   tryStart, tryEnd);
		}
    
		// Now, we have to deal not only with blocks, but
		// Exception handlers: The C++ exception handlers do not
		// contain code, but merely assign the thrown object to
		// the handler's incoming stack node, and jumps to the
		// appropriate PC.
    
		// What about lexical blocks?  Does it help to limit the scope
		// of each temporary corresponding to a stack slot, or maybe
		// reuse them in some fashion?  Reusing them would probably
		// help, given gcc's difficulty with the interpreter.
		new CodeGen(w, meth, cf, blocks,
			    machine.storedVariables, allocator,
			    ct);
		if (debugMe)
		    machine.dumpLog(System.err, null, "compiling " + msel);
	    }
	    catch (RuntimeException e) {
		machine.dumpLog(System.err, e, "couldn't generate code for " + msel);
		compilationFailures++;
		if (compilationFailures > IGNORE_FAILURE_COUNT)
		    System.exit(-1);
		w.println("");
		w.println("compiler crash... continuing...");
		w.println("");
	    }
	    catch (Error e) {
		machine.dumpLog(System.err, e, "couldn't generate code for " + msel);
		compilationFailures++;
		if (compilationFailures > IGNORE_FAILURE_COUNT)
		    System.exit(-1);
		w.println("");
		w.println("compiler crash... continuing...");
		w.println("");
	    }
	    finally { debugMe = false; }
	}
    };

    static final int headerWords
        = ObjectModel.getObjectModel().headerSkipBytes()/4;
    
    DomainWalker dumpTables = new BlueprintWalker() {
	    void dumpTable(S3Blueprint bp, Code[] tab,
			   String suffix, boolean isUsed) {
		String name = J2cFormat.format(bp) + suffix;
		if (isUsed) {
		    w.println("# 1 \"vtables\"");
		 //   Type.Class t = (Type.Class) bp.getType();
		    w.print("void *");
		    w.print(name);
		    w.print("[] = {");
		    String prefix = ",\n\t(void *)";
		    // FIXME: fake an object header, we don't want
		    // to initialize these objects.  Note that the
		    // blueprint is null, because we don't assign
		    // symbols to array blueprints!
                    for (int i = 0; i < headerWords; i++)
		        w.print("\n\t(void *) 0,");
		    w.print("\n\t(void *) " + tab.length);
		    for (int j = 0; j < tab.length; j++) {
			w.print(prefix);
			if (tab[j] == null) {
			    w.print("0");
			    continue;
			}
			S3Method meth = (S3Method) tab[j].getMethod();
			if (ctx.shouldCompile(meth))
			    w.print(J2cFormat.format(meth));
			else
			    w.print("j2cFail");
		    }
		    w.println("\n};\n");
		} else {
			Object placeHolder = sym2addr.get(name);
			if (placeHolder == null) {
			    System.err.println("unused dispatch table in " +
					       "unused blueprint " + bp);
			    return;
			} 
			VM_Address nullAddr = VM_Address.fromObject(null);
			VM_Address.fromObject(placeHolder, nullAddr);
			sym2addr.remove(name);
		}
	    }
	    public void walkBlueprint(S3Blueprint bp) {
		if (ctx.shouldCompile(bp) && bp.getType().isClass()) {
		    dumpTable(bp, bp.getVTable(), "_vtable",
			      ctx.isVTableUsed(bp));
		    dumpTable(bp, bp.getIFTable(), "_iftable",
			      ctx.isIFTableUsed(bp));
		}
	    }

	    public void walkDomain(S3Domain d) {
		w.print("\n# 1 \"tables for ");
		w.print(d);
		w.println("\"");
		super.walkDomain(d);
	    }
	};

    /**
     * What is the long-term plan here?  The code fragment table
     * should contain the start and end pcs of every code fragment
     * that J2C generates.  
     *
     * What is a code fragment?  One answer is that a code fragment is
     * a C++ function whose address can be taken.  A more interesting
     * answer is that it is the region of code corresponding to the
     * invocation of some java method.  If either J2c or gcc chooses
     * to inline one method into another, there might be four code
     * fragments: one for the out-of-line callee, one for the region
     * of the caller before the inline call, one for the methods taken
     * together, and one for the region of the caller after the inline
     * call.  That sounds complicated.
     *
     * At minimum, end pcs are probably needed to support
     * Interpreter.getCode.
     *
     * A second approach might be to call addr2line, and map C++
     * function names back to J2cCodeFragment objects.  addr2line is
     * probably the quickest way to get Java stack traces working
     */
    void parseCodeIndexes(BufferedReader r) {
	final int[][] UID2mapIndex =
	    new int[DomainDirectory.maxContextID() + 1][];
	for (int i = 0; i < UID2mapIndex.length; i++) {
	    Type.Context tc = DomainDirectory.getContext(i);
	    if (tc != null) {
		UID2mapIndex[i] = new int[tc.getMethodCount()];
		Arrays.fill(UID2mapIndex[i], -1);
	    }
	}
	int nr = 0;
	try {
	    while (r.ready()) {
		int cuid = Integer.parseInt(r.readLine());
		int cid = cuid >> 16;
		int uid = cuid & 0xffff;
		UID2mapIndex[cid][uid] = nr++;
	    }
	} catch (IOException e) {
	    throw new RuntimeException(e);
	}
	new MethodWalker() {
	    public void walk(Context ct, S3Method meth, S3ByteCode _) {
		J2cCodeFragment cf =
		    (J2cCodeFragment) meth.getCode(J2cCodeFragment.KIND);
		cf.index = UID2mapIndex[meth.getCID()][meth.getUID()];
	    }
	    public void walkDead(Context _, S3Method __) { }
	}.forAllDomains();
    }

    void bind(String name, Object value) {
	VM_Address daddr = VM_Address.makeDeferred();
	VM_Address addr = VM_Address.fromObject(value, daddr); // is this need?
//	System.err.println(value + " taken from C symbol " + name);
	sym2addr.put(name, value);
    }

    // Here J2cImageCompiler's toplevel entry points, written in the
    // order in which they execute
    
    public J2cImageCompiler(String OVMMakefile,
			    String gdbinit,
			    String cflags) {
	super(OVMMakefile, gdbinit);
	this.cflags = cflags;
    }

    public void beforeRewriting(Gardener g) {
	gcSupport = MemoryManager.the().isPrecise();
	if (gcSupport)
	    cflags += " -DPRECISE_GC";
	
	// The compiler is reachable through the invisible stitcher,
	// but doesn't belong in the image
	g.excludeClass("s3/services/j2c", "J2cImageCompiler");

	// reachable from J2cCodeFragment consturctor.  Sadly, marking
	// the constructor as BCdead doesn't keep J2cFormat out of the
	// typename closure.
	g.excludeClass("s3/services/j2c", "J2cFormat");

	Selector.Method sel = RepositoryUtils.methodSelectorFor
	    ("Gs3/services/j2c/J2cCodeFragment;",
	     "throwWildcardException:(Lovm/core/domain/Oop;)V");
	getExecutiveDomain().registerCall(sel);
	sel = RepositoryUtils.methodSelectorFor
	    ("Gs3/services/j2c/J2cCodeFragment;",
	     "generateThrowable:(I)V");
	getExecutiveDomain().registerCall(sel);
    }

    public void registerGCAdvice(Walkabout w) {
	//VM_Address.fromObject(((S3Domain) getExecutiveDomain()).ROOT_BLUEPRINT);
	w.register(new Walkabout.FieldAdvice() {
		public Class targetClass() { return S3Blueprint.Scalar.class; }
		public Object beforeField(Object o,
					  java.lang.reflect.Field f,
					  Object value) {
		    S3Blueprint bp = (S3Blueprint) o;
		    if (!bp.isSharedState()) {
			String symbol;
			if (f.getName().equals("j2cVTable"))
			    symbol = J2cFormat.format(bp) + "_vtable";
			else if (f.getName().equals("j2cIfTable"))
			    symbol = J2cFormat.format(bp) + "_iftable";
			else
			    return value;
			if (sym2addr.get(symbol) == null)
			    bind(symbol, value);
			return null;
		    }
		    return value;
		}
	    });
    }
    // The following three methods pre-allocate data structures that
    // j2c will need.  The are called in the course of
    // rewriting/object graph construction as new types and methods
    // are discovered.  By the time that we start compiling (in
    // defineInitialHeap), everything we need should be allocated.
    //
    // addClass does not work.  The purpose is to remap two fields of
    // every blueprint from a hosted VM_Address[0] array to an
    // absolute address that comes from C++ code.  We have to do this
    // BEFORE the GC walks over the hosted array and assigns it an
    // address in the bootimage.
    public void addClass(Blueprint _bp) {
// 	S3Blueprint bp = (S3Blueprint) _bp;
// 	System.err.println("addClass: " + bp.getType());
// 	bind(J2cFormat.format(bp) + "_vtable",
// 	     FieldMagic._.getVTable(bp));
// 	bind(J2cFormat.format(bp) + "_iftable",
// 	     FieldMagic._.getIfTable(bp));
	// FIXME: should processConstants call move here?  Not exactly,
	// because method rewriting changes the constant pool
    }

    private boolean once = false;
    
    public void addMethod(Method meth) {
	S3ByteCode cf = (S3ByteCode) meth.getCode(S3ByteCode.KIND);

	// Make sure that global pointers into C++ data exist
        if (!once) {
            once = true;
            bind("j2c_method_ranges", J2cCodeFragment.ranges);
            bind("j2c_method_pointers", J2cCodeFragment.frags);
        }
	// Pre-allocate J2cCodeFragment object
	meth.addCode(new J2cCodeFragment((S3Method) meth, -1));

	// Make sure that contstants are resolved appropriately
	S3Constants cp = (S3Constants)cf.getConstantPool();
	if (cp == null)
	    System.err.println(meth + " has null constant pool");
	else
	    processConstants(findContext(meth.getDeclaringType().getDomain()),
			     cp);
    }

    private void processConstants(Context ctx, S3Constants cp) {
	VM_Address nullAddr
	    = VM_Address.fromObject(null);
	int len = cp.getConstantCount();
	for (int i = 0; i < len; i++) {
	    int tag = cp.getTagAt(i);
	    switch (tag) {
	    case CP.CONSTANT_String:
		Object rs = cp.getConstantAt(i);
		if (ctx.domain.isExecutive())
		    rs = cp.resolveConstantAt(i);
		FieldMagic._.findRoot(ctx.domain, 
				      VM_Address.fromObject(rs).asOop());
		break;
	    case CP.CONSTANT_Reference:
		Oop val = cp.resolveConstantAt(i);
		FieldMagic._.findRoot(ctx.domain, val);
		break;
	    case CP.CONSTANT_Binder:
		throw new Error("Binders should be resolved at this stage, or not?");
	    case CP.CONSTANT_SharedState:
		// nothing to be done
		break;
	    default:
		// nothing
		break;
	    }
	}
    }

    private final BlueprintWalker refPruner = new BlueprintWalker() { 
	    int nPruned;
	    int nRefs;
	    public void walkBlueprint(S3Blueprint bp) {
		if (bp instanceof Blueprint.Scalar
		    && (bp.isSharedState() || ctx.anal.isConcrete(bp))) {
		    int[] refmask = bp.getRefMask();
		    for (int i = 0; i < refmask.length; i++) {
			int wd = refmask[i];
			for (int j = 0; wd != 0;  j++, wd >>>= 1) {
			    if ((wd & 1) != 0)
				try {
				    int off = ((i << 5) + j) << 2;
				    Field f = bp.fieldAt(off);
				    S3Blueprint fbp
					= ctx.blueprintFor(f.getType());
				    if (fbp == null
					|| !ctx.anal.isHeapAllocated(fbp))
					{
					    System.err.println
						("pruning reference "
						 + (f == null
						    ? (" undefined " + bp +
						       " field at " + off)
						    : f.getSelector().toString()));
					    nPruned++;
					    refmask[i] |= ~(1 << j);
					}
				    nRefs++;
				} catch (LinkageException e) { throw e.unchecked(); }
			}
		    }
		}
	    }

	    public void walkDomain(Domain d) {
		nPruned = nRefs = 0;
		super.walkDomain(d);
		System.err.println("pruned " + nPruned + 
				   " of " + nRefs +
				   " references from class GC maps");
	    }
	};

    public void beforeGC(S3Set prev) {
	if (prev.size() == 0) {
	    new DomainWalker() {
		public void walkDomain(Domain d) {
		    Context ctx = findContext(d);
		    Object csaObj = d.getCoreServicesAccess();
		    VM_Address addr = VM_Address.fromObject(csaObj);
		    FieldMagic._.findRoot((S3Domain) d, addr.asOop());
		}
	    }.forAllDomains();
	}
	FieldMagic._.saveRoots();
    }
	    
    public void defineInitialHeap(Domain d, Analysis bootAnal, S3Set objects) {
	// Some initial housekeeping.  Define a couple symbols,
	// generate  heap_exports .s and
	// .hh files, and generate the ovm_inline.cc preable
	Context ctx = findContext(d);
	ctx.anal = bootAnal;


	try {
	    w = open("ovm_inline.cc");
	    w.println("// This file was generated by J2C");
	    w.println("#include \"j2c.hh\"");
	    w.println("#include \"ovm_inline.hh\"");
	    w.println("");

	    // Generate global helper methods
	    new CodeGen(w, this);
	} catch (IOException io) {
	    System.err.println(io.toString());
	}

	if (PRUNE_GC_MAPS)
	    refPruner.walkDomain(d);
//  	methodCompiler.walkDomain(d);
	//FieldMagic._.saveRoots();
    }

    public void beforeAllocation() {
	methodCompiler.forAllDomains();
    }

    public void beforeImageWrite() 
    {
	try {
 	    // methodCompiler.forAllDomains();
	    w.println("");
	    dumpTables.forAllDomains();
	    w.close();

	    // Declare blueprint, domain, and shared state objects
	    // defined by ovm_heap_exports.s
	    w = open("ovm_heap_exports.hh");
	    w.println("// This file was generated by J2C");
	    w.println("");
	    genExterns.forAllDomains();
	    w.close();

	    // Generate a .s file giving the address of each
	    // domain, blueprint, and shared state object
	    w = open("ovm_heap_exports.s");
	    genHeapSymbols.forAllDomains();
	    w.close();

	    // We could generate prototypes before the method bodies, 
	    // but we don't know which symbols should alias j2cFail
	    // until after the methods have been compiled.  gcc-3.1
	    // can be very finnicky about defining a function as an
	    // alias after a forward declaration.  The nice thing
	    // about aliases vs. #define is that an alias returns the
	    // expected type.
	    w = open("ovm_inline.hh");
	    w.println("// This file was generated by J2C");
	    w.println("");

	    protoPrinter.forAllDomains();
	    w.close();

	    // Now that we've generated the souce code, we can compile
	    // down to an executable and look up the addresses of
	    // symbols in our data segment.  The heap is patched with
	    // these addresses before it is written to disk.
	    if (make(new String[] { "-f", "OVMMakefile",
				    "OPT=" + cflags,
				    "DEBUG=",
				    "HEADER_SKIP_BYTES="+(ObjectModel
							  .getObjectModel()
							  .headerSkipBytes()),
				    "UNDERSCORE="+(AS_NEEDS_UNDERSCORE
						   ? "_" :
						   ""),
				    "ovm_heap_imports" }) != 0)
		System.exit(-1);
	    
	    BufferedReader r = new BufferedReader
		(new FileReader(DIRECTORY + "/code_indexes"));
	    parseCodeIndexes(r);
	    r.close();
	    
	    r = new BufferedReader
		(new FileReader(DIRECTORY + "/ovm_heap_imports"));
	    char[] hexBuf = new char[9];
	    while (true) {
		int nr = r.read(hexBuf, 0, 9);
		if (nr == -1)
		    break;
		else if (nr < 9 || hexBuf[8] != ' ')
		    throw new IOException("junk in ovm_heap_imports: `"
					  + new String(hexBuf, 0, nr)
					  + "'");
		if (AS_NEEDS_UNDERSCORE) {
		    int c = r.read();
		    if (c != '_') {
			System.err.println("missing underscore: `"
					   + (char) c + r.readLine()
					   + "'");
			continue;
		    }
		}
		String sym = r.readLine();
		if (sym == null)
		    throw new IOException("error parsing ovm_heap_imports");
		Object ref =  sym2addr.get(sym);
		if (ref != null) {
		    int val = Integer.parseInt(new String(hexBuf, 0, 8), 16);
		    VM_Address addr = VM_Address.fromInt(val);
		    VM_Address.fromObject(ref, addr);
                    // the object 'ref' will henceforward be mapped to 'addr'
		    sym2addr.remove(sym);
		}
		// Only warn about missing heap refs for symbols that
		// look like they should have refs
		else if (sym.startsWith("e_") || sym.startsWith("u1_"))
		    System.err.println("no heap refs to " + sym);
	    }
	    if (!sym2addr.isEmpty()) {
		VM_Address nullAddr
		    = VM_Address.fromObject(null);
		for (Iterator it = sym2addr.entrySet().iterator();
		     it.hasNext(); ) {
		    Map.Entry ent = (Map.Entry) it.next();
// 		    System.err.println(ent.getKey() + " not defined " +
// 				       "in program, using null");
		    VM_Address.fromObject(ent.getValue(), nullAddr);
		}
	    }
	    for (StatPrinter p = statPrinters; p != null; p = p.next)
		p.printStats();
	    if (FAILURES_ARE_FATAL && compilationFailures > 0) {
		System.err.println("J2c failed to compile "
				   + compilationFailures + " methods");
		System.exit(compilationFailures);
	    }
	}
	catch (IOException e) { throw new OVMRuntimeException(e); }
    }
}
