/*
 * Copyright (c) 2002 - 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Adam Fuchs, Avik Chaudhuri, Steve Suh - Modified from stack to registers
 */
package com.ibm.wala.dalvik.ssa;

import java.util.Arrays;
import java.util.Iterator;

import com.ibm.wala.cfg.IBasicBlock;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.Language;
import com.ibm.wala.dalvik.classLoader.DexCFG;
import com.ibm.wala.dalvik.classLoader.DexCFG.BasicBlock;
import com.ibm.wala.dalvik.classLoader.DexIMethod;
import com.ibm.wala.dalvik.classLoader.Literal;
import com.ibm.wala.dalvik.dex.instructions.ArrayFill;
import com.ibm.wala.dalvik.dex.instructions.ArrayGet;
import com.ibm.wala.dalvik.dex.instructions.ArrayLength;
import com.ibm.wala.dalvik.dex.instructions.ArrayPut;
import com.ibm.wala.dalvik.dex.instructions.BinaryLiteralOperation;
import com.ibm.wala.dalvik.dex.instructions.BinaryOperation;
import com.ibm.wala.dalvik.dex.instructions.Branch;
import com.ibm.wala.dalvik.dex.instructions.CheckCast;
import com.ibm.wala.dalvik.dex.instructions.Constant;
import com.ibm.wala.dalvik.dex.instructions.GetField;
import com.ibm.wala.dalvik.dex.instructions.Goto;
import com.ibm.wala.dalvik.dex.instructions.InstanceOf;
import com.ibm.wala.dalvik.dex.instructions.Instruction;
import com.ibm.wala.dalvik.dex.instructions.Instruction.Visitor;
import com.ibm.wala.dalvik.dex.instructions.Invoke;
import com.ibm.wala.dalvik.dex.instructions.Monitor;
import com.ibm.wala.dalvik.dex.instructions.New;
import com.ibm.wala.dalvik.dex.instructions.NewArray;
import com.ibm.wala.dalvik.dex.instructions.NewArrayFilled;
import com.ibm.wala.dalvik.dex.instructions.PutField;
import com.ibm.wala.dalvik.dex.instructions.Return;
import com.ibm.wala.dalvik.dex.instructions.Switch;
import com.ibm.wala.dalvik.dex.instructions.Throw;
import com.ibm.wala.dalvik.dex.instructions.UnaryOperation;
import com.ibm.wala.shrikeBT.ArrayLengthInstruction;
import com.ibm.wala.shrikeBT.ConstantInstruction;
import com.ibm.wala.shrikeBT.GotoInstruction;
import com.ibm.wala.shrikeBT.IArrayLoadInstruction;
import com.ibm.wala.shrikeBT.IArrayStoreInstruction;
import com.ibm.wala.shrikeBT.IBinaryOpInstruction;
import com.ibm.wala.shrikeBT.IConditionalBranchInstruction;
import com.ibm.wala.shrikeBT.IGetInstruction;
import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.shrikeBT.IUnaryOpInstruction;
import com.ibm.wala.shrikeBT.MonitorInstruction;
import com.ibm.wala.shrikeBT.NewInstruction;
import com.ibm.wala.shrikeBT.ReturnInstruction;
import com.ibm.wala.shrikeBT.SwitchInstruction;
import com.ibm.wala.shrikeBT.ThrowInstruction;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.ISSABasicBlock;
import com.ibm.wala.ssa.PhiValue;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.ssa.SSACFG;
import com.ibm.wala.ssa.SSACFG.ExceptionHandlerBasicBlock;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAGetCaughtExceptionInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInstructionFactory;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSALoadMetadataInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSAPiInstruction;
import com.ibm.wala.ssa.SSAPiNodePolicy;
import com.ibm.wala.ssa.ShrikeIndirectionData;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.graph.dominators.Dominators;
import com.ibm.wala.util.intset.IntPair;

/**
 * This class constructs an SSA {@link IR} from a backing ShrikeBT instruction stream.
 *
 * The basic algorithm here is an abstract interpretation over the Java bytecode to determine types of stack locations and local
 * variables. As a side effect, the flow functions of the abstract interpretation emit instructions, eliminating the stack
 * abstraction and moving to a register-transfer language in SSA form.
 */
public class DexSSABuilder extends AbstractIntRegisterMachine {
    public static DexSSABuilder make(DexIMethod method, SSACFG cfg, DexCFG scfg, SSAInstruction[] instructions,
            SymbolTable symbolTable, boolean buildLocalMap, SSAPiNodePolicy piNodePolicy) throws IllegalArgumentException {
        if (scfg == null) {
            throw new IllegalArgumentException("scfg == null");
        }
        return new DexSSABuilder(method, cfg, scfg, instructions, symbolTable, buildLocalMap, piNodePolicy);

    }

    /**
     * A wrapper around the method being analyzed.
     */
    final private DexIMethod method;

    /**
     * Governing symbol table
     */
    final private SymbolTable symbolTable;

    /**
     * A logical mapping from &lt;bcIndex, valueNumber&gt; -&gt; local number if null, don't build it.
     */
    private final SSA2LocalMap localMap;

    /**
     * a factory to create concrete instructions
     */
    private final SSAInstructionFactory insts;

    /**
     * information about indirect use of local variables in the bytecode
     */
//    private final IndirectionData bytecodeIndirections;

    private final ShrikeIndirectionData shrikeIndirections;

    private DexSSABuilder(DexIMethod method, SSACFG cfg, DexCFG scfg, SSAInstruction[] instructions, SymbolTable symbolTable,
            boolean buildLocalMap, SSAPiNodePolicy piNodePolicy) {
        super(scfg);
        localMap = buildLocalMap ? new SSA2LocalMap(scfg, instructions.length, cfg.getNumberOfNodes(), method.getMaxLocals()) : null;
        init(new SymbolTableMeeter(cfg, scfg), new SymbolicPropagator(scfg, instructions,
                cfg, piNodePolicy));
        this.method = method;
        this.symbolTable = symbolTable;
        this.insts = method.getDeclaringClass().getClassLoader().getInstructionFactory();
//        this.bytecodeIndirections = method.getIndirectionData();
        this.shrikeIndirections = new ShrikeIndirectionData(instructions.length);
        assert cfg != null : "Null CFG";
    }

    private class SymbolTableMeeter implements Meeter {

        final SSACFG cfg;

        final DexCFG dexCFG;

