All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xmlvm.refcount.ReferenceCounting Maven / Gradle / Ivy

There is a newer version: 0.96-beta4
Show newest version
/* Copyright (c) 2002-2011 by XMLVM.org
 *
 * Project Info:  http://www.xmlvm.org
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 */

package org.xmlvm.refcount;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.jdom.Attribute;
import org.jdom.DataConversionException;
import org.jdom.Element;
import org.jdom.Namespace;
import org.xmlvm.refcount.optimizations.DeferredNullingOptimization;
import org.xmlvm.refcount.optimizations.RefCountOptimization;
import org.xmlvm.refcount.optimizations.RegisterSizeAndNullingOptimization;

/**
 * Overview:
 * 
 * This class uses reference counting to simulate the effects of a JVM garbage
 * collector. It operates on the output of the DEX XMLVM process which is a XML
 * based representation of a compiled java class that has been transformed from
 * a stack machine to a register machine. This class manages object lifespan
 * using a reference counting approach. For example, when an object is created
 * it is given a reference count of 1. During the lifespan of the object the
 * reference count is incremented as other objects store away pointers to the
 * object in question. Eventually, the reference count is set to zero and the
 * memory for the object is released.
 * 
 * This class is interesting because manages these reference counts without any
 * client programmer intervention to simulate the effects of a garbage. This is
 * done by following a few simple rules: 1) Each pointer to an object always has
 * a 'retain' associated with it. For example, if a register variable points
 * toward an object then the following pattern would occur: Register r1 =
 * object1; [r1 retain];
 * 
 * When the pointer's end of life occurred, we would release the object
 * reference [r1 release]; r1 = null;
 * 
 * The same concept holds for other classes of pointer including, arrays, static
 * references to objects, as well as instance references. In general the
 * following invariant always holds true:
 * 
 * If there is a pointer to an object, than that pointer has associated with it
 * an increment for the objects reference count. If that pointer is overwritten
 * for any reason, that reference is released.
 * 
 * Based on this invariant, we know that when we can no longer point to an
 * object all of its reference count increments will be gone and hence it will
 * be freed by the runtime.
 * 
 * Usage: For a  represented as a jdom.Element, call Process.
 */
public class ReferenceCounting
{
	Namespace dex= InstructionProcessor.dex;
	Namespace vm= InstructionProcessor.vm;
	String tmpRegNameSuffix= "tmp";

	/**
	 * The entry point to this class. This function takes a method element and
	 * processes it, adding instructions to release and retain objects as
	 * needed. For the command set that it adds, see the InstructionProcessor
	 * class.
	 */
	@SuppressWarnings("unchecked")
	public void process(Element method) throws DataConversionException, ReferenceCountingException
	{

		Attribute isAbstract= method.getAttribute("isAbstract");
		Attribute isNative= method.getAttribute("isNative");
		// abstract and native methods do not require processing
		if (isAbstract != null && isAbstract.getBooleanValue())
		{
			return;
		}

		if (isNative != null && isNative.getBooleanValue())
		{
			return;
		}

		Element codeElement= method.getChild("code", dex);

		int numReg= codeElement.getAttribute("register-size").getIntValue();
		processRecStart(numReg, (List) codeElement.getChildren(), codeElement);
	}

	/**
	 * Set the expected frees that we must do before any optimizations have
	 * removed them.
	 */
	private void setWillFree(Map beenTo) throws ReferenceCountingException, DataConversionException
	{
		{
			for (Map.Entry e : beenTo.entrySet())
			{
				RegisterSet objectRegs= e.getValue().getObjectRegs();

				if (!e.getValue().getConflict().isEmpty())
				{
					throw new ReferenceCountingException("Ambigious register contents possible: Conflict: " + e.getValue().getConflict());
				}

				InstructionUseInfo useInfo= e.getValue().useInfo;
				RegisterSet toFree;
				if (e.getKey().getName().startsWith("return"))
				{
					// we want to free everything except what this instruction
					// uses.
					toFree= objectRegs.andNot(useInfo.usedReg());
				}
				else
				{
					// we free any register reference that is overwritten by
					// this
					// instruction
					toFree= objectRegs.and(useInfo.allWrites());
				}
				useInfo.willFree= toFree;
				useInfo.willNull= toFree.clone();

			}
		}
	}

