resolved conflicts for merge of ab01c96b to lmp-mr1-dev

Change-Id: Ie70b72005a6d3df47adb0e5b1b21b9d7b99b8ab0
diff --git a/dx/etc/mainDexClasses b/dx/etc/mainDexClasses
index 28c0f0c..178ab18 100755
--- a/dx/etc/mainDexClasses
+++ b/dx/etc/mainDexClasses
@@ -137,10 +137,19 @@
     jarpath="$libdir/$jarfile"
 fi
 
+disableKeepAnnotated=
+
+while true; do
 if expr "x$1" : 'x--output' >/dev/null; then
     exec 1>$2
     shift 2
+elif expr "x$1" : 'x--disable-annotation-resolution-workaround' >/dev/null; then
+    disableKeepAnnotated=$1
+    shift 1
+else
+    break
 fi
+done
 
 if [ $# -ne 1 ]; then
   echo "Usage : $0 [--output <output file>] <application path>" 1>&2
@@ -155,4 +164,4 @@
   -libraryjars "${shrinkedAndroidJar}" -dontoptimize -dontobfuscate -dontpreverify \
   -include "${baserules}" 1>/dev/null || exit 10
 
-java -cp "$jarpath" com.android.multidex.MainDexListBuilder "${tmpOut}" ${@} ||  exit 11
+java -cp "$jarpath" com.android.multidex.MainDexListBuilder ${disableKeepAnnotated} "${tmpOut}" ${@} ||  exit 11
diff --git a/dx/etc/mainDexClasses.bat b/dx/etc/mainDexClasses.bat
index f6a4b56..1923ee4 100755
--- a/dx/etc/mainDexClasses.bat
+++ b/dx/etc/mainDexClasses.bat
@@ -67,6 +67,7 @@
 set params=

 

 set output=

+set disableKeepAnnotated=

 

 :firstArg

 if [%1]==[] goto endArgs

@@ -78,6 +79,13 @@
         goto firstArg

 

 :notOut

+

+    if %1 NEQ --disable-annotation-resolution-workaround goto notDisable

+        set "disableKeepAnnotated=%1"

+        shift

+        goto firstArg

+

+:notDisable

     if defined params goto usage

     set params=%1

     shift

@@ -96,10 +104,10 @@
 call "%proguard%" -injars %params% -dontwarn -forceprocessing  -outjars "%tmpJar%" -libraryjars "%shrinkedAndroidJar%" -dontoptimize -dontobfuscate -dontpreverify -include "%baserules%" 1>nul

 

 if DEFINED output goto redirect

-call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder "%tmpJar%" "%params%"

+call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder "%disableKeepAnnotated%" "%tmpJar%" "%params%"

 goto afterClassReferenceListBuilder

 :redirect

-call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder "%tmpJar%" "%params%" 1>"%output%"

+call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder "%disableKeepAnnotated%" "%tmpJar%" "%params%" 1>"%output%"

 :afterClassReferenceListBuilder

 

 del %tmpJar%

diff --git a/dx/src/com/android/dx/Version.java b/dx/src/com/android/dx/Version.java
index 5f02e47..1e15d55 100644
--- a/dx/src/com/android/dx/Version.java
+++ b/dx/src/com/android/dx/Version.java
@@ -21,5 +21,5 @@
  */
 public class Version {
     /** {@code non-null;} version string */
-    public static final String VERSION = "1.10";
+    public static final String VERSION = "1.11";
 }
diff --git a/dx/src/com/android/dx/cf/direct/ClassPathOpener.java b/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
index c9fe275..26fbca0 100644
--- a/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
+++ b/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
@@ -242,9 +242,6 @@
      */
     private boolean processArchive(File file) throws IOException {
         ZipFile zip = new ZipFile(file);
-        ByteArrayOutputStream baos = new ByteArrayOutputStream(40000);
-        byte[] buf = new byte[20000];
-        boolean any = false;
 
         ArrayList<? extends java.util.zip.ZipEntry> entriesList
                 = Collections.list(zip.entries());
@@ -259,28 +256,31 @@
 
         consumer.onProcessArchiveStart(file);
 
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(40000);
+        byte[] buf = new byte[20000];
+        boolean any = false;
+
         for (ZipEntry one : entriesList) {
-            if (one.isDirectory()) {
-                continue;
-            }
+            final boolean isDirectory = one.isDirectory();
 
             String path = one.getName();
             if (filter.accept(path)) {
-                InputStream in = zip.getInputStream(one);
+                final byte[] bytes;
+                if (!isDirectory) {
+                    InputStream in = zip.getInputStream(one);
 
-                baos.reset();
-                for (;;) {
-                    int amt = in.read(buf);
-                    if (amt < 0) {
-                        break;
+                    baos.reset();
+                    int read;
+                    while ((read = in.read(buf)) != -1) {
+                        baos.write(buf, 0, read);
                     }
 
-                    baos.write(buf, 0, amt);
+                    in.close();
+                    bytes = baos.toByteArray();
+                } else {
+                    bytes = new byte[0];
                 }
 
-                in.close();
-
-                byte[] bytes = baos.toByteArray();
                 any |= consumer.processFileBytes(path, one.getTime(), bytes);
             }
         }
diff --git a/dx/src/com/android/dx/command/DxConsole.java b/dx/src/com/android/dx/command/DxConsole.java
index 9ce9836..919de8c 100644
--- a/dx/src/com/android/dx/command/DxConsole.java
+++ b/dx/src/com/android/dx/command/DxConsole.java
@@ -16,6 +16,8 @@
 
 package com.android.dx.command;
 
+import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintStream;
 
 /**
@@ -34,4 +36,15 @@
      * Error output stream. Links to {@code System.err} by default.
      */
     public static PrintStream err = System.err;
+
+    /**
+     * Output stream which prints to nowhere.
+     */
+    public static final PrintStream noop = new PrintStream(new OutputStream() {
+
+        @Override
+        public void write(int b) throws IOException {
+            // noop
+        }
+    });
 }
diff --git a/dx/src/com/android/dx/command/Main.java b/dx/src/com/android/dx/command/Main.java
index 9834987..a0be5bd 100644
--- a/dx/src/com/android/dx/command/Main.java
+++ b/dx/src/com/android/dx/command/Main.java
@@ -33,7 +33,7 @@
         "[--dump-width=<n>]\n" +
         "  [--dump-method=<name>[*]] [--verbose-dump] [--no-files] " +
         "[--core-library]\n" +
-        "  [--num-threads=<n>] [--incremental] [--force-jumbo]\n" +
+        "  [--num-threads=<n>] [--incremental] [--force-jumbo] [--no-warning]\n" +
         "  [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]]\n" +
         "  [--input-list=<file>]\n" +
         "  [<file>.class | <file>.{zip,jar,apk} | <directory>] ...\n" +
diff --git a/dx/src/com/android/dx/command/dexer/Main.java b/dx/src/com/android/dx/command/dexer/Main.java
index ac4d8c2..099264c 100644
--- a/dx/src/com/android/dx/command/dexer/Main.java
+++ b/dx/src/com/android/dx/command/dexer/Main.java
@@ -64,12 +64,14 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.jar.Attributes;
@@ -179,11 +181,39 @@
     /** Library .dex files to merge into the output .dex. */
     private static final List<byte[]> libraryDexBuffers = new ArrayList<byte[]>();
 
-    /** thread pool object used for multi-threaded file processing */
-    private static ExecutorService threadPool;
+    /** Thread pool object used for multi-thread class translation. */
+    private static ExecutorService classTranslatorPool;
 
-    /** used to handle Errors for multi-threaded file processing */
-    private static List<Future<Void>> parallelProcessorFutures;
+    /** Single thread executor, for collecting results of parallel translation,
+     * and adding classes to dex file in original input file order. */
+    private static ExecutorService classDefItemConsumer;
+
+    /** Futures for {@code classDefItemConsumer} tasks. */
+    private static List<Future<Boolean>> addToDexFutures =
+            new ArrayList<Future<Boolean>>();
+
+    /** Thread pool object used for multi-thread dex conversion (to byte array).
+     * Used in combination with multi-dex support, to allow outputing
+     * a completed dex file, in parallel with continuing processing. */
+    private static ExecutorService dexOutPool;
+
+    /** Futures for {@code dexOutPool} task. */
+    private static List<Future<byte[]>> dexOutputFutures =
+            new ArrayList<Future<byte[]>>();
+
+    /** Lock object used to to coordinate dex file rotation, and
+     * multi-threaded translation. */
+    private static Object dexRotationLock = new Object();
+
+    /** Record the number if method indices "reserved" for files
+     * committed to translation in the context of the current dex
+     * file, but not yet added. */
+    private static int maxMethodIdsInProcess = 0;
+
+    /** Record the number if field indices "reserved" for files
+     * committed to translation in the context of the current dex
+     * file, but not yet added. */
+    private static int maxFieldIdsInProcess = 0;
 
     /** true if any files are successfully processed */
     private static volatile boolean anyFilesProcessed;
@@ -224,6 +254,7 @@
      * @return 0 if success > 0 otherwise.
      */
     public static int run(Arguments arguments) throws IOException {
+
         // Reset the error count to start fresh.
         errors.set(0);
         // empty the list, so that  tools that load dx and keep it around
@@ -289,7 +320,7 @@
         byte[] outArray = null;
 
         if (!outputDex.isEmpty() || (args.humanOutName != null)) {
-            outArray = writeDex();
+            outArray = writeDex(outputDex);
 
             if (outArray == null) {
                 return 2;
@@ -324,13 +355,14 @@
     private static int runMultiDex() throws IOException {
 
         assert !args.incremental;
-        assert args.numThreads == 1;
 
         if (args.mainDexListFile != null) {
             classesInMainDex = new HashSet<String>();
             readPathsFromFile(args.mainDexListFile, classesInMainDex);
         }
 
+        dexOutPool = Executors.newFixedThreadPool(args.numThreads);
+
         if (!processAllFiles()) {
             return 1;
         }
@@ -341,14 +373,31 @@
 
         if (outputDex != null) {
             // this array is null if no classes were defined
-            dexOutputArrays.add(writeDex());
+
+            dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex)));
 
             // Effectively free up the (often massive) DexFile memory.
             outputDex = null;
         }
+        try {
+            dexOutPool.shutdown();
+            if (!dexOutPool.awaitTermination(600L, TimeUnit.SECONDS)) {
+                throw new RuntimeException("Timed out waiting for dex writer threads.");
+            }
+
+            for (Future<byte[]> f : dexOutputFutures) {
+                dexOutputArrays.add(f.get());
+            }
+
+        } catch (InterruptedException ex) {
+            dexOutPool.shutdownNow();
+            throw new RuntimeException("A dex writer thread has been interrupted.");
+        } catch (Exception e) {
+            dexOutPool.shutdownNow();
+            throw new RuntimeException("Unexpected exception in dex writer thread");
+        }
 
         if (args.jarOutput) {
-
             for (int i = 0; i < dexOutputArrays.size(); i++) {
                 outputResources.put(getDexFileName(i),
                         dexOutputArrays.get(i));
@@ -368,7 +417,6 @@
                     closeOutput(out);
                 }
             }
-
         }
 
         return 0;
@@ -475,10 +523,14 @@
         String[] fileNames = args.fileNames;
         Arrays.sort(fileNames);
 
-        if (args.numThreads > 1) {
-            threadPool = Executors.newFixedThreadPool(args.numThreads);
-            parallelProcessorFutures = new ArrayList<Future<Void>>();
-        }
+        // translate classes in parallel
+        classTranslatorPool = new ThreadPoolExecutor(args.numThreads,
+               args.numThreads, 0, TimeUnit.SECONDS,
+               new ArrayBlockingQueue<Runnable>(2 * args.numThreads, true),
+               new ThreadPoolExecutor.CallerRunsPolicy());
+        // collect translated and write to dex in order
+        classDefItemConsumer = Executors.newSingleThreadExecutor();
+
 
         try {
             if (args.mainDexListFile != null) {
@@ -491,14 +543,26 @@
                     processOne(fileNames[i], mainPassFilter);
                 }
 
-                if (dexOutputArrays.size() > 0) {
+                if (dexOutputFutures.size() > 0) {
                     throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
                             + ", main dex capacity exceeded");
                 }
 
                 if (args.minimalMainDex) {
                     // start second pass directly in a secondary dex file.
-                    createDexFile();
+
+                    // Wait for classes in progress to complete
+                    synchronized(dexRotationLock) {
+                        while(maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) {
+                            try {
+                                dexRotationLock.wait();
+                            } catch(InterruptedException ex) {
+                                /* ignore */
+                            }
+                        }
+                    }
+
+                    rotateDexFile();
                 }
 
                 // remaining files
@@ -518,35 +582,36 @@
              */
         }
 
-        if (args.numThreads > 1) {
-            try {
-                threadPool.shutdown();
-                if (!threadPool.awaitTermination(600L, TimeUnit.SECONDS)) {
-                    throw new RuntimeException("Timed out waiting for threads.");
+        try {
+            classTranslatorPool.shutdown();
+            classTranslatorPool.awaitTermination(600L, TimeUnit.SECONDS);
+            classDefItemConsumer.shutdown();
+            classDefItemConsumer.awaitTermination(600L, TimeUnit.SECONDS);
+
+            for (Future<Boolean> f : addToDexFutures) {
+                try {
+                    f.get();
+                } catch(ExecutionException ex) {
+                    // Catch any previously uncaught exceptions from
+                    // class translation and adding to dex.
+                    int count = errors.incrementAndGet();
+                    if (count < 10) {
+                        DxConsole.err.println("Uncaught translation error: " + ex.getCause());
+                    } else {
+                        throw new InterruptedException("Too many errors");
+                    }
                 }
-            } catch (InterruptedException ex) {
-                threadPool.shutdownNow();
-                throw new RuntimeException("A thread has been interrupted.");
             }
 
-            try {
-              for (Future<?> future : parallelProcessorFutures) {
-                future.get();
-              }
-            } catch (ExecutionException e) {
-                Throwable cause = e.getCause();
-                // All Exceptions should have been handled in the ParallelProcessor, only Errors
-                // should remain
-                if (cause instanceof Error) {
-                    throw (Error) e.getCause();
-                } else {
-                    throw new AssertionError(e.getCause());
-                }
-            } catch (InterruptedException e) {
-              // If we're here, it means all threads have completed cleanly, so there should not be
-              // any InterruptedException
-              throw new AssertionError(e);
-            }
+        } catch (InterruptedException ie) {
+            classTranslatorPool.shutdownNow();
+            classDefItemConsumer.shutdownNow();
+            throw new RuntimeException("Translation has been interrupted", ie);
+        } catch (Exception e) {
+            classTranslatorPool.shutdownNow();
+            classDefItemConsumer.shutdownNow();
+            e.printStackTrace(System.out);
+            throw new RuntimeException("Unexpected exception in translator thread.", e);
         }
 
         int errorNum = errors.get();
@@ -573,10 +638,6 @@
     }
 
     private static void createDexFile() {
-        if (outputDex != null) {
-            dexOutputArrays.add(writeDex());
-        }
-
         outputDex = new DexFile(args.dexOptions);
 
         if (args.dumpWidth != 0) {
@@ -584,6 +645,18 @@
         }
     }
 
+    private static void rotateDexFile() {
+        if (outputDex != null) {
+            if (dexOutPool != null) {
+                dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex)));
+            } else {
+                dexOutputArrays.add(writeDex(outputDex));
+            }
+        }
+
+        createDexFile();
+    }
+
     /**
      * Processes one pathname element.
      *
@@ -595,47 +668,18 @@
     private static void processOne(String pathname, FileNameFilter filter) {
         ClassPathOpener opener;
 
-        opener = new ClassPathOpener(pathname, true, filter,
-                new ClassPathOpener.Consumer() {
+        opener = new ClassPathOpener(pathname, true, filter, new FileBytesConsumer());
 
-            @Override
-            public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
-                return Main.processFileBytes(name, lastModified, bytes);
-            }
-
-            @Override
-            public void onException(Exception ex) {
-                if (ex instanceof StopProcessing) {
-                    throw (StopProcessing) ex;
-                } else if (ex instanceof SimException) {
-                    DxConsole.err.println("\nEXCEPTION FROM SIMULATION:");
-                    DxConsole.err.println(ex.getMessage() + "\n");
-                    DxConsole.err.println(((SimException) ex).getContext());
-                } else {
-                    DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
-                    ex.printStackTrace(DxConsole.err);
-                }
-                errors.incrementAndGet();
-            }
-
-            @Override
-            public void onProcessArchiveStart(File file) {
-                if (args.verbose) {
-                    DxConsole.out.println("processing archive " + file +
-                            "...");
-                }
-            }
-        });
-
-        if (args.numThreads > 1) {
-            parallelProcessorFutures.add(threadPool.submit(new ParallelProcessor(opener)));
-        } else {
-            if (opener.process()) {
-                anyFilesProcessed = true;
-            }
+        if (opener.process()) {
+          updateStatus(true);
         }
     }
 
+    private static void updateStatus(boolean res) {
+        anyFilesProcessed |= res;
+    }
+
+
     /**
      * Processes one file, which may be either a class or a resource.
      *
@@ -644,6 +688,7 @@
      * @return whether processing was successful
      */
     private static boolean processFileBytes(String name, long lastModified, byte[] bytes) {
+
         boolean isClass = name.endsWith(".class");
         boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
         boolean keepResources = (outputResources != null);
@@ -671,7 +716,10 @@
             if (lastModified < minimumFileAge) {
                 return true;
             }
-            return processClass(fixedName, bytes);
+            processClass(fixedName, bytes);
+            // Assume that an exception may occur. Status will be updated
+            // asynchronously, if the class compiles without error.
+            return false;
         } else if (isClassesDex) {
             synchronized (libraryDexBuffers) {
                 libraryDexBuffers.add(bytes);
@@ -698,42 +746,30 @@
             checkClassName(name);
         }
 
-        DirectClassFile cf =
-            new DirectClassFile(bytes, name, args.cfOptions.strictNameCheck);
-
-        cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
-        cf.getMagic();
-
-        int numMethodIds = outputDex.getMethodIds().items().size();
-        int numFieldIds = outputDex.getFieldIds().items().size();
-        int constantPoolSize = cf.getConstantPool().size();
-
-        int maxMethodIdsInDex = numMethodIds + constantPoolSize + cf.getMethods().size() +
-                MAX_METHOD_ADDED_DURING_DEX_CREATION;
-        int maxFieldIdsInDex = numFieldIds + constantPoolSize + cf.getFields().size() +
-                MAX_FIELD_ADDED_DURING_DEX_CREATION;
-
-        if (args.multiDex
-            // Never switch to the next dex if current dex is already empty
-            && (outputDex.getClassDefs().items().size() > 0)
-            && ((maxMethodIdsInDex > args.maxNumberOfIdxPerDex) ||
-                (maxFieldIdsInDex > args.maxNumberOfIdxPerDex))) {
-            DexFile completeDex = outputDex;
-            createDexFile();
-            assert  (completeDex.getMethodIds().items().size() <= numMethodIds +
-                    MAX_METHOD_ADDED_DURING_DEX_CREATION) &&
-                    (completeDex.getFieldIds().items().size() <= numFieldIds +
-                    MAX_FIELD_ADDED_DURING_DEX_CREATION);
+        try {
+            new DirectClassFileConsumer(name, bytes, null).call(
+                    new ClassParserTask(name, bytes).call());
+        } catch(Exception ex) {
+            throw new RuntimeException("Exception parsing classes", ex);
         }
 
-        try {
-            ClassDefItem clazz =
-                CfTranslator.translate(cf, bytes, args.cfOptions, args.dexOptions, outputDex);
-            synchronized (outputDex) {
-                outputDex.add(clazz);
-            }
-            return true;
+        return true;
+    }
 
+
+    private static DirectClassFile parseClass(String name, byte[] bytes) {
+
+        DirectClassFile cf = new DirectClassFile(bytes, name,
+                args.cfOptions.strictNameCheck);
+        cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
+        cf.getMagic(); // triggers the actual parsing
+        return cf;
+    }
+
+    private static ClassDefItem translateClass(byte[] bytes, DirectClassFile cf) {
+        try {
+            return CfTranslator.translate(cf, bytes, args.cfOptions,
+                    args.dexOptions, outputDex);
         } catch (ParseException ex) {
             DxConsole.err.println("\ntrouble processing:");
             if (args.debug) {
@@ -743,7 +779,14 @@
             }
         }
         errors.incrementAndGet();
-        return false;
+        return null;
+    }
+
+    private static boolean addClassToDex(ClassDefItem clazz) {
+        synchronized (outputDex) {
+            outputDex.add(clazz);
+        }
+        return true;
     }
 
     /**
@@ -793,7 +836,7 @@
      * @return {@code null-ok;} the converted {@code byte[]} or {@code null}
      * if there was a problem
      */
-    private static byte[] writeDex() {
+    private static byte[] writeDex(DexFile outputDex) {
         byte[] outArray = null;
 
         try {
@@ -832,7 +875,6 @@
             }
             return null;
         }
-
         return outArray;
     }
 
@@ -1200,6 +1242,9 @@
         /** whether to run in debug mode */
         public boolean debug = false;
 
+        /** whether to emit warning messages */
+        public boolean warnings = true;
+
         /** whether to emit high-level verbose human-oriented output */
         public boolean verbose = false;
 
@@ -1410,6 +1455,8 @@
             while(parser.getNext()) {
                 if (parser.isArg("--debug")) {
                     debug = true;
+                } else if (parser.isArg("--no-warning")) {
+                    warnings = false;
                 } else if (parser.isArg("--verbose")) {
                     verbose = true;
                 } else if (parser.isArg("--verbose-dump")) {
@@ -1543,12 +1590,6 @@
                 throw new UsageException();
             }
 
-            if (multiDex && numThreads != 1) {
-                System.out.println(NUM_THREADS_OPTION + " is ignored when used with "
-                    + MULTI_DEX_OPTION);
-                numThreads = 1;
-            }
-
             if (multiDex && incremental) {
                 System.err.println(INCREMENTAL_OPTION + " is not supported with "
                     + MULTI_DEX_OPTION);
@@ -1581,28 +1622,272 @@
             cfOptions.optimizeListFile = optimizeListFile;
             cfOptions.dontOptimizeListFile = dontOptimizeListFile;
             cfOptions.statistics = statistics;
-            cfOptions.warn = DxConsole.err;
+
+            if (warnings) {
+                cfOptions.warn = DxConsole.err;
+            } else {
+                cfOptions.warn = DxConsole.noop;
+            }
 
             dexOptions = new DexOptions();
             dexOptions.forceJumbo = forceJumbo;
         }
     }
 
-    /** Callable helper class to process files in multiple threads */
-    private static class ParallelProcessor implements Callable<Void> {
+    /**
+     * Callback class for processing input file bytes, produced by the
+     * ClassPathOpener.
+     */
+    private static class FileBytesConsumer implements ClassPathOpener.Consumer {
 
-        ClassPathOpener classPathOpener;
-
-        private ParallelProcessor(ClassPathOpener classPathOpener) {
-            this.classPathOpener = classPathOpener;
+        @Override
+        public boolean processFileBytes(String name, long lastModified,
+                byte[] bytes)   {
+            return Main.processFileBytes(name, lastModified, bytes);
         }
 
         @Override
-        public Void call() throws Exception {
-            if (classPathOpener.process()) {
-                anyFilesProcessed = true;
+        public void onException(Exception ex) {
+            if (ex instanceof StopProcessing) {
+                throw (StopProcessing) ex;
+            } else if (ex instanceof SimException) {
+                DxConsole.err.println("\nEXCEPTION FROM SIMULATION:");
+                DxConsole.err.println(ex.getMessage() + "\n");
+                DxConsole.err.println(((SimException) ex).getContext());
+            } else {
+                DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
+                ex.printStackTrace(DxConsole.err);
             }
-            return null;
+            errors.incrementAndGet();
+        }
+
+        @Override
+        public void onProcessArchiveStart(File file) {
+            if (args.verbose) {
+                DxConsole.out.println("processing archive " + file + "...");
+            }
+        }
+    }
+
+    /** Callable helper class to parse class bytes. */
+    private static class ClassParserTask implements Callable<DirectClassFile> {
+
+        String name;
+        byte[] bytes;
+
+        private ClassParserTask(String name, byte[] bytes) {
+            this.name = name;
+            this.bytes = bytes;
+        }
+
+        @Override
+        public DirectClassFile call() throws Exception {
+            DirectClassFile cf =  parseClass(name, bytes);
+
+            return cf;
+        }
+    }
+
+    /**
+     * Callable helper class used to sequentially collect the results of
+     * the (optionally parallel) translation phase, in correct input file order.
+     * This class is also responsible for coordinating dex file rotation
+     * with the ClassDefItemConsumer class.
+     * We maintain invariant that the number of indices used in the current
+     * dex file plus the max number of indices required by classes passed to
+     * the translation phase and not yet added to the dex file, is less than
+     * or equal to the dex file limit.
+     * For each parsed file, we estimate the maximum number of indices it may
+     * require. If passing the file to the translation phase would invalidate
+     * the invariant, we wait, until the next class is added to the dex file,
+     * and then reevaluate the invariant. If there are no further classes in
+     * the translation phase, we rotate the dex file.
+     */
+    private static class DirectClassFileConsumer implements Callable<Boolean> {
+
+        String name;
+        byte[] bytes;
+        Future<DirectClassFile> dcff;
+
+        private DirectClassFileConsumer(String name, byte[] bytes,
+                Future<DirectClassFile> dcff) {
+            this.name = name;
+            this.bytes = bytes;
+            this.dcff = dcff;
+        }
+
+        @Override
+        public Boolean call() throws Exception {
+
+            DirectClassFile cf = dcff.get();
+            return call(cf);
+        }
+
+        private Boolean call(DirectClassFile cf) {
+
+            int maxMethodIdsInClass = 0;
+            int maxFieldIdsInClass = 0;
+
+            if (args.multiDex) {
+
+                // Calculate max number of indices this class will add to the
+                // dex file.
+                // The possibility of overloading means that we can't easily
+                // know how many constant are needed for declared methods and
+                // fields. We therefore make the simplifying assumption that
+                // all constants are external method or field references.
+
+                int constantPoolSize = cf.getConstantPool().size();
+                maxMethodIdsInClass = constantPoolSize + cf.getMethods().size()
+                        + MAX_METHOD_ADDED_DURING_DEX_CREATION;
+                maxFieldIdsInClass = constantPoolSize + cf.getFields().size()
+                        + MAX_FIELD_ADDED_DURING_DEX_CREATION;
+                synchronized(dexRotationLock) {
+
+                    int numMethodIds;
+                    int numFieldIds;
+                    // Number of indices used in current dex file.
+                    synchronized(outputDex) {
+                        numMethodIds = outputDex.getMethodIds().items().size();
+                        numFieldIds = outputDex.getFieldIds().items().size();
+                    }
+                    // Wait until we're sure this class will fit in the current
+                    // dex file.
+                    while(((numMethodIds + maxMethodIdsInClass + maxMethodIdsInProcess
+                            > args.maxNumberOfIdxPerDex) ||
+                           (numFieldIds + maxFieldIdsInClass + maxFieldIdsInProcess
+                            > args.maxNumberOfIdxPerDex))) {
+
+                        if (maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) {
+                            // There are classes in the translation phase that
+                            // have not yet been added to the dex file, so we
+                            // wait for the next class to complete.
+                            try {
+                                dexRotationLock.wait();
+                            } catch(InterruptedException ex) {
+                                /* ignore */
+                            }
+                        } else if (outputDex.getClassDefs().items().size() > 0) {
+                            // There are no further classes in the translation
+                            // phase, and we have a full dex file. Rotate!
+                            rotateDexFile();
+                        } else {
+                            // The estimated number of indices is too large for
+                            // an empty dex file. We proceed hoping the actual
+                            // number of indices needed will fit.
+                            break;
+                        }
+                        synchronized(outputDex) {
+                            numMethodIds = outputDex.getMethodIds().items().size();
+                            numFieldIds = outputDex.getFieldIds().items().size();
+                        }
+                    }
+                    // Add our estimate to the total estimate for
+                    // classes under translation.
+                    maxMethodIdsInProcess += maxMethodIdsInClass;
+                    maxFieldIdsInProcess += maxFieldIdsInClass;
+                }
+            }
+
+            // Submit class to translation phase.
+            Future<ClassDefItem> cdif = classTranslatorPool.submit(
+                    new ClassTranslatorTask(name, bytes, cf));
+            Future<Boolean> res = classDefItemConsumer.submit(new ClassDefItemConsumer(
+                    name, cdif, maxMethodIdsInClass, maxFieldIdsInClass));
+            addToDexFutures.add(res);
+
+            return true;
+        }
+    }
+
+
+    /** Callable helper class to translate classes in parallel  */
+    private static class ClassTranslatorTask implements Callable<ClassDefItem> {
+
+        String name;
+        byte[] bytes;
+        DirectClassFile classFile;
+
+        private ClassTranslatorTask(String name, byte[] bytes,
+                DirectClassFile classFile) {
+            this.name = name;
+            this.bytes = bytes;
+            this.classFile = classFile;
+        }
+
+        @Override
+        public ClassDefItem call() {
+            ClassDefItem clazz = translateClass(bytes, classFile);
+            return clazz;
+        }
+    }
+
+    /**
+     * Callable helper class used to collect the results of
+     * the parallel translation phase, adding the translated classes to
+     * the current dex file in correct (deterministic) file order.
+     * This class is also responsible for coordinating dex file rotation
+     * with the DirectClassFileConsumer class.
+     */
+    private static class ClassDefItemConsumer implements Callable<Boolean> {
+
+        String name;
+        Future<ClassDefItem> futureClazz;
+        int maxMethodIdsInClass;
+        int maxFieldIdsInClass;
+
+        private ClassDefItemConsumer(String name, Future<ClassDefItem> futureClazz,
+                int maxMethodIdsInClass, int maxFieldIdsInClass) {
+            this.name = name;
+            this.futureClazz = futureClazz;
+            this.maxMethodIdsInClass = maxMethodIdsInClass;
+            this.maxFieldIdsInClass = maxFieldIdsInClass;
+        }
+
+        @Override
+        public Boolean call() throws Exception {
+            try {
+                ClassDefItem clazz = futureClazz.get();
+                if (clazz != null) {
+                    addClassToDex(clazz);
+                    updateStatus(true);
+                }
+                return true;
+            } catch(ExecutionException ex) {
+                // Rethrow previously uncaught translation exceptions.
+                // These, as well as any exceptions from addClassToDex,
+                // are handled and reported in processAllFiles().
+                Throwable t = ex.getCause();
+                throw (t instanceof Exception) ? (Exception) t : ex;
+            } finally {
+                if (args.multiDex) {
+                    // Having added our actual indicies to the dex file,
+                    // we subtract our original estimate from the total estimate,
+                    // and signal the translation phase, which may be paused
+                    // waiting to determine if more classes can be added to the
+                    // current dex file, or if a new dex file must be created.
+                    synchronized(dexRotationLock) {
+                        maxMethodIdsInProcess -= maxMethodIdsInClass;
+                        maxFieldIdsInProcess -= maxFieldIdsInClass;
+                        dexRotationLock.notifyAll();
+                    }
+                }
+            }
+        }
+    }
+
+    /** Callable helper class to convert dex files in worker threads */
+    private static class DexWriter implements Callable<byte[]> {
+
+        private DexFile dexFile;
+
+        private DexWriter(DexFile dexFile) {
+            this.dexFile = dexFile;
+        }
+
+        @Override
+        public byte[] call() throws IOException {
+            return writeDex(dexFile);
         }
     }
 }
diff --git a/dx/src/com/android/dx/dex/file/MixedItemSection.java b/dx/src/com/android/dx/dex/file/MixedItemSection.java
index 4edc6b6..9053043 100644
--- a/dx/src/com/android/dx/dex/file/MixedItemSection.java
+++ b/dx/src/com/android/dx/dex/file/MixedItemSection.java
@@ -189,7 +189,7 @@
      * @param item {@code non-null;} the item to intern
      * @return {@code non-null;} the equivalent interned instance
      */
-    public <T extends OffsettedItem> T intern(T item) {
+    public synchronized <T extends OffsettedItem> T intern(T item) {
         throwIfPrepared();
 
         OffsettedItem result = interns.get(item);
diff --git a/dx/src/com/android/dx/dex/file/ProtoIdsSection.java b/dx/src/com/android/dx/dex/file/ProtoIdsSection.java
index 4b1303b..b7df10c 100644
--- a/dx/src/com/android/dx/dex/file/ProtoIdsSection.java
+++ b/dx/src/com/android/dx/dex/file/ProtoIdsSection.java
@@ -86,7 +86,7 @@
      * @param prototype {@code non-null;} the prototype to intern
      * @return {@code non-null;} the interned reference
      */
-    public ProtoIdItem intern(Prototype prototype) {
+    public synchronized ProtoIdItem intern(Prototype prototype) {
         if (prototype == null) {
             throw new NullPointerException("prototype == null");
         }
diff --git a/dx/src/com/android/dx/dex/file/StringIdsSection.java b/dx/src/com/android/dx/dex/file/StringIdsSection.java
index 7aff82e..6826c5a 100644
--- a/dx/src/com/android/dx/dex/file/StringIdsSection.java
+++ b/dx/src/com/android/dx/dex/file/StringIdsSection.java
@@ -117,7 +117,7 @@
      * @param string {@code non-null;} the string to intern
      * @return {@code non-null;} the interned string
      */
-    public StringIdItem intern(StringIdItem string) {
+    public synchronized StringIdItem intern(StringIdItem string) {
         if (string == null) {
             throw new NullPointerException("string == null");
         }
@@ -140,7 +140,7 @@
      *
      * @param nat {@code non-null;} the name-and-type
      */
-    public void intern(CstNat nat) {
+    public synchronized void intern(CstNat nat) {
         intern(nat.getName());
         intern(nat.getDescriptor());
     }
diff --git a/dx/src/com/android/dx/dex/file/TypeIdsSection.java b/dx/src/com/android/dx/dex/file/TypeIdsSection.java
index c3380f4..35d8e66 100644
--- a/dx/src/com/android/dx/dex/file/TypeIdsSection.java
+++ b/dx/src/com/android/dx/dex/file/TypeIdsSection.java
@@ -106,7 +106,7 @@
      * @param type {@code non-null;} the type to intern
      * @return {@code non-null;} the interned reference
      */
-    public TypeIdItem intern(Type type) {
+    public synchronized TypeIdItem intern(Type type) {
         if (type == null) {
             throw new NullPointerException("type == null");
         }
diff --git a/dx/src/com/android/multidex/ClassReferenceListBuilder.java b/dx/src/com/android/multidex/ClassReferenceListBuilder.java
index 0434cad..8218693 100644
--- a/dx/src/com/android/multidex/ClassReferenceListBuilder.java
+++ b/dx/src/com/android/multidex/ClassReferenceListBuilder.java
@@ -19,7 +19,11 @@
 import com.android.dx.cf.direct.DirectClassFile;
 import com.android.dx.rop.cst.Constant;
 import com.android.dx.rop.cst.ConstantPool;
+import com.android.dx.rop.cst.CstFieldRef;
+import com.android.dx.rop.cst.CstMethodRef;
 import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.Prototype;
+import com.android.dx.rop.type.StdTypeList;
 import com.android.dx.rop.type.Type;
 import com.android.dx.rop.type.TypeList;
 
@@ -37,8 +41,8 @@
 public class ClassReferenceListBuilder {
     private static final String CLASS_EXTENSION = ".class";
 
-    private Path path;
-    private Set<String> classNames = new HashSet<String>();
+    private final Path path;
+    private final Set<String> classNames = new HashSet<String>();
 
     public ClassReferenceListBuilder(Path path) {
         this.path = path;
@@ -96,23 +100,35 @@
     private void addDependencies(ConstantPool pool) {
         for (Constant constant : pool.getEntries()) {
             if (constant instanceof CstType) {
-                Type type = ((CstType) constant).getClassType();
-                String descriptor = type.getDescriptor();
-                if (descriptor.endsWith(";")) {
-                    int lastBrace = descriptor.lastIndexOf('[');
-                    if (lastBrace < 0) {
-                        addClassWithHierachy(descriptor.substring(1, descriptor.length()-1));
-                    } else {
-                        assert descriptor.length() > lastBrace + 3
-                        && descriptor.charAt(lastBrace + 1) == 'L';
-                        addClassWithHierachy(descriptor.substring(lastBrace + 2,
-                                descriptor.length() - 1));
-                    }
+                checkDescriptor(((CstType) constant).getClassType());
+            } else if (constant instanceof CstFieldRef) {
+                checkDescriptor(((CstFieldRef) constant).getType());
+            } else if (constant instanceof CstMethodRef) {
+                Prototype proto = ((CstMethodRef) constant).getPrototype();
+                checkDescriptor(proto.getReturnType());
+                StdTypeList args = proto.getParameterTypes();
+                for (int i = 0; i < args.size(); i++) {
+                    checkDescriptor(args.get(i));
                 }
             }
         }
     }
 
+    private void checkDescriptor(Type type) {
+        String descriptor = type.getDescriptor();
+        if (descriptor.endsWith(";")) {
+            int lastBrace = descriptor.lastIndexOf('[');
+            if (lastBrace < 0) {
+                addClassWithHierachy(descriptor.substring(1, descriptor.length()-1));
+            } else {
+                assert descriptor.length() > lastBrace + 3
+                && descriptor.charAt(lastBrace + 1) == 'L';
+                addClassWithHierachy(descriptor.substring(lastBrace + 2,
+                        descriptor.length() - 1));
+            }
+        }
+    }
+
     private void addClassWithHierachy(String classBinaryName) {
         if (classNames.contains(classBinaryName)) {
             return;
diff --git a/dx/src/com/android/multidex/MainDexListBuilder.java b/dx/src/com/android/multidex/MainDexListBuilder.java
index c9e1a18..7fed119 100644
--- a/dx/src/com/android/multidex/MainDexListBuilder.java
+++ b/dx/src/com/android/multidex/MainDexListBuilder.java
@@ -53,18 +53,43 @@
             "Slightly longer version: This tool is used by mainDexClasses script to build" + EOL +
             "the main dex list." + EOL;
 
+    /**
+     * By default we force all classes annotated with runtime annotation to be kept in the
+     * main dex list. This option disable the workaround, limiting the index pressure in the main
+     * dex but exposing to the Dalvik resolution bug. The resolution bug occurs when accessing
+     * annotations of a class that is not in the main dex and one of the annotations as an enum
+     * parameter.
+     *
+     * @see <a href="https://code.google.com/p/android/issues/detail?id=78144">bug discussion</a>
+     *
+     */
+    private static final String DISABLE_ANNOTATION_RESOLUTION_WORKAROUND =
+            "--disable-annotation-resolution-workaround";
+
     private Set<String> filesToKeep = new HashSet<String>();
 
     public static void main(String[] args) {
 
-        if (args.length != 2) {
+        int argIndex = 0;
+        boolean keepAnnotated = true;
+        while (argIndex < args.length -2) {
+            if (args[argIndex].equals(DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) {
+                keepAnnotated = false;
+            } else {
+                System.err.println("Invalid option " + args[argIndex]);
+                printUsage();
+                System.exit(STATUS_ERROR);
+            }
+            argIndex++;
+        }
+        if (args.length - argIndex != 2) {
             printUsage();
             System.exit(STATUS_ERROR);
         }
 
         try {
-
-            MainDexListBuilder builder = new MainDexListBuilder(args[0], args[1]);
+            MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex],
+                    args[argIndex + 1]);
             Set<String> toKeep = builder.getMainDexList();
             printList(toKeep);
         } catch (IOException e) {
@@ -74,7 +99,8 @@
         }
     }
 
-    public MainDexListBuilder(String rootJar, String pathString) throws IOException {
+    public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)
+            throws IOException {
         ZipFile jarOfRoots = null;
         Path path = null;
         try {
@@ -91,7 +117,9 @@
             for (String className : mainListBuilder.getClassNames()) {
                 filesToKeep.add(className + CLASS_EXTENSION);
             }
-            keepAnnotated(path);
+            if (keepAnnotated) {
+                keepAnnotated(path);
+            }
         } finally {
             try {
                 jarOfRoots.close();
diff --git a/dx/tests/129-numthread-deterministic/expected.txt b/dx/tests/129-numthread-deterministic/expected.txt
new file mode 100644
index 0000000..5418338
--- /dev/null
+++ b/dx/tests/129-numthread-deterministic/expected.txt
@@ -0,0 +1 @@
+Yay!
diff --git a/dx/tests/129-numthread-deterministic/info.txt b/dx/tests/129-numthread-deterministic/info.txt
new file mode 100644
index 0000000..e4885fa
--- /dev/null
+++ b/dx/tests/129-numthread-deterministic/info.txt
@@ -0,0 +1,2 @@
+Test that dx generates deterministic output when using --num-threads
+
diff --git a/dx/tests/129-numthread-deterministic/run b/dx/tests/129-numthread-deterministic/run
new file mode 100644
index 0000000..fdc0506
--- /dev/null
+++ b/dx/tests/129-numthread-deterministic/run
@@ -0,0 +1,54 @@
+#!/bin/bash
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Stop if something fails.
+set -e
+
+# Write out classes
+mkdir src
+awk '
+BEGIN {
+    for (c = 1; c <= 500; c++) {
+        writeClass(c);
+    }
+}
+function writeClass(name) {
+    fileName = "src/Clazz" name ".java";
+    printf("public class Clazz%s {\n", name) > fileName;
+    for (i = 1; i <= 100; i++) {
+        printf("    int field%d;\n", i) > fileName;
+    }
+    for (i = 1; i <= 100; i++) {
+        printf("    void method%d(int param) { }\n", i) > fileName;
+    }
+    printf("}\n") > fileName;
+}'
+
+
+mkdir classes
+${JAVAC} -d classes `find src -name '*.java'`
+mkdir out
+dx -JXmx4g -JXms4g --dex --no-optimize --output=out classes
+mkdir out-multi
+dx -JXmx4g -JXms4g --dex --no-optimize --num-threads=4 --output=out-multi classes
+diff -r out out-multi > unit-out.txt
+
+if [ "$?" = "0" ]; then
+    echo "Yay!"
+else
+    cat unit-out.txt
+fi
+
diff --git a/dx/tests/130-numthread-multidex-deterministic/expected.txt b/dx/tests/130-numthread-multidex-deterministic/expected.txt
new file mode 100644
index 0000000..5418338
--- /dev/null
+++ b/dx/tests/130-numthread-multidex-deterministic/expected.txt
@@ -0,0 +1 @@
+Yay!
diff --git a/dx/tests/130-numthread-multidex-deterministic/info.txt b/dx/tests/130-numthread-multidex-deterministic/info.txt
new file mode 100644
index 0000000..e4885fa
--- /dev/null
+++ b/dx/tests/130-numthread-multidex-deterministic/info.txt
@@ -0,0 +1,2 @@
+Test that dx generates deterministic output when using --num-threads
+
diff --git a/dx/tests/130-numthread-multidex-deterministic/run b/dx/tests/130-numthread-multidex-deterministic/run
new file mode 100644
index 0000000..b43ba1f
--- /dev/null
+++ b/dx/tests/130-numthread-multidex-deterministic/run
@@ -0,0 +1,54 @@
+#!/bin/bash
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Stop if something fails.
+set -e
+
+# Write out classes
+mkdir src
+awk '
+BEGIN {
+    for (c = 1; c <= 1000; c++) {
+        writeClass(c);
+    }
+}
+function writeClass(name) {
+    fileName = "src/Clazz" name ".java";
+    printf("public class Clazz%s {\n", name) > fileName;
+    for (i = 1; i <= 100; i++) {
+        printf("    int field%d;\n", i) > fileName;
+    }
+    for (i = 1; i <= 100; i++) {
+        printf("    void method%d(int param) { }\n", i) > fileName;
+    }
+    printf("}\n") > fileName;
+}'
+
+
+mkdir classes
+${JAVAC} -d classes `find src -name '*.java'`
+mkdir out
+dx -JXmx4g -JXms4g --dex --no-optimize --multi-dex --output=out classes
+mkdir out-multi
+dx -JXmx4g -JXms4g --dex --no-optimize --multi-dex --num-threads=4 --output=out-multi classes
+diff -r out out-multi > unit-out.txt
+
+if [ "$?" = "0" ]; then
+    echo "Yay!"
+else
+    cat unit-out.txt
+fi
+
diff --git a/dx/tests/131-perf/ClassGen.java b/dx/tests/131-perf/ClassGen.java
new file mode 100644
index 0000000..c35bbcf
--- /dev/null
+++ b/dx/tests/131-perf/ClassGen.java
@@ -0,0 +1,53 @@
+import java.io.File;
+import java.io.PrintWriter;
+
+public class ClassGen {
+
+    public static void main(String... args) {
+
+	int start = 1;
+        int end =   8024;
+	int fields =   4;
+        int methods =   6;
+	if (args.length > 0) {
+	    start = Integer.parseInt(args[0]);
+        }
+	if (args.length > 1) {
+	    end = Integer.parseInt(args[1]);
+        }
+	if (args.length > 2) {
+	    fields = Integer.parseInt(args[2]);
+        }
+	if (args.length > 3) {
+	    methods = Integer.parseInt(args[3]);
+        }
+
+	for (int file = start; file <= end; file++) {
+            try {
+	        File f = new File("src/Clazz" + file + ".java");
+	        PrintWriter pw = new PrintWriter(f);
+		pw.println("class Clazz" + file + " {");
+		for (int field = 1; field <= fields; field++) {
+		    pw.println("    public static int f" + field + ";");
+		}
+		for (int method = 1; method <= methods; method++) {
+		    pw.println("    boolean m" + method + "_" + (file%(end/2)) + "() {"
+);
+		    pw.println("      int max = Thread.MAX_PRIORITY;");
+		    pw.println("      for (int i = 0; i < max; i++) {");
+		    pw.println("        System.out.println(\"Hello from: \" + Clazz"
+                            + file + ".class + \".method" + method
+                            + "() \" + Clazz" + (end-file+1) + ".f1);");
+		    pw.println("        Thread.dumpStack();");
+		    pw.println("      }");
+		    pw.println("      return Thread.holdsLock(this);");
+		    pw.println("    }");
+		}
+		pw.println("}");
+                pw.close();
+            } catch(Exception ex) {
+		System.out.println("Ups");
+            }
+        }
+    }
+}
diff --git a/dx/tests/131-perf/expected.txt b/dx/tests/131-perf/expected.txt
new file mode 100644
index 0000000..5418338
--- /dev/null
+++ b/dx/tests/131-perf/expected.txt
@@ -0,0 +1 @@
+Yay!
diff --git a/dx/tests/131-perf/info.txt b/dx/tests/131-perf/info.txt
new file mode 100644
index 0000000..f5b6a0c
--- /dev/null
+++ b/dx/tests/131-perf/info.txt
@@ -0,0 +1,2 @@
+Script for --multi-dex --num-threads performance testing, default just test options can be used together
+
diff --git a/dx/tests/131-perf/run b/dx/tests/131-perf/run
new file mode 100644
index 0000000..e57545c
--- /dev/null
+++ b/dx/tests/131-perf/run
@@ -0,0 +1,88 @@
+#!/bin/bash
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Stop if something fails.
+set -e
+
+# Write out classes
+
+${JAVAC} ClassGen.java
+
+mkdir src
+mkdir classes
+
+# Heap size, min and max
+MEM=4g
+
+MULTIDEX="--multi-dex"
+THREADS="--num-threads=5"
+
+# Extra statistics, use to calibrate test.
+#EXTRA="--profile-concurrency"
+
+# Test smaller dex files
+#EXTRA="--set-max-idx-number=20000"
+
+# Test GC options
+#GC="-JXX:+UseConcMarkSweepGC"
+
+# Limit HW threads
+#TASKSET="taskset 0x00000fff
+
+# Number of classes, initial
+TEST_SIZE=1000
+
+# number of classes, max
+LIMIT=1000
+
+# Number of additional classes per test
+STEP=100
+
+# Number of fields per classes
+FIELDS=4
+
+# Number of methods per class
+METHODS=6
+
+
+first=1;
+while [ $TEST_SIZE -le $LIMIT ]; do
+  rm -rf out
+  mkdir out
+
+  sleep 2
+  java -classpath . ClassGen $first $TEST_SIZE $FIELDS $METHODS
+  first=`expr $TEST_SIZE + 1`
+
+  ${JAVAC} -d classes `find src -name '*.java'`
+  (cd classes; jar cf ../x.jar `find . -name '*.class'`)
+  sleep 3
+
+  start=`date +'%s%N'`
+  $TASKSET dx -JXmx$MEM -JXms$MEM $GC --dex $EXTRA --no-optimize $MULTIDEX $THREADS --output=out x.jar
+  end=`date +'%s%N'`
+  nsec=`expr $end - $start`
+  msec=`expr $nsec / 1000000`
+  echo "Classes/msec $TEST_SIZE $msec"
+
+  TEST_SIZE=`expr $TEST_SIZE + $STEP`
+done
+
+if [ "$?" = "1" ]; then
+    echo "Yay!"
+else
+    cat unit-out.txt
+fi
diff --git a/tools/hprof-conv/HprofConv.c b/tools/hprof-conv/HprofConv.c
index bd8bc4f..bd61e36 100644
--- a/tools/hprof-conv/HprofConv.c
+++ b/tools/hprof-conv/HprofConv.c
@@ -25,6 +25,7 @@
 #include <stdint.h>
 #include <errno.h>
 #include <assert.h>
+#include <unistd.h>
 
 //#define VERBOSE_DEBUG
 #ifdef VERBOSE_DEBUG
@@ -84,9 +85,17 @@
     HPROF_PRIMITIVE_ARRAY_NODATA_DUMP   = 0xc3,
 } HprofHeapTag;
 
+typedef enum HprofHeapId {
+    HPROF_HEAP_DEFAULT = 0,
+    HPROF_HEAP_ZYGOTE = 'Z',
+    HPROF_HEAP_APP = 'A',
+    HPROF_HEAP_IMAGE = 'I',
+} HprofHeapId;
+
 #define kIdentSize  4
 #define kRecHdrLen  9
 
+#define kFlagAppOnly 1
 
 /*
  * ===========================================================================
@@ -423,13 +432,15 @@
  * Crunch through a heap dump record, writing the original or converted
  * data to "out".
  */
-static int processHeapDump(ExpandBuf* pBuf, FILE* out)
+static int processHeapDump(ExpandBuf* pBuf, FILE* out, int flags)
 {
     ExpandBuf* pOutBuf = ebAlloc();
     unsigned char* origBuf = ebGetBuffer(pBuf);
     unsigned char* buf = origBuf;
     int len = ebGetLength(pBuf);
     int result = -1;
+    int heapType = HPROF_HEAP_DEFAULT;
+    int heapIgnore = FALSE;
 
     pBuf = NULL;        /* we just use the raw pointer from here forward */
 
@@ -480,16 +491,31 @@
             break;
         case HPROF_INSTANCE_DUMP:
             subLen = computeInstanceDumpLen(buf+1, len-1);
+            if (heapIgnore) {
+                justCopy = FALSE;
+            }
             break;
         case HPROF_OBJECT_ARRAY_DUMP:
             subLen = computeObjectArrayDumpLen(buf+1, len-1);
+            if (heapIgnore) {
+                justCopy = FALSE;
+            }
             break;
         case HPROF_PRIMITIVE_ARRAY_DUMP:
             subLen = computePrimitiveArrayDumpLen(buf+1, len-1);
+            if (heapIgnore) {
+                justCopy = FALSE;
+            }
             break;
-
         /* these were added for Android in 1.0.3 */
         case HPROF_HEAP_DUMP_INFO:
+            heapType = get4BE(buf+1);
+            if ((flags & kFlagAppOnly) != 0
+                    && (heapType == HPROF_HEAP_ZYGOTE || heapType == HPROF_HEAP_IMAGE)) {
+                heapIgnore = TRUE;
+            } else {
+                heapIgnore = FALSE;
+            }
             justCopy = FALSE;
             subLen = kIdentSize + 4;
             // no 1.0.2 equivalent for this
@@ -570,7 +596,7 @@
 /*
  * Filter an hprof data file.
  */
-static int filterData(FILE* in, FILE* out)
+static int filterData(FILE* in, FILE* out, int flags)
 {
     const char *magicString;
     ExpandBuf* pBuf;
@@ -645,12 +671,11 @@
                 goto bail;
         }
 
-        if (type == HPROF_TAG_HEAP_DUMP ||
-            type == HPROF_TAG_HEAP_DUMP_SEGMENT)
-        {
+        if (type == HPROF_TAG_HEAP_DUMP
+                || type == HPROF_TAG_HEAP_DUMP_SEGMENT) {
             DBUG("Processing heap dump 0x%02x (%d bytes)\n",
                 type, length);
-            if (processHeapDump(pBuf, out) != 0)
+            if (processHeapDump(pBuf, out, flags) != 0)
                 goto bail;
             ebClear(pBuf);
         } else {
@@ -668,57 +693,75 @@
     return result;
 }
 
-/*
- * Get args.
- */
+static FILE* fopen_or_default(const char* path, const char* mode, FILE* def) {
+    if (!strcmp(path, "-")) {
+        return def;
+    } else {
+        return fopen(path, mode);
+    }
+}
+
 int main(int argc, char** argv)
 {
-    FILE* in = stdin;
-    FILE* out = stdout;
-    int cc;
+    FILE* in = NULL;
+    FILE* out = NULL;
+    int flags = 0;
+    int res = 1;
 
-    if (argc != 3) {
-        fprintf(stderr, "Usage: hprof-conf infile outfile\n\n");
-        fprintf(stderr,
-            "Specify '-' for either or both to use stdin/stdout.\n\n");
-
-        fprintf(stderr,
-            "Copyright (C) 2009 The Android Open Source Project\n\n"
-            "This software is built from source code licensed under the "
-            "Apache License,\n"
-            "Version 2.0 (the \"License\"). You may obtain a copy of the "
-            "License at\n\n"
-            "     http://www.apache.org/licenses/LICENSE-2.0\n\n"
-            "See the associated NOTICE file for this software for further "
-            "details.\n");
-
-        return 2;
-    }
-
-    if (strcmp(argv[1], "-") != 0) {
-        in = fopen(argv[1], "rb");
-        if (in == NULL) {
-            fprintf(stderr, "ERROR: failed to open input '%s': %s\n",
-                argv[1], strerror(errno));
-            return 1;
-        }
-    }
-    if (strcmp(argv[2], "-") != 0) {
-        out = fopen(argv[2], "wb");
-        if (out == NULL) {
-            fprintf(stderr, "ERROR: failed to open output '%s': %s\n",
-                argv[2], strerror(errno));
-            if (in != stdin)
-                fclose(in);
-            return 1;
+    int opt;
+    while ((opt = getopt(argc, argv, "z")) != -1) {
+        switch (opt) {
+            case 'z':
+                flags |= kFlagAppOnly;
+                break;
+            case '?':
+            default:
+                goto usage;
         }
     }
 
-    cc = filterData(in, out);
+    int i;
+    for (i = optind; i < argc; i++) {
+        char* arg = argv[i];
+        if (!in) {
+            in = fopen_or_default(arg, "rb", stdin);
+        } else if (!out) {
+            out = fopen_or_default(arg, "wb", stdout);
+        } else {
+            goto usage;
+        }
+    }
 
+    if (in == NULL || out == NULL) {
+        goto usage;
+    }
+
+    res = filterData(in, out, flags);
+    goto finish;
+
+usage:
+    fprintf(stderr, "Usage: hprof-conf [-z] infile outfile\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "  -z: exclude non-app heaps, such as Zygote\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "Specify '-' for either or both files to use stdin/stdout.\n");
+    fprintf(stderr, "\n");
+
+    fprintf(stderr,
+        "Copyright (C) 2009 The Android Open Source Project\n\n"
+        "This software is built from source code licensed under the "
+        "Apache License,\n"
+        "Version 2.0 (the \"License\"). You may obtain a copy of the "
+        "License at\n\n"
+        "     http://www.apache.org/licenses/LICENSE-2.0\n\n"
+        "See the associated NOTICE file for this software for further "
+        "details.\n");
+    res = 2;
+
+finish:
     if (in != stdin)
         fclose(in);
     if (out != stdout)
         fclose(out);
-    return (cc != 0);
+    return res;
 }