        SymbolTableMeeter(SSACFG cfg, DexCFG dexCFG) {
            this.cfg = cfg;
//            this.instructions = instructions;
            this.dexCFG = dexCFG;
        }

//      public int meetStack(int slot, int[] rhs, IBasicBlock<Instruction> bb) {
//
//          assert bb != null : "null basic block";
//
//          if (bb.isExitBlock()) {
//              return TOP;
//          }
//
//          if (allTheSame(rhs)) {
//              for (int i = 0; i < rhs.length; i++) {
//                  if (rhs[i] != TOP) {
//                      return rhs[i];
//                  }
//              }
//              // didn't find anything but TOP
//              return TOP;
//          } else {
//              SSACFG.BasicBlock newBB = cfg.getNode(dexCFG.getNumber(bb));
//              // if we already have a phi for this stack location
//              SSAPhiInstruction phi = newBB.getPhiForStackSlot(slot);
//              int result;
//              if (phi == null) {
//                  // no phi already exists. create one.
//                  result = symbolTable.newPhi(rhs);
//                  PhiValue v = symbolTable.getPhiValue(result);
//                  phi = v.getPhiInstruction();
//                  newBB.addPhiForStackSlot(slot, phi);
//              } else {
//                  // already created a phi. update it to account for the
//                  // new merge.
//                  result = phi.getDef();
//                  phi.setValues(rhs.clone());
//              }
//              return result;
//          }
//      }

        @Override
        public int meetLocal(int n, int[] rhs, DexCFG.BasicBlock bb) {
            if (allTheSame(rhs)) {
                for (int rh : rhs) {
                    if (rh != TOP) {
                        return rh;
                    }
                }
                // didn't find anything but TOP
                return TOP;
            } else {
                SSACFG.BasicBlock newBB = cfg.getNode(dexCFG.getNumber(bb));
                if (bb.isExitBlock()) {
                    // no phis in exit block please
                    return TOP;
                }
                // if we already have a phi for this local
                SSAPhiInstruction phi = newBB.getPhiForLocal(n);
                int result;
                if (phi == null) {
                    // no phi already exists. create one.
                    result = symbolTable.newPhi(rhs);
                    PhiValue v = symbolTable.getPhiValue(result);
                    phi = v.getPhiInstruction();
                    newBB.addPhiForLocal(n, phi);
                } else {
                    // already created a phi. update it to account for the
                    // new merge.
                    result = phi.getDef();
                    phi.setValues(rhs.clone());
                }
                return result;
            }
        }

        /**
         * Are all rhs values all the same? Note, we consider TOP (-1) to be same as everything else.
         *
         * @return boolean
         */
        private boolean allTheSame(int[] rhs) {
            int x = -1;
            // set x := the first non-TOP value
            int i = 0;
            for (i = 0; i < rhs.length; i++) {
                if (rhs[i] != TOP) {
                    x = rhs[i];
                    break;
                }
            }
            // check the remaining values
            for (i = i + 1; i < rhs.length; i++) {
                if (rhs[i] != x && rhs[i] != TOP)
                    return false;
            }
            return true;
        }
    }

    @Override
    protected void initializeVariables() {
        MachineState entryState = getEntryState();
        int parameterNumber = 0;
        // added -2 to account for the return and exception register
//      int local = method.getMaxLocals() - method.getNumberOfParameters() - 1 - 2;

        //can't use just getNumberOfParameters because it does not account for Long/Wide variables which take up 2 registers
        //as the parameter
        int local = method.getMaxLocals() - method.getNumberOfParameterRegisters() - 1 - 2;

//      MyLogger.log(LogLevel.DEBUG, "DexSSABuilder - initializeVariables() - local: " + local);


        //initialize the "this" parameter if it needs to be set.
        //the "this" parameter will be symbol number 1 in a virtual method.
//      if (method.getMaxLocals() - method.getNumberOfParameters() - 2 > 0)
//          entryState.setLocal(local, 1);

//      System.out.println("visiting initalizeVartiables()");
//      if (method.isStatic())
//          System.out.println("Static");
//      if (method.isClinit())
//          System.out.println("Clinit");
//      System.out.println("GetNumberOfParameter: " + method.getNumberOfParameterRegisters());
//      System.out.println("Total Registers: " + (method.getMaxLocals()-2));
//      System.out.println("local: " + local);

//      if (local >= 0) {
//          System.out.println("Max Registers: " + (int)(method.getMaxLocals() - 2));
//          System.out.println("Parameters: " + method.getNumberOfParameters());
//          System.out.println("Setting Entry State, local:"+ local + " with 1");
//          entryState.setLocal(local, 1);
//      }

//      System.out.println("Max Registers: " + (int)(method.getMaxLocals() - 2));
//      System.out.println("Parameters: " + method.getNumberOfParameters());

        entryState.allocateLocals();
        for (int i = 0; i < method.getNumberOfParameters(); i++) {
            local++;
            TypeReference t = method.getParameterType(i);
            if (t != null) {
                int symbol = symbolTable.getParameter(parameterNumber++);
                entryState.setLocal(local, symbol);
                if (t.equals(TypeReference.Double) || t.equals(TypeReference.Long)) {
                    local++;
                    entryState.setLocal(local,symbol);
                }
            }
        }
//      MyLogger.log(LogLevel.DEBUG, "DexSSABuilder - initializeVariables() - local: " + local);
        // This useless value ensures that the state cannot be empty, even
        // for a static method with no arguments in blocks with an empty stack
        // and no locals being used. This ensures that propagation of the
        // state thru the CFGSystem will always show changes the first time
        // it reaches a block, and thus no piece of the CFG will be skipped.
        //
        // (note that this bizarre state really happened, in java_cup)
        //
        // SJF: I don't understand how this is supposed to work. It
        // causes a bug right now in normal cases, so I'm commenting it out
        // for now. If there's a problem, let's add a regression test
        // to catch it.
        //
        // TODO: if there's no stack do we need this?
        //entryState.push(symbolTable.newSymbol());
    }

    /**
     * This class defines the type abstractions for this analysis and the flow function for each instruction in the ShrikeBT IR.
     */
    private class SymbolicPropagator extends BasicRegisterFlowProvider {

        final SSAInstruction[] instructions;

        final DexCFG dexCFG;

        final SSACFG cfg;

        final ClassLoaderReference loader;

        /**
         * creators[i] holds the instruction that defs value number i
         */
        private SSAInstruction[] creators;

//        final SSA2LocalMap localMap;

        final SSAPiNodePolicy piNodePolicy;

        public SymbolicPropagator(DexCFG dexCFG, SSAInstruction[] instructions, SSACFG cfg,
                SSAPiNodePolicy piNodePolicy) {
            super(dexCFG);
            this.piNodePolicy = piNodePolicy;
            this.cfg = cfg;
            this.creators = new SSAInstruction[0];
            this.dexCFG = dexCFG;
            this.instructions = instructions;
            this.loader = dexCFG.getMethod().getDeclaringClass().getClassLoader().getReference();
//            this.localMap = localMap;
            init(this.new NodeVisitor(cfg), this.new EdgeVisitor());
        }

        @Override
        public boolean needsEdgeFlow() {
            return piNodePolicy != null;
        }

        private void emitInstruction(SSAInstruction s) {
            instructions[getCurrentInstructionIndex()] = s;
            for (int i = 0; i < s.getNumberOfDefs(); i++) {
                if (creators.length < (s.getDef(i) + 1)) {
                    creators = Arrays.copyOf(creators, 2 * s.getDef(i));
                }

                assert s.getDef(i) != -1 : "invalid def " + i + " for " + s;

                creators[s.getDef(i)] = s;
            }
        }

        private SSAInstruction getCurrentInstruction() {
            return instructions[getCurrentInstructionIndex()];
        }