	/**
	 * This is the last step in the release/retain markup process. It processes
	 * all of the DEX instructions that we have traversed in a method, looking
	 * at how they have been marked up. Based on how they have been marked up it
	 * adds required release/retains or other commands to the body of the method
	 * being processed.
	 * 
	 * Returns whether this method needs to have a temp register defined.
	 */
	private boolean processReleaseRetain(Map beenTo) throws ReferenceCountingException, DataConversionException
	{
		boolean needsTmpReg= false;

		for (Map.Entry e : beenTo.entrySet())
		{

			if (!e.getValue().getConflict().isEmpty())
			{
				throw new ReferenceCountingException("Ambigious register contents possible: Conflict: " + e.getValue().getConflict());
			}

			InstructionUseInfo useInfo= e.getValue().useInfo;

			// if we are writing into an object, we may need to free.
			// All objects in registers are held with a reference, so we will
			// need to release.
			List toAddBefore= new ArrayList();
			List toAddAfter= new ArrayList();
			// Release last -- because other wise we can get into odd situations
			// where we don't to a required retain before the release.
			List toReleaseLast= new ArrayList();
			RegisterSet toFree;
			toFree= useInfo.willFree;
			// for the registers we want to free
			for (int oneReg : toFree)
			{
				// If we use the object in the instruction as an argument and
				// overwrite it, we must be careful to preserve it until after
				// the call is done.
				// Example of true case
				// tmp = f1;
				// f1 = func(f1);
				// [release tmp];
				// Example of false case:
				// f1 = func(f1);
				// [release f1]
				if (!useInfo.usesAsObj().and(useInfo.allWrites()).isEmpty())
				{
					if (useInfo.freeTmpAfter)
					{
						throw new ReferenceCountingException("Conflict, tmp register used twice.");
					}

					Element tmpR= new Element(InstructionProcessor.cmd_tmp_equals_r, vm);
					tmpR.setAttribute("reg", oneReg + "");
					toAddBefore.add(tmpR);
					needsTmpReg= true;

					Element releaseTmp= new Element(InstructionProcessor.cmd_release, vm);
					releaseTmp.setAttribute("reg", tmpRegNameSuffix);
					toReleaseLast.add(releaseTmp);

					Element nullTmp= new Element(InstructionProcessor.cmd_set_null, vm);
					nullTmp.setAttribute("num", tmpRegNameSuffix);
					toReleaseLast.add(nullTmp);

				}
				else
				{
					// No need to use tmp
					Element release= new Element(InstructionProcessor.cmd_release, vm);
					release.setAttribute("reg", oneReg + "");
					toAddBefore.add(release);

					if (useInfo.willNull.has(oneReg))
					{
						Element nullTmp= new Element(InstructionProcessor.cmd_set_null, vm);
						nullTmp.setAttribute("num", oneReg + "");
						toAddBefore.add(nullTmp);

					}
				}
			}

			if (useInfo.putRelease != null)
			{
				if (!useInfo.usesAsObj().and(useInfo.allWrites()).isEmpty())
				{
					needsTmpReg= true;
					throw new ReferenceCountingException("We do not handle the case where a release is " + "made in a x = foo(x) situation because it " + " hasn't showed up so far");
				}
				else
				{
					toAddBefore.add(useInfo.putRelease);
				}
			}

			// Add any necessary retains.
			for (int oneReg : useInfo.requiresRetain)
			{
				Element retain= new Element(InstructionProcessor.cmd_retain, vm);
				retain.setAttribute("reg", oneReg + "");
				toAddAfter.add(retain);
			}

			// This handles the case where xmlvm2objc.xsl has set the temp reg
			// to a value because a function call was made, but the result was
			// not used by the program.
			if (useInfo.freeTmpAfter)
			{

				Element releaseTmp= new Element(InstructionProcessor.cmd_release, vm);
				releaseTmp.setAttribute("reg", tmpRegNameSuffix);
				toAddAfter.add(releaseTmp);
				needsTmpReg= true;

			}
			toAddAfter.addAll(toReleaseLast);

			// At this point toAddBefore and toAddAfter have been filled with
			// whatever instructions we need to add before and after this
			// specific element. The helper function adds them.
			addBeforeAndAfter(e.getKey(), toAddBefore, toAddAfter);

		}
		return needsTmpReg;
	}

