package s3.services.simplejit.optimization;

import java.util.Date;

import ovm.core.OVMBase;
import ovm.util.ArrayList;
import ovm.util.Iterator;
import s3.core.domain.S3Method;
import s3.services.simplejit.CompilerVMInterface;
import s3.services.simplejit.SimpleJITLogger;
import s3.services.simplejit.ir.SimpleJITBC2IR;
import s3.services.simplejit.ir.SimpleJITBasicBlock;
import s3.services.simplejit.ir.SimpleJITIRCode;
import s3.services.simplejit.ir.SimpleJITInstruction;
import s3.services.simplejit.ir.SimpleJITOperand;
import s3.services.simplejit.ir.SimpleJITOperand.SimpleJITTemporaryDefOperand;
import s3.services.simplejit.ir.SimpleJITOperand.SimpleJITTemporaryOperand;
import s3.services.simplejit.ir.SimpleJITOperand.SimpleJITTemporaryUseOperand;
import s3.services.simplejit.util.SimpleJITIdentitySet;

public class SimpleJITDataFlowAnalyzer {

    public static class Driver {

	private final SimpleJITBC2IR.Driver bc2ir_driver;
	private final SimpleJITPreInliner.Driver preinliner_driver;

	public Driver(SimpleJITBC2IR.Driver bc2ir_driver) {
	    this.bc2ir_driver = bc2ir_driver;
	    this.preinliner_driver = null;
	}

	public Driver(SimpleJITPreInliner.Driver preinliner_driver) {
	    this.bc2ir_driver = null;
	    this.preinliner_driver = preinliner_driver;
	}

	public SimpleJITIRCode
	    run(S3Method method,
		CompilerVMInterface compilerVMInterface) {

	   // String sig = method.getSelector().toString();

	    SimpleJITIRCode sjMethod = null;
	    if (preinliner_driver != null) {
		sjMethod = preinliner_driver.run(method,
						 compilerVMInterface);
	    } else if (bc2ir_driver != null) {
		sjMethod = bc2ir_driver.run(method,
					    compilerVMInterface);
	    } else
		throw new Error();
	    if (sjMethod == null)
		return null;

	    runDFA(sjMethod,
		   compilerVMInterface);

	    return sjMethod;
	}	    
	private void runDFA(SimpleJITIRCode sjMethod,
			    CompilerVMInterface compilerVMInterface) {
	    SimpleJITDataFlowAnalyzer dfa = 
		new SimpleJITDataFlowAnalyzer(sjMethod);
	    dfa.run();
	    dfa.print();
	}
    }

    /** This option enables logging in this abstract intepreter, if
     * this is turned on, the abstract interpretation gets way
     * slow. */
    public static final boolean LOG_ENABLED = false;

    /** If this option is enabled, full log messages will be logged,
     * whereas if it is disabled, only certain amount of the latest
     * log messages will be logged. This option is meaningful only
     * when LOG_ENABLED == true */
    private static final boolean FULL_LOG = true;

    /** This option specifies the size of the log message buffer when
     * fullLog == false */
    private static final int LOG_SIZE = 8 * 8192;

    private SimpleJITLogger logger;

    private SimpleJITBasicBlock.Set blockSet;

    private SimpleJITIRCode sjMethod;

    private long analysisTime = 0L;
    private Date startTime;
    private Date endTime;

    public SimpleJITDataFlowAnalyzer(SimpleJITIRCode sjMethod) {
	this.sjMethod = sjMethod;
	this.blockSet = sjMethod.getBlockSet();
	this.logger = 
	    FULL_LOG 
	    ? new SimpleJITLogger("SJDFA", System.err)
	    : new SimpleJITLogger("SJDFA", LOG_SIZE);
    }

    private void log(String message) {
	if (LOG_ENABLED)
	    logger.addLog(message);
    }

    private void __assert__(boolean shouldBeTrue) {
	OVMBase.OVMassert(shouldBeTrue);
    }

    public void run() {
	if (LOG_ENABLED)
	    log("[DFA " + sjMethod.getS3Method() + "]");
	startTime = new Date();
	depthFirstSearch();
	detectLoops();
	wrapTemporaryOperands();
	computeReachingDefinition();
	computeLiveness();
	fillUDChains();
	fillDUChains();
	endTime = new Date();
	analysisTime += endTime.getTime() - startTime.getTime();

	sjMethod.optimizationTime += analysisTime;
    }

    public void print() {
	if (LOG_ENABLED) {
	    printBlockSet();
	}
    }

    public void printBlockSet() {
	String b = blockSet.toString();
	log(b);
    }