        /**
         * If we've already created the current instruction, return the value number def'ed by the current instruction. Else, create a
         * new symbol.
         */

        private int reuseOrCreateDef() {
            if (getCurrentInstruction() == null || !getCurrentInstruction().hasDef()) {
                return symbolTable.newSymbol();
            } else {
                return getCurrentInstruction().getDef();
            }
        }

        /**
         * If we've already created the current instruction, return the value number representing the exception the instruction may
         * throw. Else, create a new symbol
         */
        private int reuseOrCreateException() {

            if (getCurrentInstruction() != null) {
                assert getCurrentInstruction() instanceof SSAInvokeInstruction;
            }
            if (getCurrentInstruction() == null) {
                return symbolTable.newSymbol();
            } else {
                SSAInvokeInstruction s = (SSAInvokeInstruction) getCurrentInstruction();
                return s.getException();
            }
        }


        /**
         * Update the machine state to account for an instruction
         */
        class NodeVisitor extends BasicRegisterMachineVisitor {
        	private final SSACFG cfg;
        	
			public NodeVisitor(SSACFG cfg) {
				this.cfg = cfg;
			}

			// TODO: make sure all visit functions are overridden

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitArrayLength(ArrayLengthInstruction)
             */
            @Override
            public void visitArrayLength(ArrayLength instruction) {
                int arrayRef = workingState.getLocal(instruction.source);
                int dest = instruction.destination;
                int length = reuseOrCreateDef();
                setLocal(dest, length);

                emitInstruction(insts.ArrayLengthInstruction(getCurrentInstructionIndex(), length, arrayRef));
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitArrayLoad(IArrayLoadInstruction)
             */
            @Override
            public void visitArrayGet(ArrayGet instruction) {
                int index = workingState.getLocal(instruction.offset);
                int arrayRef = workingState.getLocal(instruction.array);
                int dest = instruction.destination;
//              int index = workingState.pop();
//              int arrayRef = workingState.pop();
                int result = reuseOrCreateDef();
                setLocal(dest, result);
//              workingState.push(result);
                TypeReference t = instruction.getType();
//              if (instruction.isAddressOf()) {
//                  emitInstruction(insts.AddressOfInstruction(result, arrayRef, index, t));
//              } else {
                    emitInstruction(insts.ArrayLoadInstruction(getCurrentInstructionIndex(), result, arrayRef, index, t));
//              }
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitArrayStore(IArrayStoreInstruction)
             */
            @Override
            public void visitArrayPut(ArrayPut instruction) {

                int value = workingState.getLocal(instruction.source);
                int index = workingState.getLocal(instruction.offset);
                int arrayRef = workingState.getLocal(instruction.array);
                TypeReference t = instruction.getType();
                //System.out.println(t.getName().toString());
//              int value = workingState.pop();
//              int index = workingState.pop();
//              int arrayRef = workingState.pop();
//              TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getType());
                emitInstruction(insts.ArrayStoreInstruction(getCurrentInstructionIndex(), arrayRef, index, value, t));
            }

            @Override
            public void visitArrayFill(ArrayFill instruction) {
                Iterator<Number> iae = instruction.getTable().getArrayElements().iterator();
                int i = 0;
                while (iae.hasNext())
                {
                    Number ae = iae.next();                    
                    int index = symbolTable.getConstant(i);
                    int arrayRef = workingState.getLocal(instruction.array);
                    TypeReference t = instruction.getType();
//                  System.out.println(t.getName().toString());
                    int value;
//
//                      System.out.println("Index: " + ae.bufferIndex + ", Width: " + ae.elementWidth + ", Value: " +  byte_buffer.getSomethingDependingonType );


                    //okay to call the getConstant(String) for a char?
                    if (t.equals(TypeReference.Char))
                        value = symbolTable.getConstant((char) ae.intValue());
                    else if (t.equals(TypeReference.Byte))
                        value = symbolTable.getConstant(ae.byteValue());
                    else if (t.equals(TypeReference.Short))
                        value = symbolTable.getConstant(ae.shortValue());
                    else if (t.equals(TypeReference.Int))
                        value = symbolTable.getConstant(ae.intValue());
                    else if (t.equals(TypeReference.Long))
                        value = symbolTable.getConstant(ae.longValue());
                    else if (t.equals(TypeReference.Float))
                        value = symbolTable.getConstant(ae.floatValue());
                    else if (t.equals(TypeReference.Double))
                        value = symbolTable.getConstant(ae.doubleValue());
                    else if (t.equals(TypeReference.Boolean))
                        value = symbolTable.getConstant(ae.intValue() != 0);
                    else
                    {
                        
                        value = 0;
                    }
                    emitInstruction(insts.ArrayStoreInstruction(getCurrentInstructionIndex(), arrayRef, index, value, t));

//                  System.out.println("Index: " + t.bufferIndex + ", Value: " +  t.buffer[t.bufferIndex]);

                    i++;
                }

            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitBinaryOp(IBinaryOpInstruction)
             */
            @Override
            public void visitBinaryOperation(BinaryOperation instruction) {
                int val2 = workingState.getLocal(instruction.oper2);
                int val1 = workingState.getLocal(instruction.oper1);
                int dest = instruction.destination;
//              int val2 = workingState.pop();
//              int val1 = workingState.pop();
                int result = reuseOrCreateDef();
                setLocal(dest, result);
//              workingState.push(result);
//              boolean isFloat = instruction.getType().equals(TYPE_double) || instruction.getType().equals(TYPE_float);
                emitInstruction(insts.BinaryOpInstruction(getCurrentInstructionIndex(), instruction.getOperator(), false, instruction.isUnsigned(), result, val1, val2, !instruction.isFloat()));
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitBinaryOp(IBinaryOpInstruction)
             */
            @Override
            public void visitBinaryLiteral(BinaryLiteralOperation instruction) {
                //int val2 = workingState.getLocal(instruction.oper2);

                Literal lit = instruction.oper2;

                int val2;
                if (lit instanceof Literal.IntLiteral)
                    val2 = symbolTable.getConstant(((Literal.IntLiteral)lit).value);
                else if (lit instanceof Literal.LongLiteral)
                    val2 = symbolTable.getConstant(((Literal.LongLiteral)lit).value);
                else if (lit instanceof Literal.DoubleLiteral)
                    val2 = symbolTable.getConstant(((Literal.DoubleLiteral)lit).value);
                else
                    val2 = symbolTable.getConstant(((Literal.FloatLiteral)lit).value);


                int val1 = workingState.getLocal(instruction.oper1);
                int dest = instruction.destination;
//              int val2 = workingState.pop();
//              int val1 = workingState.pop();
                int result = reuseOrCreateDef();
                setLocal(dest, result);
//              workingState.push(result);
//              boolean isFloat = instruction.getType().equals(TYPE_double) || instruction.getType().equals(TYPE_float);
                try {
                    if (instruction.isSub())
                        emitInstruction(insts.BinaryOpInstruction(getCurrentInstructionIndex(), instruction.getOperator(), false, instruction.isUnsigned(), result, val2, val1, !instruction.isFloat()));
                    else
                        emitInstruction(insts.BinaryOpInstruction(getCurrentInstructionIndex(), instruction.getOperator(), false, instruction.isUnsigned(), result, val1, val2, !instruction.isFloat()));
                } catch (AssertionError e) {
                    System.err.println("When visiting Instuction " + instruction);
                    throw e;
                }
            }

			protected void setLocal(int dest, int result) {
				assert result <= symbolTable.getMaxValueNumber();
				workingState.setLocal(dest, result);
			}

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitCheckCast
             */
            @Override
            public void visitCheckCast(CheckCast instruction) {
                int val = workingState.getLocal(instruction.object);
//              int val = workingState.pop();
                // dex does not use this result, but we need it for the SSA CheckCastInstruction
                int result = reuseOrCreateDef();
                workingState.setLocal(instruction.object, result);
//              workingState.push(result);
//              TypeReference t = instruction.getType();
                emitInstruction(insts.CheckCastInstruction(getCurrentInstructionIndex(), result, val, instruction.type, instruction.isPEI()));
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitConditionalBranch(IConditionalBranchInstruction)
             */
            @Override
            public void visitBranch(Branch instruction) {
                if(instruction instanceof Branch.BinaryBranch)
                {
                    Branch.BinaryBranch bbranch = (Branch.BinaryBranch)instruction;
                    int val2 = workingState.getLocal(bbranch.oper2);
                    int val1 = workingState.getLocal(bbranch.oper1);
//                  int val2 = workingState.pop();
//                  int val1 = workingState.pop();

                    TypeReference t = TypeReference.Int;
                    emitInstruction(insts.ConditionalBranchInstruction(getCurrentInstructionIndex(), instruction.getOperator(), t, val1, val2, -1));
                }
                else if(instruction instanceof Branch.UnaryBranch)
                {
                    Branch.UnaryBranch ubranch = (Branch.UnaryBranch)instruction;
                    int val2 = symbolTable.getConstant(0);
                    int val1 = workingState.getLocal(ubranch.oper1);
                    TypeReference t = TypeReference.Int;
                    emitInstruction(insts.ConditionalBranchInstruction(getCurrentInstructionIndex(), instruction.getOperator(), t, val1, val2, -1));
                }
                else
                {
                    throw new IllegalArgumentException("instruction is of an unknown subtype of Branch");
                }
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitConstant(ConstantInstruction)
             */
            @Override
            public void visitConstant(Constant instruction) {
                int dest = instruction.destination;
                int symbol = 0;
                if(instruction instanceof Constant.ClassConstant)
                {
                	Constant.ClassConstant constInst = (Constant.ClassConstant)instruction;
                	
                    // TODO: change to a symbol that represents the given IClass
//                  symbol = symbolTable.getConstant(((Constant.ClassConstant)instruction).value);
                    symbol = reuseOrCreateDef();

                    TypeReference typeRef = constInst.value;
					SSALoadMetadataInstruction s = 
                    		insts.LoadMetadataInstruction(getCurrentInstructionIndex(), symbol, TypeReference.JavaLangClass, typeRef);
					emitInstruction(s);
                }
                else if(instruction instanceof Constant.IntConstant)
                {
                    symbol = symbolTable.getConstant(((Constant.IntConstant)instruction).value);
                }
                else if(instruction instanceof Constant.LongConstant)
                {
                    symbol = symbolTable.getConstant(((Constant.LongConstant)instruction).value);
                }
                else if(instruction instanceof Constant.StringConstant)
                {
                    symbol = symbolTable.getConstant(((Constant.StringConstant)instruction).value);
                }
                else
                {
                    Assertions.UNREACHABLE("unexpected constant instruction " + instruction);
                }
                setLocal(dest, symbol);
//              Language l = cfg.getMethod().getDeclaringClass().getClassLoader().getLanguage();
//              TypeReference type = l.getConstantType(instruction.getValue());
//              int symbol = 0;
//              if (l.isNullType(type)) {
//                  symbol = symbolTable.getNullConstant();
//              } else if (l.isIntType(type)) {
//                  Integer value = (Integer) instruction.getValue();
//                  symbol = symbolTable.getConstant(value.intValue());
//              } else if (l.isLongType(type)) {
//                  Long value = (Long) instruction.getValue();
//                  symbol = symbolTable.getConstant(value.longValue());
//              } else if (l.isFloatType(type)) {
//                  Float value = (Float) instruction.getValue();
//                  symbol = symbolTable.getConstant(value.floatValue());
//              } else if (l.isDoubleType(type)) {
//                  Double value = (Double) instruction.getValue();
//                  symbol = symbolTable.getConstant(value.doubleValue());
//              } else if (l.isStringType(type)) {
//                  String value = (String) instruction.getValue();
//                  symbol = symbolTable.getConstant(value);
//              } else if (l.isMetadataType(type)) {
//                  Object rval = l.getMetadataToken(instruction.getValue());
//                  symbol = reuseOrCreateDef();
//                  emitInstruction(insts.LoadMetadataInstruction(symbol, type, rval));
//              } else {
//                  Assertions.UNREACHABLE("unexpected " + type);
//              }
//              workingState.push(symbol);
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitConversion(IConversionInstruction)
             */
            // TODO: is this just a unary operation?
//          @Override
//          public void visitConversion(IConversionInstruction instruction) {
//
//              int val = workingState.pop();
//              int result = reuseOrCreateDef();
//              workingState.push(result);
//
//              TypeReference fromType = ShrikeUtil.makeTypeReference(loader, instruction.getFromType());
//              TypeReference toType = ShrikeUtil.makeTypeReference(loader, instruction.getToType());
//
//              emitInstruction(insts.ConversionInstruction(result, val, fromType, toType, instruction.throwsExceptionOnOverflow()));
//          }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitGet(IGetInstruction)
             */
            @Override
            public void visitGetField(GetField instruction) {
                int dest = instruction.destination;
                int result = reuseOrCreateDef();
                FieldReference f = FieldReference.findOrCreate(loader, instruction.clazzName, instruction.fieldName, instruction.fieldType);
                // TODO: what is isAddressOf()?
                // shouldn't matter, java doesn't allow isAddressOf()
                if(instruction instanceof GetField.GetInstanceField)
                {
                    int instance = workingState.getLocal(((GetField.GetInstanceField)instruction).instance);
                    emitInstruction(insts.GetInstruction(getCurrentInstructionIndex(), result, instance, f));
                }
                else if(instruction instanceof GetField.GetStaticField)
                {
                    emitInstruction(insts.GetInstruction(getCurrentInstructionIndex(), result, f));
                }
                else
                {
                    throw new IllegalArgumentException("unknown subclass of GetField: "+instruction);
                }
//              if (instruction.isAddressOf()) {
//                  int ref = instruction.isStatic()? -1: workingState.pop();
//                  emitInstruction(insts.AddressOfInstruction(result, ref, f, f.getFieldType()));
//              } else if (instruction.isStatic()) {
//                  emitInstruction(insts.GetInstruction(result, f));
//              } else {
//                  int ref = workingState.pop();
//                  emitInstruction(insts.GetInstruction(result, ref, f));
//              }
                setLocal(dest, result);
//              workingState.push(result);
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitGoto(GotoInstruction)
             */
            @Override
            public void visitGoto(Goto instruction) {
                emitInstruction(insts.GotoInstruction(getCurrentInstructionIndex(), instruction.destination));
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitInstanceof
             */
            @Override
            public void visitInstanceof(InstanceOf instruction) {
                int ref = workingState.getLocal(instruction.source);
                int dest = instruction.destination;
//              int ref = workingState.pop();
                int result = reuseOrCreateDef();
                setLocal(dest, result);
//              workingState.push(result);
//              TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getType());
                emitInstruction(insts.InstanceofInstruction(getCurrentInstructionIndex(), result, ref, instruction.type));
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitInvoke(IInvokeInstruction)
             */
            @Override
            public void visitInvoke(Invoke instruction) {
                // TODO: can other methods do indirect reads from a dex method?
                //              doIndirectReads(bytecodeIndirections.indirectlyReadLocals(getCurrentInstructionIndex()));
//              int n = instruction.getPoppedCount();
//              int n = instruction.args.length;
//              int[] params = new int[n];
//              for (int i = n - 1; i >= 0; i--) {
//                  params[i] = workingState.pop();
//              }


                Language lang = dexCFG.getMethod().getDeclaringClass().getClassLoader().getLanguage();
                // TODO: check that the signature needed by findOrCreate can use the descriptor
                MethodReference m = MethodReference.findOrCreate(lang, loader, instruction.clazzName, instruction.methodName, instruction.descriptor);


                //((DexIClass)dexCFG.getDexMethod().getDeclaringClass()).getSuperclass().get
//              MethodReference m = ((DexIClass)dexCFG.getDexMethod().getDeclaringClass()).loader.lookupClass(TypeName.findOrCreate(instruction.clazzName)).
//              this.myClass.loader.lookupClass(TypeName.findOrCreate(cname)),
//              ((Instruction21c)inst).getRegisterA()));


                
                IInvokeInstruction.IDispatch code = instruction.getInvocationCode();
                CallSiteReference site = CallSiteReference.make(getCurrentProgramCounter(), m, code);
                int exc = reuseOrCreateException();
                setLocal(dexCFG.getDexMethod().getExceptionReg(), exc);


                int n = instruction.args.length;
                for (int i = 0; i < m.getNumberOfParameters(); i++)
                    if (m.getParameterType(i) == TypeReference.Double || m.getParameterType(i) == TypeReference.Long)
                        n--;
                int[] params = new int[n];

                int arg_i = 0;
                //there is no "this" parameter when calling this invoke call
                if (n == m.getNumberOfParameters()) {
                    for (int i = 0; i < n; i++) {
                        params[i] = workingState.getLocal(instruction.args[arg_i]);
                        
                        if (m.getParameterType(i) == TypeReference.Double || m.getParameterType(i) == TypeReference.Long)
                            arg_i++;
                        arg_i++;
                    }
                }
                //there is a "this" parameter in this invoke call
                else if (n == m.getNumberOfParameters()+1) {
                    params[0] = workingState.getLocal(instruction.args[0]);
                    
                    arg_i = 1;
                    for (int i = 0; i < (n-1); i++) {
                        params[i+1] = workingState.getLocal(instruction.args[arg_i]);
                        if (m.getParameterType(i) == TypeReference.Double || m.getParameterType(i) == TypeReference.Long)
                            arg_i++;
                        arg_i++;
                    }
                }
                //this should not happen
                else
                    throw new UnsupportedOperationException("visitInvoke DexSSABuilder, error");



                if(m.getReturnType().equals(TypeReference.Void))
                {
                    SSAInstruction inst = insts.InvokeInstruction(getCurrentInstructionIndex(), params, exc, site, null);
                    //System.out.println("Emitting(1) InvokeInstruction: "+inst);
                    emitInstruction(inst);
                } else {
                    int result = reuseOrCreateDef();
                    assert result != -1;
                    // TODO: check that this return register is correct
                    //I think it be registerCount() or registerCount()+1
//                  int dest = dexCFG.getDexMethod().regBank.getReturnReg().regID;
                    int dest = dexCFG.getDexMethod().getReturnReg();

                    setLocal(dest, result);
                    SSAInstruction inst = insts.InvokeInstruction(getCurrentInstructionIndex(), result, params, exc, site, null);
                    //System.out.println("Emitting(2) InvokeInstruction: "+inst);
                    emitInstruction(inst);
                }
//              if (instruction.getPushedWordSize() > 0) {
//                  int result = reuseOrCreateDef();
//                  workingState.push(result);
//                  emitInstruction(insts.InvokeInstruction(result, params, exc, site));
//              } else {
//                  emitInstruction(insts.InvokeInstruction(params, exc, site));
//              }
                // TODO: can other methods do indirect writes to a dex method?
//              doIndirectWrites(bytecodeIndirections.indirectlyWrittenLocals(getCurrentInstructionIndex()), -1);
            }

            // Dex doesn't have local load or store
//          @Override
//          public void visitLocalLoad(ILoadInstruction instruction) {
//              if (instruction.isAddressOf()) {
//                  int result = reuseOrCreateDef();
//
//                  int t = workingState.getLocal(instruction.getVarIndex());
//                  if (t == -1) {
//                      doIndirectWrites(new int[]{instruction.getVarIndex()}, -1);
//                      t = workingState.getLocal(instruction.getVarIndex());
//                  }
//
//                  TypeReference type = ShrikeUtil.makeTypeReference(loader, instruction.getType());
//                  emitInstruction(insts.AddressOfInstruction(result, t, type));
//                  workingState.push(result);
//              } else {
//                  super.visitLocalLoad(instruction);
//              }
//          }
//
//          /*
//           * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitLocalStore(com.ibm.wala.shrikeBT.StoreInstruction)
//           */
//          @Override
//          public void visitLocalStore(IStoreInstruction instruction) {
//              if (localMap != null) {
//                  localMap.startRange(getCurrentInstructionIndex(), instruction.getVarIndex(), workingState.peek());
//              }
//              super.visitLocalStore(instruction);
//          }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitMonitor(MonitorInstruction)
             */
            @Override
            public void visitMonitor(Monitor instruction) {
                int ref = workingState.getLocal(instruction.object);
//              int ref = workingState.pop();
                emitInstruction(insts.MonitorInstruction(getCurrentInstructionIndex(), ref, instruction.enter));
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitNew(NewInstruction)
             */
            @Override
            public void visitNew(New instruction) {
                int dest = instruction.destination;
                int result = reuseOrCreateDef();
//              TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getType());
//              NewSiteReference ref = NewSiteReference.make(getCurrentProgramCounter(), t);

//              if (t.isArrayType()) {
//                  int[] sizes = new int[instruction.getArrayBoundsCount()];
//                  for (int i = 0; i < instruction.getArrayBoundsCount(); i++) {
//                      sizes[instruction.getArrayBoundsCount() - 1 - i] = workingState.pop();
//                  }
//                  emitInstruction(insts.NewInstruction(result, ref, sizes));
//              } else {
                    emitInstruction(insts.NewInstruction(getCurrentInstructionIndex(), result, instruction.newSiteRef));
//                  popN(instruction);
//              }
                setLocal(dest, result);
//              workingState.push(result);
            }

            @Override
            public void visitNewArray(NewArray instruction)
            {
                int dest = instruction.destination;
                int result = reuseOrCreateDef();
                int[] sizes = new int[instruction.sizes.length];
                for(int i = 0; i < instruction.sizes.length; i++)
                {
                    sizes[i] = workingState.getLocal(instruction.sizes[i]);
                }
                emitInstruction(insts.NewInstruction(getCurrentInstructionIndex(), result, instruction.newSiteRef, sizes));
                setLocal(dest, result);
            }

            @Override
            public void visitNewArrayFilled(NewArrayFilled instruction)
            {
                int dest = instruction.destination;
                int result = reuseOrCreateDef();
                int[] sizes = new int[instruction.sizes.length];
                for(int i = 0; i < instruction.sizes.length; i++)
                {
                    sizes[i] = symbolTable.getConstant(instruction.sizes[i]);
                }
                emitInstruction(insts.NewInstruction(getCurrentInstructionIndex(), result, instruction.newSiteRef, sizes));
                setLocal(dest, result);

                /*
                 * we need to emit these instructions somehow, but for now this clobbers the emitInstruction mechanism
                 * 
                for (int i = 0; i < instruction.args.length; i++)
                {
                    int value = workingState.getLocal(instruction.args[i]);
                    int index = symbolTable.getConstant(i);
                    int arrayRef = result;
                    TypeReference t = instruction.myType;
                    emitInstruction(insts.ArrayStoreInstruction(getCurrentInstructionIndex(), arrayRef, index, value, t));
                }
                */
            }


            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitGet(IGetInstruction)
             */
            @Override
            public void visitPutField(PutField instruction) {
                int value = workingState.getLocal(instruction.source);
                FieldReference f = FieldReference.findOrCreate(loader, instruction.clazzName, instruction.fieldName, instruction.fieldType);
//              int value = workingState.pop();
                if (instruction instanceof PutField.PutStaticField) {
//              if (instruction.isStatic()) {
                    emitInstruction(insts.PutInstruction(getCurrentInstructionIndex(), value, f));
                } else if (instruction instanceof PutField.PutInstanceField) {
//              } else {
                    int ref = workingState.getLocal(((PutField.PutInstanceField)instruction).instance);
//                  int ref = workingState.pop();
                    emitInstruction(insts.PutInstruction(getCurrentInstructionIndex(), ref, value, f));
                } else {
                    throw new IllegalArgumentException("Unknown subclass of PutField: "+instruction);
                }
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitReturn(ReturnInstruction)
             */
            @Override
            public void visitReturn(Return instruction) {
                if(instruction instanceof Return.ReturnDouble)
                {
                    // TODO: figure out how to return a double
                    Return.ReturnDouble retD = (Return.ReturnDouble)instruction;
                    int result = workingState.getLocal(retD.source1);
//                  boolean isPrimitive = symbolTable.isLongConstant(result) || symbolTable.isDoubleConstant(result);
                    boolean isPrimitive = true;
                    emitInstruction(insts.ReturnInstruction(getCurrentInstructionIndex(), result, isPrimitive));
//                  throw new UnsupportedOperationException("can't yet support returning doubles");
                } else if (instruction instanceof Return.ReturnSingle)
                {
                    Return.ReturnSingle retS = (Return.ReturnSingle)instruction;
                    int result = workingState.getLocal(retS.source);
                    // TODO: figure out if this is primitive or not
                    //boolean isPrimitive = false;
                    boolean isPrimitive = retS.isPrimitive();
                    emitInstruction(insts.ReturnInstruction(getCurrentInstructionIndex(), result, isPrimitive));
                } else if (instruction instanceof Return.ReturnVoid)
                {
                    emitInstruction(insts.ReturnInstruction(getCurrentInstructionIndex()));
                }
//              if (instruction.getPoppedCount() == 1) {
//                  int result = workingState.pop();
//                  TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getType());
//                  emitInstruction(insts.ReturnInstruction(result, t.isPrimitiveType()));
//              } else {
//                  emitInstruction(insts.ReturnInstruction());
//              }
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitShift(IShiftInstruction)
             */
            // TODO: this is just a binary operation
//          @Override
//          public void visitShift(IShiftInstruction instruction) {
//              int val2 = workingState.pop();
//              int val1 = workingState.pop();
//              int result = reuseOrCreateDef();
//              workingState.push(result);
//              emitInstruction(insts.BinaryOpInstruction(instruction.getOperator(), false, instruction.isUnsigned(), result, val1, val2,
//                      true));
//          }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitSwitch(SwitchInstruction)
             */
            @Override
            public void visitSwitch(Switch instruction) {
                int val = workingState.getLocal(instruction.regA);
//              int val = workingState.pop();
                // TODO: figure out if the switch offset should refer to a pc offset or an instruction id or what
                emitInstruction(insts.SwitchInstruction(getCurrentInstructionIndex(), val, instruction.getDefaultLabel(), instruction.getCasesAndLabels()));
            }

            private Dominators<ISSABasicBlock> dom = null;

            @SuppressWarnings("unused")
			private int findRethrowException() {
                int index = getCurrentInstructionIndex();
                SSACFG.BasicBlock bb = cfg.getBlockForInstruction(index);
                if (bb.isCatchBlock()) {
                    SSACFG.ExceptionHandlerBasicBlock newBB = (SSACFG.ExceptionHandlerBasicBlock) bb;
                    SSAGetCaughtExceptionInstruction s = newBB.getCatchInstruction();
                    return s.getDef();
                } else {
                    // TODO: should we really use dominators here? maybe it would be cleaner to propagate
                    // the notion of 'current exception to rethrow' using the abstract interpreter.
                    if (dom == null) {
                        dom = Dominators.make(cfg, cfg.entry());
                    }

                    ISSABasicBlock x = bb;
                    while (x != null) {
                        if (x.isCatchBlock()) {
                            SSACFG.ExceptionHandlerBasicBlock newBB = (SSACFG.ExceptionHandlerBasicBlock) x;
                            SSAGetCaughtExceptionInstruction s = newBB.getCatchInstruction();
                            return s.getDef();
                        } else {
                            x = dom.getIdom(x);
                        }
                    }

                    // assert false;
                    return -1;
                }
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitThrow(ThrowInstruction)
             */
            @Override
            public void visitThrow(Throw instruction) {
                int throwable = workingState.getLocal(instruction.throwable);
                assert symbolTable.getMaxValueNumber() >= throwable;
                emitInstruction(insts.ThrowInstruction(getCurrentInstructionIndex(), throwable));
//              if (instruction.isRethrow()) {
//                  workingState.clearStack();
//                  emitInstruction(insts.ThrowInstruction(findRethrowException()));
//              } else {
//                  int exception = workingState.pop();
//                  workingState.clearStack();
//                  workingState.push(exception);
//                  emitInstruction(insts.ThrowInstruction(exception));
//              }
            }

            /**
             * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitUnaryOp(IUnaryOpInstruction)
             */
            @Override
            public void visitUnaryOperation(UnaryOperation instruction) {
            	
            	if (instruction.op == UnaryOperation.OpID.MOVE_EXCEPTION) {
            		
            		int idx = getCurrentInstructionIndex();
            		int bbidx = dexCFG.getBlockForInstruction(idx).getNumber();
            		ExceptionHandlerBasicBlock newBB = (ExceptionHandlerBasicBlock) cfg.getBasicBlock(bbidx);

            		SSAGetCaughtExceptionInstruction s = newBB.getCatchInstruction();
                    int exceptionValue;
                     if (s == null) {
                    	exceptionValue = symbolTable.newSymbol();
                        s = insts.GetCaughtExceptionInstruction(newBB.getLastInstructionIndex(), bbidx, exceptionValue);
                        newBB.setCatchInstruction(s);
                    } else {
                        exceptionValue = s.getException();
                    }

                 	setLocal(instruction.destination, exceptionValue);
                	return;
                }
            	
                //System.out.println("Instruction: " + getCurrentInstructionIndex());
                int val = workingState.getLocal(instruction.source);
//              int val = workingState.pop();
//              workingState.push(result);
                if(instruction.isConversion())
                {
                    TypeReference fromType, toType;
                    boolean overflows = false;
                    // TODO: figure out if any of these can overflow
                    switch(instruction.op)
                    {
                    case DOUBLETOLONG:
                        fromType = TypeReference.Double;
                        toType = TypeReference.Long;
                        break;
                    case DOUBLETOFLOAT:
                        fromType = TypeReference.Double;
                        toType = TypeReference.Float;
                        break;
                    case INTTOBYTE:
                        fromType = TypeReference.Int;
                        toType = TypeReference.Byte;
                        break;
                    case INTTOCHAR:
                        fromType = TypeReference.Int;
                        toType = TypeReference.Char;
                        break;
                    case INTTOSHORT:
                        fromType = TypeReference.Int;
                        toType = TypeReference.Short;
                        break;
                    case DOUBLETOINT:
                        fromType = TypeReference.Double;
                        toType = TypeReference.Int;
                        break;
                    case FLOATTODOUBLE:
                        fromType = TypeReference.Float;
                        toType = TypeReference.Double;
                        break;
                    case FLOATTOLONG:
                        fromType = TypeReference.Float;
                        toType = TypeReference.Long;
                        break;
                    case FLOATTOINT:
                        fromType = TypeReference.Float;
                        toType = TypeReference.Int;
                        break;
                    case LONGTODOUBLE:
                        fromType = TypeReference.Long;
                        toType = TypeReference.Double;
                        break;
                    case LONGTOFLOAT:
                        fromType = TypeReference.Long;
                        toType = TypeReference.Float;
                        break;
                    case LONGTOINT:
                        fromType = TypeReference.Long;
                        toType = TypeReference.Int;
                        break;
                    case INTTODOUBLE:
                        fromType = TypeReference.Int;
                        toType = TypeReference.Double;
                        break;
                    case INTTOFLOAT:
                        fromType = TypeReference.Int;
                        toType = TypeReference.Float;
                        break;
                    case INTTOLONG:
                        fromType = TypeReference.Int;
                        toType = TypeReference.Long;
                        break;
                    default:
                        throw new IllegalArgumentException("unknown conversion type "+instruction.op+" in unary instruction: "+instruction);
                    }
                    int dest = instruction.destination;
                    int result = reuseOrCreateDef();
                    setLocal(dest, result);
                    emitInstruction(insts.ConversionInstruction(getCurrentInstructionIndex(), result, val, fromType, toType, overflows));
                }        
                else
                {
                	if (instruction.op == UnaryOperation.OpID.MOVE) {
                        setLocal(instruction.destination, workingState.getLocal(instruction.source));
                    }
                    else if (instruction.op == UnaryOperation.OpID.MOVE_WIDE) {
                        setLocal(instruction.destination, workingState.getLocal(instruction.source));
                        if (instruction.source == dexCFG.getDexMethod().getReturnReg())
                            setLocal(instruction.destination+1, workingState.getLocal(instruction.source));
                        else
                            setLocal(instruction.destination+1, workingState.getLocal(instruction.source+1));
                    } else {
                        int dest = instruction.destination;
                        int result = reuseOrCreateDef();
                        setLocal(dest, result);
                    	emitInstruction(insts.UnaryOpInstruction(getCurrentInstructionIndex(), instruction.getOperator(), result, val));
                    }
                }
            }

            // dex doesn't have indirect reads
//          private void doIndirectReads(int[] locals) {
//              for(int i = 0; i < locals.length; i++) {
//                  ssaIndirections.setUse(getCurrentInstructionIndex(), new ShrikeLocalName(locals[i]), workingState.getLocal(locals[i]));
//              }
//          }

            // dex doesn't have load or store, even with indirection
//          @Override
//          public void visitLoadIndirect(ILoadIndirectInstruction instruction) {
//              int addressVal = workingState.pop();
//              int result = reuseOrCreateDef();
//              doIndirectReads(bytecodeIndirections.indirectlyReadLocals(getCurrentInstructionIndex()));
//              TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getPushedType(null));
//              emitInstruction(insts.LoadIndirectInstruction(result, t, addressVal));
//              workingState.push(result);
//          }
//
//          private void doIndirectWrites(int[] locals, int rval) {
//              for(int i = 0; i < locals.length; i++) {
//                  ShrikeLocalName name = new ShrikeLocalName(locals[i]);
//                  int idx = getCurrentInstructionIndex();
//                  if (ssaIndirections.getDef(idx, name) == -1) {
//                      ssaIndirections.setDef(idx, name, rval==-1? symbolTable.newSymbol(): rval);
//                  }
//                  workingState.setLocal(locals[i], ssaIndirections.getDef(idx, name));
//              }
//          }
//
//          @Override
//          public void visitStoreIndirect(IStoreIndirectInstruction instruction) {
//              int val = workingState.pop();
//              int addressVal = workingState.pop();
//              doIndirectWrites(bytecodeIndirections.indirectlyWrittenLocals(getCurrentInstructionIndex()), val);
//              TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getType());
//              emitInstruction(insts.StoreIndirectInstruction(addressVal, val, t));
//          }

        }

        private void reuseOrCreatePi(SSAInstruction piCause, int ref) {
            int n = getCurrentInstructionIndex();
            SSACFG.BasicBlock bb = cfg.getBlockForInstruction(n);

            BasicBlock path = getCurrentSuccessor();
            int outNum = dexCFG.getNumber(path);

            SSAPiInstruction pi = bb.getPiForRefAndPath(ref, path);
            if (pi == null) {
                pi = insts.PiInstruction(getCurrentInstructionIndex(), symbolTable.newSymbol(), ref, bb.getNumber(), outNum, piCause);
                bb.addPiForRefAndPath(ref, path, pi);
            }

            workingState.replaceValue(ref, pi.getDef());
        }

        // private void maybeInsertPi(int val) {
        // if ((addPiForFieldSelect) && (creators.length > val) && (creators[val] instanceof SSAGetInstruction)
        // && !((SSAGetInstruction) creators[val]).isStatic()) {
        // reuseOrCreatePi(creators[val], val);
        // } else if ((addPiForDispatchSelect)
        // && (creators.length > val)
        // && (creators[val] instanceof SSAInvokeInstruction)
        // && (((SSAInvokeInstruction) creators[val]).getInvocationCode() == IInvokeInstruction.Dispatch.VIRTUAL ||
        // ((SSAInvokeInstruction) creators[val])
        // .getInvocationCode() == IInvokeInstruction.Dispatch.INTERFACE)) {
        // reuseOrCreatePi(creators[val], val);
        // }
        // }

        private void maybeInsertPi(SSAAbstractInvokeInstruction call) {
            if (piNodePolicy != null) {
                Pair<Integer, SSAInstruction> pi = piNodePolicy.getPi(call, symbolTable);
                if (pi != null) {
                    reuseOrCreatePi(pi.snd, pi.fst);
                }
            }
        }

        private void maybeInsertPi(SSAConditionalBranchInstruction cond) {
            if (piNodePolicy != null) {
                Pair<Integer, SSAInstruction> pi = piNodePolicy.getPi(cond, getDef(cond.getUse(0)), getDef(cond.getUse(1)), symbolTable);
                if (pi != null) {
                    reuseOrCreatePi(pi.snd, pi.fst);
                }
            }
        }

        private SSAInstruction getDef(int vn) {
            if (vn < creators.length) {
                return creators[vn];
            } else {
                return null;
            }
        }

        class EdgeVisitor extends Visitor {

            @Override
            public void visitInvoke(Invoke instruction) {
                maybeInsertPi((SSAAbstractInvokeInstruction) getCurrentInstruction());
            }

            @Override
            public void visitBranch(Branch instruction) {
                maybeInsertPi((SSAConditionalBranchInstruction) getCurrentInstruction());
            }
        }

        @Override
        public Instruction[] getInstructions() {
            return dexCFG.getDexMethod().getDexInstructions();
        }
    }

    /**
     * Build the IR
     */
    public void build() {
        try {
           solve();
            if (localMap != null) {
                localMap.finishLocalMap(this);
            }
        } catch (AssertionError e) {
            System.err.println("When handling method " + method.getReference());
            e.printStackTrace();
            //throw e;
        }
    }

    public SSA2LocalMap getLocalMap() {
        return localMap;
    }

    public ShrikeIndirectionData getIndirectionData() {
        return shrikeIndirections;
    }

    /**
     * A logical mapping from &lt;pc, valueNumber&gt; -&gt; local number Note: make sure this class remains static: this persists as part of
     * the IR!!
     */
    private static class SSA2LocalMap implements com.ibm.wala.ssa.IR.SSA2LocalMap {

        private final DexCFG dexCFG;

        /**
         * Mapping Integer -&gt; IntPair where p maps to (vn,L) iff we've started a range at pc p where value number vn corresponds to
         * local L
         */
        private final IntPair[] localStoreMap;

        /**
         * For each basic block i and local j, block2LocalState[i][j] gives the contents of local j at the start of block i
         */
        private final int[][] block2LocalState;

        /**
         * maximum number of locals used at any program point
         */
        private final int maxLocals;

        /**
         * @param nInstructions number of instructions in the bytecode for this method
         * @param nBlocks number of basic blocks in the CFG
         */
        SSA2LocalMap(DexCFG dexCfg, int nInstructions, int nBlocks, int maxLocals) {
            dexCFG = dexCfg;
            localStoreMap = new IntPair[nInstructions];
            block2LocalState = new int[nBlocks][];
            this.maxLocals = maxLocals;
        }

        /**
         * Record the beginning of a new range, starting at the given program counter, in which a particular value number corresponds to
         * a particular local number
         */
        @SuppressWarnings("unused")
		void startRange(int pc, int localNumber, int valueNumber) {
            int max = ((DexIMethod)dexCFG.getMethod()).getMaxLocals();
            if (localNumber >= max) {
                assert false : "invalid local " + localNumber + '>' + max;
            }

            localStoreMap[pc] = new IntPair(valueNumber, localNumber);
        }

        /**
         * Finish populating the map of local variable information
         */
        private void finishLocalMap(DexSSABuilder builder) {
            for (BasicBlock bb : dexCFG) {
                MachineState S = builder.getIn(bb);
                int number = bb.getNumber();
                block2LocalState[number] = S.getLocals();
            }
        }

        /**
         * @param index - index into IR instruction array
         * @param vn - value number
         */
        @Override
        public String[] getLocalNames(int index, int vn) {
            try {
                if (!dexCFG.getMethod().hasLocalVariableTable()) {
                    return null;
                } else {
                    int[] localNumbers = findLocalsForValueNumber(index, vn);
                    if (localNumbers == null) {
                        return null;
                    } else {
                        DexIMethod m = dexCFG.getDexMethod();
                        String[] result = new String[localNumbers.length];
                        for (int i = 0; i < localNumbers.length; i++) {
                            result[i] = m.getLocalVariableName(m.getBytecodeIndex(index), localNumbers[i]);
                        }
                        return result;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                Assertions.UNREACHABLE();
                return null;
            }
        }

        /**
         * @param pc a program counter (index into ShrikeBT instruction array)
         * @param vn a value number
         * @return if we know that immediately after the given program counter, v_vn corresponds to some set of locals, then return an
         *         array of the local numbers. else return null.
         */
        private int[] findLocalsForValueNumber(int pc, int vn) {

            IBasicBlock<Instruction> bb = dexCFG.getBlockForInstruction(pc);
            int firstInstruction = bb.getFirstInstructionIndex();
            // walk forward from the first instruction to reconstruct the
            // state of the locals at this pc
            int[] locals = block2LocalState[bb.getNumber()];
            if (locals == null) {
                locals = allocateNewLocalsArray();
            }
            for (int i = firstInstruction; i <= pc; i++) {
                if (localStoreMap[i] != null) {
                    IntPair p = localStoreMap[i];
                    locals[p.getY()] = p.getX();
                }
            }
            return extractIndices(locals, vn);
        }

        public int[] allocateNewLocalsArray() {
            int[] result = new int[maxLocals];
            for (int i = 0; i < maxLocals; i++) {
                result[i] = OPTIMISTIC ? TOP : BOTTOM;
            }
            return result;
        }

        /**
         * @return the indices i s.t. x[i] == y, or null if none found.
         */
        private static int[] extractIndices(int[] x, int y) {
            int count = 0;
            for (int element : x) {
                if (element == y) {
                    count++;
                }
            }
            if (count == 0) {
                return null;
            } else {
                int[] result = new int[count];
                int j = 0;
                for (int i = 0; i < x.length; i++) {
                    if (x[i] == y) {
                        result[j++] = i;
                    }
                }
                return result;
            }
        }
    }
}