	/**
	 * This is here because the jdom XML API is dumb enough that it cannot
	 * easily find the element before element X, or the element after element X.
	 * 
	 * This function adds some elements before and after a particular element.
	 * 
	 * TODO: if we believe prevElement map and nextElement map are correct, then
	 * we can use them instead to make this run faster.
	 */
	@SuppressWarnings("unchecked")
	void addBeforeAndAfter(Element toAddTo, List toAddBefore, List toAddAfter) throws ReferenceCountingException
	{
		Element parent= toAddTo.getParentElement();
		List con= parent.getContent();

		// go through the parents elements looking for this element
		for (int x= 0; x < con.size(); x++)
		{
			if (con.get(x).equals(toAddTo))
			{
				// order here matters so we don't screw up the index for the
				// before add.
				parent.addContent(x + 1, toAddAfter);
				parent.addContent(x, toAddBefore);

				return;
			}
		}
		throw new ReferenceCountingException("Impossible");
	}

	/**
	 * label id to label element. Used for construction of code paths.
	 */
	Map labels= new HashMap();
	/**
	 * What is the next and previous element for a particular element ?
	 */
	Map nextElement= new HashMap();
	Map prevElement= new HashMap();

	/**
	 * Represents a single run of the reference counter. We have this because we
	 * currently use a two pass implementation and don't want any interactions
	 * between the passes.
	 * 
	 * TODO: We could prevent having to do a whole lot of work in the second
	 * pass if we care to.
	 */
	class RunState
	{
		public List allCodePaths= new ArrayList();
		/*
		 * What we label the next code path as
		 */
		public int codePathId= 0;
		/*
		 * List of all the elements that we have visited in the method.
		 */
		public Map beenTo= new HashMap();
		/*
		 * What are the conflicted registers on this run?
		 */
		public RegisterSet allConflict= RegisterSet.none();
		/*
		 * Used so we don't have to use stack recursion, which apparently causes
		 * big problems in the JVM.
		 */
		LinkedList callsToDo= new LinkedList();
	}

	/*
	 * Our current run context. TODO: pass this down the stack instead of having
	 * it be an instance variable.
	 */
	RunState curRun;

	/**
	 * This adds any labels it finds to our labels map. It also populates our
	 * previous and next element hashes.
	 */
	private void addToNextPrevElement(List toProcess) throws DataConversionException
	{
		Element prev= null;
		for (int k= 0; k < toProcess.size(); k++)
		{
			Element cur= toProcess.get(k);

			if (cur.getName().equals("label"))
			{
				labels.put(cur.getAttribute("id").getIntValue(), cur);
			}

			if (prev != null)
			{
				nextElement.put(prev, cur);
				prevElement.put(cur, prev);
			}
			prev= cur;
		}
	}