    private void detectLoops() {
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(blockSet.getBlockList().toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITIdentitySet loop =
		findSingleEntryLoopStartingAt(b);
	    if (loop.size() > 0) {
		b.setLoopEntry();
		b.setBlockSetStartingAtThisBlock(loop);
		b.addLoop(b);
	    }
	    for(Iterator it = loop.iterator();
		it.hasNext(); ) {
		SimpleJITBasicBlock lb =
		    (SimpleJITBasicBlock)it.next();
		lb.addLoop(b);
	    }
	}
    }

    private SimpleJITIdentitySet 
	findSingleEntryLoopStartingAt(SimpleJITBasicBlock b) {
	SimpleJITIdentitySet loop = new SimpleJITIdentitySet();
	SimpleJITIdentitySet queue = new SimpleJITIdentitySet();
	SimpleJITBasicBlock[] preds = b.getPredecessors();
	for(int i = 0; i < preds.length; i++) {
	    SimpleJITBasicBlock p = preds[i];
	    if (p.getEdgeType(b) == SimpleJITBasicBlock.BACK_EDGE) {
		if (! loop.contains(p) && p != b) {
		    queue.add(p);
		    loop.add(p);
		}
	    }
	}
	while (! queue.empty()) {
	    SimpleJITBasicBlock x =
		(SimpleJITBasicBlock)queue.remove();
	    SimpleJITBasicBlock[] xpreds = x.getPredecessors();
	    for(int i = 0; i < xpreds.length; i++) {
		SimpleJITBasicBlock p = xpreds[i];
		if (p != b && ! loop.contains(p)) {
		    queue.add(p);
		    loop.add(p);
		}
	    }
	}
//	loop.add(b);
	return loop;
    }

    private int preorder;
    private int rpostorder;
    private void depthFirstSearch() {
	preorder = 1;
	rpostorder = blockSet.getBlockList().size();
	for(Iterator it = blockSet.getBlockList().iterator();
	    it.hasNext(); ) {
	    SimpleJITBasicBlock b =
		(SimpleJITBasicBlock)it.next();
	    b.setPreorder(0);
	    b.setReversePostOrder(0);
	}
	DFS(blockSet.getBeginBlock());
    }
    private void DFS(SimpleJITBasicBlock n) {
	n.setPreorder(preorder);
	preorder++;
	SimpleJITBasicBlock[] succs = n.getSuccessors();
	for(int i = 0; i < succs.length; i++) {
	    SimpleJITBasicBlock s = succs[i];
	    if (s.getPreorder() == 0) {
		n.setEdgeType(s, SimpleJITBasicBlock.TREE_EDGE);
		DFS(s);
	    } else if (s.getReversePostOrder() == 0) {
		n.setEdgeType(s, SimpleJITBasicBlock.BACK_EDGE);
	    } else if (n.getPreorder() < s.getPreorder()) {
		n.setEdgeType(s, SimpleJITBasicBlock.FORWARD_EDGE);
	    } else {
		n.setEdgeType(s, SimpleJITBasicBlock.CROSS_EDGE);
	    }
	}
	n.setReversePostOrder(rpostorder);
	rpostorder--;
    }