	@SuppressWarnings("unchecked")
	private void processRecStart(int numReg, List toProcess, Element codeElement) throws DataConversionException, ReferenceCountingException
	{
		addToNextPrevElement(toProcess);
		for (Element x : toProcess)
		{
			if (x.getName().equals("try-catch"))
			{
				// Try catches are special: we must descend into them to
				// mark up their code.
				addToNextPrevElement(x.getChild("try", dex).getChildren());
				for (Element catchE : (List) x.getChildren("catch", dex))
				{
					addToNextPrevElement(catchE.getChildren());
				}
			}

		}

		doMarkup(toProcess);

		curRun.allConflict= RegisterSet.none();
		for (Entry x : curRun.beenTo.entrySet())
		{
			curRun.allConflict.orEq(x.getValue().getConflict());
		}

		// the method element.
		Element parent= toProcess.get(0).getParentElement();

		// We found some code paths that end up with a register that has an
		// object OR a primitive at a point where we think we need to do a
		// object release. This is bad, so we must split the register so that
		// the primitive is always separate from the object.
		if (!curRun.allConflict.isEmpty())
		{
			int newRegSize= splitConflictedRegisters(numReg, curRun.allConflict, curRun.beenTo);
			parent.getAttribute("register-size").setValue(newRegSize + "");

			doMarkup(toProcess);

		}

		refLog("Conflict is: " + curRun.allConflict);

		setWillFree(curRun.beenTo);

		// Start going through optimizations before generating change
		RefCountOptimization.ReturnValue ret= new RegisterSizeAndNullingOptimization().Process(curRun.allCodePaths, curRun.beenTo, codeElement);

		new DeferredNullingOptimization().Process(curRun.allCodePaths, curRun.beenTo, codeElement);

		// TODO fix this optimization
		// new ExcessRetainsOptimization().Process(curRun.allCodePaths,
		// curRun.beenTo, codeElement);
		toProcess.addAll(0, ret.functionInit);

		addExTempReg(toProcess);

		clearReleaseRetainOnSyntheticMembers(curRun, codeElement);
		// Now we want to follow the paths to find unambiguous ones so that we
		// can determine
		// where to do release/retain to prevent ambiguity.
		// we do this by tracking which branch we are on by explicitly
		// constructing paths through the code during
		// our normal traversal.
		boolean usesTemp= processReleaseRetain(curRun.beenTo);
		if (usesTemp)
		{
			Element setupTmp= new Element(InstructionProcessor.cmd_define_register, InstructionProcessor.vm);
			setupTmp.setAttribute("vartype", InstructionProcessor.cmd_define_register_attr_temp);
			toProcess.add(0, setupTmp);
		}

	}

	/*
	 * Synthetics help create cycles so we don't do releases or retains on them.
	 */
	@SuppressWarnings("unchecked")
	private void clearReleaseRetainOnSyntheticMembers(RunState curRun, Element codeElement) throws DataConversionException
	{
		// Find the synthetic members of the class;
		Element classElement= codeElement.getParentElement().getParentElement();

		HashSet hashSet= new HashSet();

		for (Element elem : (List) classElement.getChildren())
		{
			if (elem.getName().equals("field") && elem.getAttribute("isSynthetic") != null && elem.getAttributeValue("isSynthetic").equals("true") && elem.getAttributeValue("name").startsWith("this$"))
			{
				hashSet.add(elem.getAttributeValue("name"));
			}
		}

		for (Map.Entry e : curRun.beenTo.entrySet())
		{

			String instructionElementName= e.getKey().getName();
			if ((instructionElementName.equals("iput-object") || instructionElementName.equals("iput")) && e.getKey().getAttribute("member-name") != null && hashSet.contains(e.getKey().getAttributeValue("member-name")))
			{
				InstructionUseInfo useInfo= e.getValue().useInfo;
				// We don't want to release what was in there because it was not
				// retained
				useInfo.putRelease= null;
				useInfo.requiresRetain= RegisterSet.none();
			}
		}
	}

	/**
	 * Adds definition for exception register if needed.
	 */
	private void addExTempReg(List toProcess)
	{
		boolean useEx= false;
		boolean useTmp= false;
		for (Element e : curRun.beenTo.keySet())
		{
			if (e.getName().equals("throw") || e.getName().equals("try-catch"))
			{
				useEx= true;
			}

			if (useEx && useTmp)
			{
				break; // early quit
			}
		}
		if (useEx)
		{
			Element setupEx= new Element(InstructionProcessor.cmd_define_register, InstructionProcessor.vm);
			setupEx.setAttribute("vartype", InstructionProcessor.cmd_define_register_attr_exception);
			toProcess.add(0, setupEx);
		}
		for (Element e : curRun.beenTo.keySet())
		{
			if (e.getName().startsWith("return"))
			{
				e.setAttribute("catchesException", useEx + "");
			}
		}
	}

	/**
	 * Determines conflicts and retain/release for the method.
	 */
	private void doMarkup(List toProcess) throws DataConversionException
	{
		// create a new run of the processor, prime the recursion, and then
		// run it until its complete.
		curRun= new RunState();
		processRecAdd(RegisterSet.none(), RegisterSet.none(), toProcess.get(0), createNewCodePath(null));
		processWhileCallsToDo();
		// Debug print for state at this stage.
		//printInstSeq(toProcess);
	}

	/*
	 * Purely for debug, shows the instructions as well as our what we have
	 * calculated about them.
	 */
	@SuppressWarnings("unchecked")
	private void printInstSeq(List toProcess)
	{
		refLog("All " + toProcess.size() + " instructions been to " + curRun.beenTo.size());
		for (Element x : toProcess)
		{
			if (curRun.beenTo.containsKey(x))
			{
				String startStr= curRun.beenTo.get(x).useInfo + "";
				if (x.getName().equals("label"))
				{
					refLog(startStr + " ID = " + x.getAttributeValue("id"));
				}
				else
				{
					refLog(startStr + "");
				}

				if (x.getName().equals("try-catch"))
				{
					printInstSeq(x.getChild("try", dex).getChildren());
				}
			}
		}
	}

	/**
	 * In certain cases, DEX will create a code path where we think we need to
	 * do a release of an object on a particular register which may or may not
	 * hold an object depending on the particular path through the code taken at
	 * runtime. There are several ways to approach this issue, the most simple
	 * is to split a conflicted register into two new registers. Conceptually,
	 * this is done by defining a function that maps RX to RY or RZ depending on
	 * whether RX is known to hold an object or a primitive.
	 * 
	 * The following code implements this mapping, with the slight optimization
	 * that instead of mapping RX to RY and RZ, it maps it to RX and RY. This is
	 * because it keeps the sequence of registers intact with no holes, and
	 * because it allows us to deal with function parameters more easily.
	 * 
	 * We return the total number of registers required for this method.
	 */
	int splitConflictedRegisters(int numReg, RegisterSet allConflict, Map beenTo) throws DataConversionException, ReferenceCountingException
	{

		for (int reg : allConflict)
		{
			int newReg= numReg++;

			// When dealing with a passed parameter that has a conflict, we want
			// to make sure to not change the register that the parameter is
			// originally inserted into.
			int regObj= reg;
			int regNonObj= newReg;

			for (Element varE : beenTo.keySet())
			{
				if (varE.getName().equals("var"))
				{
					InstructionUseInfo varUi= this.curRun.beenTo.get(varE).useInfo;
					if (varUi.isWrite)
					{
						if (!varUi.writesObj().isEmpty())
						{
							regObj= reg;
							regNonObj= newReg;
							break;
						}
						else if (!varUi.writesNonObj().isEmpty())
						{
							regObj= newReg;
							regNonObj= reg;
						}
						else
						{
							throw new ReferenceCountingException("impossible");
						}
					}
				}
			}

			refLog(reg + " -> o:" + regObj + ":" + regNonObj);

			// Go through all the instructions in the method, replacing any
			// that use the conflicted value with the new register or the old
			// register depending on whether the instruction expects the
			// register to contain an object or a primitive.
			for (Map.Entry beenToKv : beenTo.entrySet())
			{
				InstructionUseInfo ui= beenToKv.getValue().useInfo;

				for (Map.Entry kv : ui.typeIsObj.entrySet())
				{
					if (kv.getKey().getIntValue() == reg)
					{
						if (kv.getValue())
						{
							// its an object
							kv.getKey().setValue(regObj + "");
						}
						else
						{
							kv.getKey().setValue(regNonObj + "");
						}
					}
				}
			}
		}
		return numReg;
	}

	/**
	 * Given a parent code path, create a child.
	 */
	private CodePath createNewCodePath(CodePath curPath)
	{
		CodePath c;
		c= new CodePath(curRun.codePathId++, curPath);
		curRun.allCodePaths.add(c);
		if (curPath != null)
		{
			curPath.subPaths.add(c);
		}
		return c;
	}