    /**
     * Establish use-def chains based on the computed reaching
     * definitions.
     */
    private void fillUDChains() {
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(blockSet.getBlockList().toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();

	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (! inst.isUser())
		    continue;
		SimpleJITIdentitySet reachingInDefs =
		    inst.getReachingInDefs();
		SimpleJITOperand[] uses = 
		    inst.asUser().getUseOperands();
		for(Iterator it = reachingInDefs.iterator();
		    it.hasNext(); ) {
		    SimpleJITTemporaryDefOperand oneDef = 
			(SimpleJITTemporaryDefOperand)it.next();
		    for(int j = 0; j < uses.length; j++) {
			if (uses[j] 
			    instanceof SimpleJITTemporaryUseOperand) {
			    SimpleJITTemporaryUseOperand u = 
				(SimpleJITTemporaryUseOperand)uses[j];
			    if (oneDef.equalsAsTemporary(u))
				u.addDef(oneDef);
			}
		    }
		}
	    }

	}
    }

    /**
     * Establish def-use chains based on the use-def chains
     */
    private void fillDUChains() {
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(blockSet.getBlockList().toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();

	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (! inst.isUser())
		    continue;
		SimpleJITOperand[] uses = 
		    inst.asUser().getUseOperands();
		for(int j = 0; j < uses.length; j++) {
		    if (uses[j] 
			instanceof SimpleJITTemporaryUseOperand) {
			SimpleJITTemporaryUseOperand u = 
			    (SimpleJITTemporaryUseOperand)uses[j];
			SimpleJITTemporaryDefOperand[] defs = u.getDefs();
			for(int k = 0; k < defs.length; k++) {
			    SimpleJITTemporaryDefOperand d = defs[k];
			    d.addUse(u);
			}
		    }
		}
	    }
	}
    }

    /**
     * Compute reaching definition
     */
    private void computeReachingDefinition() {
//	System.err.println("computeReachingDefinition");
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(blockSet.getBlockList().toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();

	    SimpleJITIdentitySet oldOut = b.getReachingOutDefs();
	    SimpleJITIdentitySet in = null;
	    SimpleJITIdentitySet out = null;
	    SimpleJITIdentitySet gen = null;

	    if (insts.length == 0) {
		in = new SimpleJITIdentitySet();
		SimpleJITBasicBlock[] predecessors = b.getPredecessors();
		for(int k = 0; k < predecessors.length; k++) {
		    SimpleJITIdentitySet reachingOutDefs =
			predecessors[k].getReachingOutDefs();
		    in.union(reachingOutDefs);
		}
		b.setReachingInDefs(in);
		out = in.copy();
		b.setReachingOutDefs(out);
		if (! oldOut.equals(out)) {
		    workset.addAll(b.getSuccessors());
		}
		continue;
	    }

	    /* For each instruction,
	     * OLD_OUT = OUT
	     * IN = UNION(OUT[p]) where p is a predecessor of this instruction
	     * OUT = UNION(GEN, IN - KILL)
	     * if OLD_OUT != OUT then add the successsors to the workset
	     */
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		SimpleJITOperand[] defs = null;
		if (inst.isDefiner()) {
		    defs = inst.asDefiner().getDefOperands();
		} else {
		    defs = new SimpleJITOperand[0];
		}
		if (i == 0) { // First instruction
		    in = new SimpleJITIdentitySet();
		    SimpleJITBasicBlock[] predecessors = b.getPredecessors();
		    for(int k = 0; k < predecessors.length; k++) {
			SimpleJITIdentitySet reachingOutDefs =
			    predecessors[k].getReachingOutDefs();
			in.union(reachingOutDefs);
		    }
		    b.setReachingInDefs(in);
		    inst.setReachingInDefs(in);
		} else { // otherwise
		    // IN
		    in = insts[i - 1].getReachingOutDefs();
		    inst.setReachingInDefs(in);
		}
		// GEN  : def from this inst
		gen = new SimpleJITIdentitySet(defs);
		// KILL & OUT : remove from IN all defs wrapping the
		// same temporary as GEN except GEN and union the
		// resulting IN and GEN
		out = new SimpleJITIdentitySet();
		out.union(gen);
		SimpleJITIdentitySet copyIn = in.copy();
		for(Iterator it = copyIn.iterator();
		    it.hasNext(); ) {
    		    SimpleJITTemporaryDefOperand oneOfIn = 
    			(SimpleJITTemporaryDefOperand)it.next();
		    boolean to_be_removed = false;
		    for(int j = 0; j < defs.length; j++) {
			if (! (defs[j] instanceof SimpleJITTemporaryDefOperand)) {
			    throw new Error
				("found non-tempdef at block " + b 
				 + " inst " + inst + " def " + defs[j]);
			}
			SimpleJITTemporaryDefOperand d = 
			    (SimpleJITTemporaryDefOperand)defs[j];
			if (oneOfIn == d)
			    continue; // skip GEN
			if (oneOfIn.equalsAsTemporaryExceptType(d))
			    to_be_removed = true;
		    }
		    if (to_be_removed)
			it.remove(); // remove oneOfIn
		}
		out.union(copyIn);
		inst.setReachingOutDefs(out);
		if (i == insts.length - 1)
		    b.setReachingOutDefs(out);
	    }

	    // if oldOut != newOut, add successors to the work list
	    if (! oldOut.equals(out)) {
		workset.addAll(b.getSuccessors());
	    }
	}
    }

    /**
     * Replace each TemporaryOperand with an either
     * TemporaryDefOperand or TemporaryUseOperand which wraps the
     * original TemporaryOperand.
     */
    private void wrapTemporaryOperands() {
//	System.err.println("wrapTemporaryOperands");
	SimpleJITBasicBlock.List blist =
	    blockSet.getBlockList().copy();
	while (! blist.empty()) {
	    SimpleJITBasicBlock b = blist.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (inst.isDefiner()) {
		    SimpleJITInstruction.Definer definer = 
			inst.asDefiner();
		    SimpleJITOperand[] defs = 
			definer.getDefOperands();
		    SimpleJITOperand[] new_defs = 
			new SimpleJITOperand[defs.length];
		    for(int j = 0; j < defs.length; j++) {
			SimpleJITOperand op = defs[j];
			if (op instanceof SimpleJITTemporaryOperand) {
			    new_defs[j] = 
				new SimpleJITTemporaryDefOperand
				((SimpleJITTemporaryOperand)op, b);
			} else {
			    new_defs[j] = defs[j];
			}
		    }
		    definer.setDefOperands(new_defs);
		}
		if (inst.isUser()) {
		    SimpleJITInstruction.User user = 
			inst.asUser();
		    SimpleJITOperand[] uses = 
			user.getUseOperands();
		    SimpleJITOperand[] new_uses = 
			new SimpleJITOperand[uses.length];
		    for(int j = 0; j < uses.length; j++) {
			SimpleJITOperand op = uses[j];
			if (op instanceof SimpleJITTemporaryOperand) {
			    new_uses[j] = 
				new SimpleJITTemporaryUseOperand
				((SimpleJITTemporaryOperand)op, b);
			} else {
			    new_uses[j] = uses[j];
			}
		    }
		    user.setUseOperands(new_uses);
		}
	    }
	}

    }


    /**
     * Compute reaching definition
     */
    private void computeLiveness() {
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(blockSet.getBlockList().toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();

	    SimpleJITIdentitySet oldIn = b.getLiveInUses();
	    SimpleJITIdentitySet in = null;
	    SimpleJITIdentitySet out = null;
	    SimpleJITIdentitySet gen = null;
	    if (insts.length == 0) {
		out = new SimpleJITIdentitySet();
		SimpleJITBasicBlock[] successors = b.getSuccessors();
		for(int k = 0; k < successors.length; k++) {
		    SimpleJITIdentitySet live_in_uses =
			successors[k].getLiveInUses();
		    out.union(live_in_uses);
		}
		b.setLiveOutUses(out);
		in = out.copy();
		b.setLiveInUses(in);
		if (! oldIn.equals(in)) {
		    workset.addAll(b.getPredecessors());
		}
		continue;
	    }

	    /* For each instruction,
	     * OLD_IN = IN
	     * OUT = UNION(IN[s]) where s is a successor of this instruction
	     * INT = UNION(GEN, OUT - KILL)
	     * if OLD_IN != IN then add the predecessors to the workset
	     */
	    for(int i = insts.length - 1; i >= 0; i--) {
		SimpleJITInstruction inst = insts[i];
		SimpleJITOperand[] uses = null;
		if (inst.isUser()) {
		    uses = inst.asUser().getUseOperands();
		} else {
		    uses = new SimpleJITOperand[0];
		}
		// Filter only temporary uses
		ArrayList tempUses = new ArrayList();
		for(int j = 0; j < uses.length; j++) {
		    if (uses[j] instanceof SimpleJITTemporaryUseOperand)
			tempUses.add(uses[j]);
		}
		uses = new SimpleJITOperand[tempUses.size()];
		tempUses.toArray(uses);

		SimpleJITOperand[] defs = null;
		if (inst.isDefiner()) {
		    defs = inst.asDefiner().getDefOperands();
		} else {
		    defs = new SimpleJITOperand[0];
		}
		if (i == insts.length - 1) { // Last instruction
		    out = new SimpleJITIdentitySet();
		    SimpleJITBasicBlock[] successors = b.getSuccessors();
		    for(int k = 0; k < successors.length; k++) {
			SimpleJITIdentitySet live_in_uses =
			    successors[k].getLiveInUses();
			out.union(live_in_uses);
		    }
		    b.setLiveOutUses(out);
		    inst.setLiveOutUses(out);
		} else { // otherwise
		    out = insts[i + 1].getLiveInUses();
		    inst.setLiveOutUses(out);
		}
		// GEN  : uses from this inst
		gen = new SimpleJITIdentitySet(uses);
		// IN = union(GEN, OUT - KILL)
		in = new SimpleJITIdentitySet();
		in.union(gen);
		
		SimpleJITIdentitySet copyOut = out.copy();
		for(Iterator it = copyOut.iterator();
		    it.hasNext(); ) {
		    SimpleJITTemporaryUseOperand oneOfOut =
			(SimpleJITTemporaryUseOperand)it.next();
		    boolean to_be_removed = false;
		    for(int j = 0; j < defs.length; j++) {
			SimpleJITTemporaryDefOperand d = 
			    (SimpleJITTemporaryDefOperand)defs[j];
			if (oneOfOut.equalsAsTemporaryExceptType(d))
			    to_be_removed = true;
		    }
		    if (to_be_removed)
			it.remove(); // remove oneOfOut
		}
		in.union(copyOut);
		inst.setLiveInUses(in);
		if (i == 0) {
		    b.setLiveInUses(in);
		}
	    }

	    // if oldIn != newIn, add predecessors to the work list
	    if (! oldIn.equals(in)) {
		workset.addAll(b.getPredecessors());
	    }
	}
    }

}