	/**
	 * Class representing collected parameters for one execution of the body of
	 * ProcessWhileCallsToDo
	 */
	class OneRecusiveCall
	{
		RegisterSet regHoldingObject;
		RegisterSet regNotHoldingObject;
		Element currentElement;
		CodePath codePath;
	}

	/**
	 * Helper to add to the list of recursive calls to do.
	 */
	private void processRecAdd(RegisterSet regHoldingObject, RegisterSet regNotHoldingObject, Element currentElement, CodePath codePath) throws DataConversionException
	{
		OneRecusiveCall oneCall= new OneRecusiveCall();
		oneCall.regHoldingObject= regHoldingObject;
		oneCall.regNotHoldingObject= regNotHoldingObject;
		oneCall.currentElement= currentElement;
		oneCall.codePath= codePath;
		this.curRun.callsToDo.add(oneCall);
	}

	/**
	 * This function creates a representation of the different execution paths
	 * through the method. At the same time, it gathers information on how
	 * particular instructions are making use of registers. This is an
	 * implementation of a recursive function, however when implemented as a
	 * directly recursive function (without the calls to do) the JVM blows up
	 * for lack of stack space. Because modifying stack space available to a
	 * thread in the JVM (even if you create a new thread) is a pain, we just
	 * switched to using a heap list for the recursive stack.
	 * 
	 * In most cases this is *not* tail recursion, so don't try and make a loop
	 * out of it.
	 */
	@SuppressWarnings("unchecked")
	private void processWhileCallsToDo() throws DataConversionException
	{
		int maxSize= 0;
		while (this.curRun.callsToDo.size() != 0)
		{
			maxSize= Math.max(maxSize, this.curRun.callsToDo.size());
			OneRecusiveCall thisTime= this.curRun.callsToDo.removeFirst();

			// Arguments to the recursive function.
			RegisterSet regHoldingObject= thisTime.regHoldingObject;
			RegisterSet regNotHoldingObject= thisTime.regNotHoldingObject;
			Element currentElement= thisTime.currentElement;
			CodePath codePath= thisTime.codePath;

			if (currentElement == null)
			{
				continue; // base case
			}

			InstructionActions actions= beenHereBefore(this.curRun.beenTo, regHoldingObject, regNotHoldingObject, currentElement, codePath);
			if (actions == null)
			{
				continue; // base case
			}
			InstructionUseInfo useInfo= actions.useInfo;

			Element nextInstruction;

			if (currentElement.getName().startsWith("goto"))
			{
				nextInstruction= labels.get(currentElement.getAttribute("target").getIntValue());
			}
			else
			{
				nextInstruction= nextElement.get(currentElement);
				if (nextInstruction == null && currentElement.getParentElement().getName().equals("try"))
				{
					// Exited the try, move to the element after the
					// try terminates.
					nextInstruction= nextElement.get(currentElement.getParentElement().getParentElement());
				}
			}

			// the ones we came in with
			RegisterSet ourObjUse= regHoldingObject.clone();
			// plus the ones that we write obj to
			ourObjUse.orEq(useInfo.writesObj());
			// minus the ones we write non obj into
			ourObjUse.andEqNot(useInfo.writesNonObj());

			RegisterSet ourNonObjUse= regNotHoldingObject.clone();
			ourNonObjUse.orEq(useInfo.writesNonObj());
			ourNonObjUse.andEqNot(useInfo.writesObj());

			if (currentElement.getName().startsWith("return"))
			{
				continue;
			}

			if (currentElement.getName().equals("try-catch"))
			{
				processRecAdd(ourObjUse, ourNonObjUse, (Element) currentElement.getChild("try", dex).getChildren().get(0), createNewCodePath(codePath));

				for (Element caught : (List) currentElement.getChildren("catch", dex))
				{
					Element nextInst= labels.get(caught.getAttribute("target").getIntValue());

					processRecAdd(ourObjUse, ourNonObjUse, nextInst, createNewCodePath(codePath));
				}

			}
			else if (currentElement.getName().equals("packed-switch") || currentElement.getName().equals("sparse-switch"))
			{
				processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, createNewCodePath(codePath));

				for (Element target : (List) currentElement.getChildren("case", dex))
				{
					processRecAdd(ourObjUse, ourNonObjUse, labels.get(target.getAttribute("label").getIntValue()), createNewCodePath(codePath));
				}
			}
			else if (currentElement.getName().startsWith("if"))
			{
				processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, createNewCodePath(codePath));
				processRecAdd(ourObjUse, ourNonObjUse, labels.get(currentElement.getAttribute("target").getIntValue()), createNewCodePath(codePath));
			}
			else if (currentElement.getName().equals("label"))
			{
				// It will be useful in the future to have labels treated as
				// creating a new code path.
				processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, createNewCodePath(codePath));

			}
			else
			{
				// straight line code.
				processRecAdd(ourObjUse, ourNonObjUse, nextInstruction, codePath);
			}
		}
		refLog("Max recusrive depth " + maxSize);
	}

	/**
	 * This thing is used to determine whether or not our recursion keeps going
	 * It terminates the recursion if we have been to this instruction with the
	 * exact same state before. We return information collected about the
	 * current instruction to the caller.
	 */
	private static InstructionActions beenHereBefore(Map beenTo, RegisterSet regHoldingObject, RegisterSet regNotHoldingObject, Element currentElement, CodePath c) throws DataConversionException
	{
		InstructionActions toRet;

		if (beenTo.containsKey(currentElement))
		{
			// Visited here on another code branch
			toRet= beenTo.get(currentElement);
		}
		else
		{
			// Haven't been here yet.
			toRet= new InstructionActions();
			toRet.useInfo= processElement(currentElement);
			beenTo.put(currentElement, toRet);

		}

		// Labels are not code, and thus do not belong in code paths.
		if (!currentElement.getName().equals("label"))
		{
			c.path.add(new OnePathInstructionRegisterContents(currentElement, regHoldingObject, regNotHoldingObject));
		}

		boolean enteredNotHolding= false;
		// figure out if we have been here before with the same
		// enteredNotHolding state
		for (RegisterSet m : toRet.enteredNot)
		{
			if (m.equals(regNotHoldingObject))
			{
				enteredNotHolding= true;
				break;
			}
		}

		boolean enteredHolding= false;
		// figure out if we have been here before with the same enteredHolding
		// state.
		for (RegisterSet m : toRet.enteredHoldingObj)
		{
			if (m.equals(regHoldingObject))
			{
				enteredHolding= true;
				break;
			}
		}

		if (enteredNotHolding && enteredHolding)
		{
			// We were here before with the exact same state: time to terminate
			// the search along this path.
			return null;
		}
		// Add info about the state we were in when we got to here along this
		// code path.
		if (!enteredHolding)
		{
			toRet.enteredHoldingObj.add(regHoldingObject);
		}
		if (!enteredNotHolding)
		{
			toRet.enteredNot.add(regNotHoldingObject);
		}
		return toRet;
	}

	/**
	 * This function creates a InstructionUseInfo based on the current element
	 * TODO: if anyone really cares this can be made faster by not using
	 * reflection.
	 */
	private static InstructionUseInfo processElement(Element element) throws DataConversionException
	{
		InstructionUseInfo use= new InstructionUseInfo(element);
		// If we find the instruction using the generic handler, return
		// immediately.
		if (InstructionProcessor.processGeneric(element, use))
		{
			return use;
		}
		else
		{
			// Otherwise, we need to hit the correct processor function:
			String todo= "process_" + element.getName().replace("-", "_");
			Method method;
			try
			{
				method= InstructionProcessor.class.getMethod(todo, Element.class, InstructionUseInfo.class);
				method.invoke(null, element, use);
			}
			catch (Exception ex)
			{
				throw new DataConversionException(ex.getMessage(), "When attempting to: " + todo);
			}
		}
		return use;
	}

	private static void refLog(String message)
	{
		// Log.debug("ref", message);
	}

}