Merge "Ensure that compile-time constants from library classes are ignored"
diff --git a/build.gradle b/build.gradle
index 5cdfd45..c734ad5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -131,10 +131,10 @@
     })
     compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
     compile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
-    compile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA'
-    compile group: 'org.ow2.asm', name: 'asm-commons', version: '6.0_BETA'
-    compile group: 'org.ow2.asm', name: 'asm-tree', version: '6.0_BETA'
-    compile group: 'org.ow2.asm', name: 'asm-util', version: '6.0_BETA'
+    compile group: 'org.ow2.asm', name: 'asm', version: '5.1'
+    compile group: 'org.ow2.asm', name: 'asm-commons', version: '5.1'
+    compile group: 'org.ow2.asm', name: 'asm-tree', version: '5.1'
+    compile group: 'org.ow2.asm', name: 'asm-util', version: '5.1'
     testCompile sourceSets.examples.output
     testCompile 'junit:junit:4.12'
     testCompile group: 'org.smali', name: 'smali', version: '2.2b4'
@@ -143,7 +143,7 @@
     jctfCommonCompile 'junit:junit:4.12'
     jctfTestsCompile 'junit:junit:4.12'
     jctfTestsCompile sourceSets.jctfCommon.output
-    examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA'
+    examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '5.1'
     examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0'
     examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0'
     supportLibs 'com.android.support:support-v4:25.4.0'
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 5a18b41..6190cbd 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -37,7 +37,8 @@
 
   private List<String> run(AndroidApp app) throws IOException, ExecutionException {
     ExecutorService executor = ThreadUtils.getExecutorService(options);
-    DexApplication application = new ApplicationReader(app, options, timing).read(executor);
+    DexApplication application =
+        new ApplicationReader(app, options, timing).read(executor).toDirect();
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
     RootSet mainDexRootSet =
         new RootSetBuilder(application, appInfo, options.mainDexKeepRules).run(executor);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 0b16963..d0badf2 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -222,7 +222,7 @@
             + oLevel.getName() + " and later (--min-api " + oLevel.getLevel() + ")");
       }
       DexApplication application =
-          new ApplicationReader(inputApp, options, timing).read(executorService);
+          new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
 
       AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
       RootSet rootSet;
@@ -309,7 +309,7 @@
 
         // Calculate the automatic main dex list according to legacy multidex constraints.
         // Add those classes to an eventual manual list of classes.
-        application = new DexApplication.Builder(application)
+        application = application.builder()
             .addToMainDexList(new MainDexListBuilder(mainDexBaseClasses, application).run())
             .build();
       }
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectState.java b/src/main/java/com/android/tools/r8/bisect/BisectState.java
index 8c0626a..2e7d731 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectState.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -270,7 +270,7 @@
       }
     }
     System.out.println("Class split is good: " + goodClasses + ", bad: " + badClasses);
-    return new Builder(badApp).replaceProgramClasses(programClasses).build();
+    return badApp.builder().replaceProgramClasses(programClasses).build();
   }
 
   private DexProgramClass getGoodClass(DexProgramClass clazz) {
@@ -326,6 +326,7 @@
     return classes;
   }
 
+  @SuppressWarnings("deprecation")
   private static String makeSignature(DexApplication app) {
     List<DexProgramClass> classes = getSortedClasses(app);
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 3e2aba5..9aa712a 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.naming.ProguardMapReader;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ClassProvider;
@@ -73,7 +74,7 @@
   public final DexApplication read(ExecutorService executorService)
       throws IOException, ExecutionException {
     timing.begin("DexApplication.read");
-    final DexApplication.Builder builder = new DexApplication.Builder(itemFactory, timing);
+    final LazyLoadedDexApplication.Builder builder = DexApplication.builder(itemFactory, timing);
     try {
       List<Future<?>> futures = new ArrayList<>();
       // Still preload some of the classes, primarily for two reasons:
@@ -142,7 +143,7 @@
     }
   }
 
-  private void readMainDexList(DexApplication.Builder builder, ExecutorService executorService,
+  private void readMainDexList(DexApplication.Builder<?> builder, ExecutorService executorService,
       List<Future<?>> futures) {
     if (inputApp.hasMainDexList()) {
       futures.add(executorService.submit(() -> {
@@ -254,7 +255,7 @@
           : ClassProvider.combine(classKind, providers);
     }
 
-    void initializeLazyClassCollection(DexApplication.Builder builder) {
+    void initializeLazyClassCollection(LazyLoadedDexApplication.Builder builder) {
       // Add all program classes to the builder.
       for (DexProgramClass clazz : programClasses) {
         builder.addProgramClass(clazz.asProgramClass());
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index afd923c..2d111f0 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -52,10 +52,6 @@
     return app.classes();
   }
 
-  public Iterable<DexLibraryClass> libraryClasses() {
-    return app.libraryClasses();
-  }
-
   public DexClass definitionFor(DexType type) {
     return app.definitionFor(type);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 9c87b61..54b2a51 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.graph;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -21,19 +23,29 @@
 
   public AppInfoWithSubtyping(DexApplication application) {
     super(application);
-    populateSubtypeMap(application.getFullClassMap(), application.dexItemFactory);
+    populateSubtypeMap(application.asDirect(), application.dexItemFactory);
   }
 
   protected AppInfoWithSubtyping(AppInfoWithSubtyping previous) {
     super(previous);
     missingClasses.addAll(previous.missingClasses);
     subtypeMap.putAll(previous.subtypeMap);
+    assert app instanceof DirectMappedDexApplication;
   }
 
   protected AppInfoWithSubtyping(AppInfoWithSubtyping previous, GraphLense lense) {
     super(previous, lense);
     // Recompute subtype map if we have modified the graph.
-    populateSubtypeMap(previous.app.getFullClassMap(), dexItemFactory);
+    populateSubtypeMap(previous.getDirectApplication(), dexItemFactory);
+  }
+
+  private DirectMappedDexApplication getDirectApplication() {
+    // TODO(herhut): Remove need for cast.
+    return (DirectMappedDexApplication) app;
+  }
+
+  public Iterable<DexLibraryClass> libraryClasses() {
+    return getDirectApplication().libraryClasses();
   }
 
   public Set<DexType> getMissingClasses() {
@@ -84,17 +96,17 @@
     }
   }
 
-  private void populateSubtypeMap(Map<DexType, DexClass> classes, DexItemFactory dexItemFactory) {
+  private void populateSubtypeMap(DirectMappedDexApplication app, DexItemFactory dexItemFactory) {
     dexItemFactory.clearSubtypeInformation();
     dexItemFactory.objectType.tagAsSubtypeRoot();
     Hashtable<DexType, Set<DexType>> map = new Hashtable<>();
-    for (Map.Entry<DexType, DexClass> entry : classes.entrySet()) {
-      populateAllSuperTypes(map, entry.getKey(), entry.getValue(), classes::get);
+    for (DexClass clazz : Iterables.<DexClass>concat(app.classes(), app.libraryClasses())) {
+      populateAllSuperTypes(map, clazz.type, clazz, app::definitionFor);
     }
     for (Map.Entry<DexType, Set<DexType>> entry : map.entrySet()) {
       subtypeMap.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
     }
-    assert DexType.validateLevelsAreCorrect(classes::get, dexItemFactory);
+    assert DexType.validateLevelsAreCorrect(app::definitionFor, dexItemFactory);
   }
 
   // For mapping invoke virtual instruction to target methods.
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 294b031..b79f30e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -6,10 +6,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.utils.ClasspathClassCollection;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.LibraryClassCollection;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
@@ -26,17 +25,13 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
-public class DexApplication {
+public abstract class DexApplication {
 
   // Maps type into class, may be used concurrently.
-  private ProgramClassCollection programClasses;
-  private ClasspathClassCollection classpathClasses;
-  private LibraryClassCollection libraryClasses;
+  ProgramClassCollection programClasses;
 
   public final ImmutableSet<DexType> mainDexList;
   public final byte[] deadCode;
@@ -50,12 +45,12 @@
   // Information on the lexicographically largest string referenced from code.
   public final DexString highestSortingString;
 
-  /** Constructor should only be invoked by the DexApplication.Builder. */
-  private DexApplication(
+  /**
+   * Constructor should only be invoked by the DexApplication.Builder.
+   */
+  DexApplication(
       ClassNameMapper proguardMap,
       ProgramClassCollection programClasses,
-      ClasspathClassCollection classpathClasses,
-      LibraryClassCollection libraryClasses,
       ImmutableSet<DexType> mainDexList,
       byte[] deadCode,
       DexItemFactory dexItemFactory,
@@ -64,8 +59,6 @@
     assert programClasses != null;
     this.proguardMap = proguardMap;
     this.programClasses = programClasses;
-    this.classpathClasses = classpathClasses;
-    this.libraryClasses = libraryClasses;
     this.mainDexList = mainDexList;
     this.deadCode = deadCode;
     this.dexItemFactory = dexItemFactory;
@@ -73,10 +66,7 @@
     this.timing = timing;
   }
 
-  /** Force load all classes and return type -> class map containing all the classes */
-  public Map<DexType, DexClass> getFullClassMap() {
-    return forceLoadAllClasses();
-  }
+  public abstract Builder<?> builder();
 
   // Reorder classes randomly. Note that the order of classes in program or library
   // class collections should not matter for compilation of valid code and when running
@@ -96,61 +86,14 @@
     return classes;
   }
 
-  public List<DexLibraryClass> libraryClasses() {
-    assert classpathClasses == null : "Operation is not supported.";
-    Map<DexType, DexClass> classMap = forceLoadAllClasses();
-    List<DexLibraryClass> classes = new ArrayList<>();
-    for (DexClass clazz : classMap.values()) {
-      if (clazz.isLibraryClass()) {
-        classes.add(clazz.asLibraryClass());
-      }
-    }
-    assert reorderClasses(classes);
-    return classes;
-  }
-
-  private Map<DexType, DexClass> forceLoadAllClasses() {
-    Map<DexType, DexClass> loaded = new IdentityHashMap<>();
-
-    // program classes are supposed to be loaded, but force-loading them is no-op.
-    programClasses.forceLoad(type -> true);
-    programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
-
-    if (classpathClasses != null) {
-      classpathClasses.forceLoad(type -> !loaded.containsKey(type));
-      classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
-    }
-
-    if (libraryClasses != null) {
-      libraryClasses.forceLoad(type -> !loaded.containsKey(type));
-      libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
-    }
-
-    return loaded;
-  }
-
-  public DexClass definitionFor(DexType type) {
-    if (type == null) {
-      return null;
-    }
-    DexClass clazz = programClasses.get(type);
-    if (clazz == null && classpathClasses != null) {
-      clazz = classpathClasses.get(type);
-    }
-    if (clazz == null && libraryClasses != null) {
-      clazz = libraryClasses.get(type);
-    }
-    return clazz;
-  }
+  public abstract DexClass definitionFor(DexType type);
 
   public DexProgramClass programDefinitionFor(DexType type) {
     DexClass clazz = programClasses.get(type);
     return clazz == null ? null : clazz.asProgramClass();
   }
 
-  public String toString() {
-    return "Application (" + programClasses + "; " + classpathClasses + "; " + libraryClasses + ")";
-  }
+  public abstract String toString();
 
   public ClassNameMapper getProguardMap() {
     return proguardMap;
@@ -208,7 +151,9 @@
     }
   }
 
-  /** Return smali source for the application code. */
+  /**
+   * Return smali source for the application code.
+   */
   public String smali(InternalOptions options) {
     ByteArrayOutputStream os = new ByteArrayOutputStream();
     PrintStream ps = new PrintStream(os);
@@ -296,7 +241,7 @@
     }
   }
 
-  public static class Builder {
+  public abstract static class Builder<T extends Builder> {
     // We handle program class collection separately from classpath
     // and library class collections. Since while we assume program
     // class collection should always be fully loaded and thus fully
@@ -304,17 +249,15 @@
     // new or removing existing classes), classpath and library
     // collections will be considered monolithic collections.
 
-    private final List<DexProgramClass> programClasses;
-    private ClasspathClassCollection classpathClasses;
-    private LibraryClassCollection libraryClasses;
+    final List<DexProgramClass> programClasses;
 
     public final DexItemFactory dexItemFactory;
     ClassNameMapper proguardMap;
-    private final Timing timing;
+    final Timing timing;
 
     DexString highestSortingString;
-    private byte[] deadCode;
-    private final Set<DexType> mainDexList = Sets.newIdentityHashSet();
+    byte[] deadCode;
+    final Set<DexType> mainDexList = Sets.newIdentityHashSet();
     private final Collection<DexProgramClass> synthesizedClasses;
 
     public Builder(DexItemFactory dexItemFactory, Timing timing) {
@@ -322,16 +265,14 @@
       this.dexItemFactory = dexItemFactory;
       this.timing = timing;
       this.deadCode = null;
-      this.classpathClasses = null;
-      this.libraryClasses = null;
       this.synthesizedClasses = new ArrayList<>();
     }
 
+    abstract T self();
+
     public Builder(DexApplication application) {
       programClasses = application.programClasses.getAllClasses();
-      classpathClasses = application.classpathClasses;
-      libraryClasses = application.libraryClasses;
-      proguardMap = application.proguardMap;
+      proguardMap = application.getProguardMap();
       timing = application.timing;
       highestSortingString = application.highestSortingString;
       dexItemFactory = application.dexItemFactory;
@@ -340,53 +281,43 @@
       synthesizedClasses = new ArrayList<>();
     }
 
-    public synchronized Builder setProguardMap(ClassNameMapper proguardMap) {
+    public synchronized T setProguardMap(ClassNameMapper proguardMap) {
       assert this.proguardMap == null;
       this.proguardMap = proguardMap;
-      return this;
+      return self();
     }
 
-    public synchronized Builder replaceProgramClasses(List<DexProgramClass> newProgramClasses) {
+    public synchronized T replaceProgramClasses(List<DexProgramClass> newProgramClasses) {
       assert newProgramClasses != null;
       this.programClasses.clear();
       this.programClasses.addAll(newProgramClasses);
-      return this;
+      return self();
     }
 
-    public Builder appendDeadCode(byte[] deadCodeAtAnotherRound) {
+    public T appendDeadCode(byte[] deadCodeAtAnotherRound) {
       if (deadCodeAtAnotherRound == null) {
-        return this;
+        return self();
       }
       if (this.deadCode == null) {
         this.deadCode = deadCodeAtAnotherRound;
-        return this;
+        return self();
       }
       // Concatenate existing byte[] and the given byte[].
       this.deadCode = Bytes.concat(this.deadCode, deadCodeAtAnotherRound);
-      return this;
+      return self();
     }
 
-    public synchronized Builder setHighestSortingString(DexString value) {
+    public synchronized T setHighestSortingString(DexString value) {
       highestSortingString = value;
-      return this;
+      return self();
     }
 
-    public synchronized Builder addProgramClass(DexProgramClass clazz) {
+    public synchronized T addProgramClass(DexProgramClass clazz) {
       programClasses.add(clazz);
-      return this;
+      return self();
     }
 
-    public Builder setClasspathClassCollection(ClasspathClassCollection classes) {
-      this.classpathClasses = classes;
-      return this;
-    }
-
-    public Builder setLibraryClassCollection(LibraryClassCollection classes) {
-      this.libraryClasses = classes;
-      return this;
-    }
-
-    public synchronized Builder addSynthesizedClass(
+    public synchronized T addSynthesizedClass(
         DexProgramClass synthesizedClass, boolean addToMainDexList) {
       assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
       addProgramClass(synthesizedClass);
@@ -394,7 +325,7 @@
       if (addToMainDexList && !mainDexList.isEmpty()) {
         mainDexList.add(synthesizedClass.type);
       }
-      return this;
+      return self();
     }
 
     public Collection<DexProgramClass> getProgramClasses() {
@@ -414,17 +345,17 @@
       return this;
     }
 
-    public DexApplication build() {
-      return new DexApplication(
-          proguardMap,
-          ProgramClassCollection.create(programClasses),
-          classpathClasses,
-          libraryClasses,
-          ImmutableSet.copyOf(mainDexList),
-          deadCode,
-          dexItemFactory,
-          highestSortingString,
-          timing);
-    }
+    public abstract DexApplication build();
   }
+
+  public static LazyLoadedDexApplication.Builder builder(DexItemFactory factory, Timing timing) {
+    return new LazyLoadedDexApplication.Builder(factory, timing);
+  }
+
+  public DirectMappedDexApplication asDirect() {
+    throw new Unreachable("Cannot use a LazyDexApplication where a DirectDexApplication is"
+        + " expected.");
+  }
+
+  public abstract DirectMappedDexApplication toDirect();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
new file mode 100644
index 0000000..e2583c3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ProgramClassCollection;
+import com.android.tools.r8.utils.Timing;
+
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class DirectMappedDexApplication extends DexApplication {
+
+  private final ImmutableMap<DexType, DexLibraryClass> libraryClasses;
+
+  private DirectMappedDexApplication(ClassNameMapper proguardMap,
+      ProgramClassCollection programClasses,
+      ImmutableMap<DexType, DexLibraryClass> libraryClasses,
+      ImmutableSet<DexType> mainDexList, byte[] deadCode,
+      DexItemFactory dexItemFactory, DexString highestSortingString,
+      Timing timing) {
+    super(proguardMap, programClasses, mainDexList, deadCode,
+        dexItemFactory, highestSortingString, timing);
+    this.libraryClasses = libraryClasses;
+  }
+
+  public Collection<DexLibraryClass> libraryClasses() {
+    return libraryClasses.values();
+  }
+
+  @Override
+  public DexClass definitionFor(DexType type) {
+    DexClass result = programClasses.get(type);
+    if (result == null) {
+      result = libraryClasses.get(type);
+    }
+    return result;
+  }
+
+  @Override
+  public Builder builder() {
+    return new Builder(this);
+  }
+
+  @Override
+  public DirectMappedDexApplication toDirect() {
+    return this;
+  }
+
+  @Override
+  public DirectMappedDexApplication asDirect() {
+    return this;
+  }
+
+  public String toString() {
+    return "DexApplication (direct)";
+  }
+
+  public static class Builder extends DexApplication.Builder<Builder> {
+
+    private Map<DexType, DexLibraryClass> libraryClasses = new IdentityHashMap<>();
+
+    Builder(LazyLoadedDexApplication application) {
+      super(application);
+      // As a side-effect, this will force-load all classes.
+      Map<DexType, DexClass> allClasses = application.getFullClassMap();
+      Iterables.filter(allClasses.values(), DexLibraryClass.class)
+          .forEach(k -> libraryClasses.put(k.type, k));
+
+    }
+
+    private Builder(DirectMappedDexApplication application) {
+      super(application);
+      this.libraryClasses.putAll(application.libraryClasses);
+    }
+
+    @Override
+    Builder self() {
+      return this;
+    }
+
+    @Override
+    public DexApplication build() {
+      return new DirectMappedDexApplication(proguardMap,
+          ProgramClassCollection.create(programClasses),
+          ImmutableMap.copyOf(libraryClasses), ImmutableSet.copyOf(mainDexList), deadCode,
+          dexItemFactory, highestSortingString, timing);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a90373e..5718ec2 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -5,7 +5,7 @@
 
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
-import static org.objectweb.asm.Opcodes.ASM6;
+import static org.objectweb.asm.Opcodes.ASM5;
 
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.dex.Constants;
@@ -121,7 +121,7 @@
         byte[] classCache,
         JarApplicationReader application,
         Consumer<DexClass> classConsumer) {
-      super(ASM6);
+      super(ASM5);
       this.file = file;
       this.classKind = classKind;
       this.classConsumer = classConsumer;
@@ -299,7 +299,7 @@
 
     public CreateFieldVisitor(CreateDexClassVisitor parent,
         int access, String name, String desc, String signature, Object value) {
-      super(ASM6);
+      super(ASM5);
       this.parent = parent;
       this.access = access;
       this.name = name;
@@ -401,7 +401,7 @@
 
     public CreateMethodVisitor(int access, String name, String desc, String signature,
         String[] exceptions, CreateDexClassVisitor parent) {
-      super(ASM6);
+      super(ASM5);
       this.access = access;
       this.name = name;
       this.desc = desc;
@@ -588,7 +588,7 @@
 
     public CreateAnnotationVisitor(
         JarApplicationReader application, BiConsumer<List<DexString>, List<DexValue>> onVisitEnd) {
-      super(ASM6);
+      super(ASM5);
       this.application = application;
       this.onVisitEnd = onVisitEnd;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 39c580e..1dc5964 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -165,7 +165,7 @@
     private final JarApplicationReader application;
 
     public SecondVisitor(ReparseContext context, JarApplicationReader application) {
-      super(Opcodes.ASM6);
+      super(Opcodes.ASM5);
       this.context = context;
       this.application = application;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
new file mode 100644
index 0000000..bd7b266
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ClasspathClassCollection;
+import com.android.tools.r8.utils.LibraryClassCollection;
+import com.android.tools.r8.utils.ProgramClassCollection;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class LazyLoadedDexApplication extends DexApplication {
+
+  private ClasspathClassCollection classpathClasses;
+  private LibraryClassCollection libraryClasses;
+
+  /**
+   * Constructor should only be invoked by the DexApplication.Builder.
+   */
+  private LazyLoadedDexApplication(ClassNameMapper proguardMap,
+      ProgramClassCollection programClasses,
+      ClasspathClassCollection classpathClasses,
+      LibraryClassCollection libraryClasses,
+      ImmutableSet<DexType> mainDexList, byte[] deadCode,
+      DexItemFactory dexItemFactory, DexString highestSortingString,
+      Timing timing) {
+    super(proguardMap, programClasses, mainDexList, deadCode,
+        dexItemFactory, highestSortingString, timing);
+    this.classpathClasses = classpathClasses;
+    this.libraryClasses = libraryClasses;
+  }
+
+  @Override
+  public DexClass definitionFor(DexType type) {
+    if (type == null) {
+      return null;
+    }
+    DexClass clazz = programClasses.get(type);
+    if (clazz == null && classpathClasses != null) {
+      clazz = classpathClasses.get(type);
+    }
+    if (clazz == null && libraryClasses != null) {
+      clazz = libraryClasses.get(type);
+    }
+    return clazz;
+  }
+
+  private Map<DexType, DexClass> forceLoadAllClasses() {
+    Map<DexType, DexClass> loaded = new IdentityHashMap<>();
+
+    // Program classes are supposed to be loaded, but force-loading them is no-op.
+    programClasses.forceLoad(type -> true);
+    programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+
+    if (classpathClasses != null) {
+      classpathClasses.forceLoad(type -> !loaded.containsKey(type));
+      classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+    }
+
+    if (libraryClasses != null) {
+      libraryClasses.forceLoad(type -> !loaded.containsKey(type));
+      libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+    }
+
+    return loaded;
+  }
+
+  /**
+   * Force load all classes and return type -> class map containing all the classes.
+   */
+  public Map<DexType, DexClass> getFullClassMap() {
+    return forceLoadAllClasses();
+  }
+
+  public static class Builder extends DexApplication.Builder<Builder> {
+
+    private ClasspathClassCollection classpathClasses;
+    private LibraryClassCollection libraryClasses;
+
+    Builder(DexItemFactory dexItemFactory, Timing timing) {
+      super(dexItemFactory, timing);
+      this.classpathClasses = null;
+      this.libraryClasses = null;
+    }
+
+    private Builder(LazyLoadedDexApplication application) {
+      super(application);
+      this.classpathClasses = application.classpathClasses;
+      this.libraryClasses = application.libraryClasses;
+    }
+
+    Builder self() {
+      return this;
+    }
+
+    public Builder setClasspathClassCollection(ClasspathClassCollection classes) {
+      this.classpathClasses = classes;
+      return this;
+    }
+
+    public Builder setLibraryClassCollection(LibraryClassCollection classes) {
+      this.libraryClasses = classes;
+      return this;
+    }
+
+    public LazyLoadedDexApplication build() {
+      return new LazyLoadedDexApplication(proguardMap,
+          ProgramClassCollection.create(programClasses),
+          classpathClasses, libraryClasses, ImmutableSet.copyOf(mainDexList), deadCode,
+          dexItemFactory, highestSortingString, timing);
+    }
+  }
+
+  @Override
+  public Builder builder() {
+    return new Builder(this);
+  }
+
+  @Override
+  public DirectMappedDexApplication toDirect() {
+    return new DirectMappedDexApplication.Builder(this).build().asDirect();
+  }
+
+  public String toString() {
+    return "Application (" + programClasses + "; " + classpathClasses + "; " + libraryClasses
+        + ")";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c110f2f..801c684 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -57,6 +57,15 @@
     }
   }
 
+  public boolean hasInValueWithLocalInfo() {
+    for (Value inValue : inValues()) {
+      if (inValue.getLocalInfo() != null) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public Value outValue() {
     return outValue;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 716db5e..2ba4421 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -226,7 +226,7 @@
     convertClassesToDex(application.classes(), executor);
 
     // Build a new application with jumbo string info,
-    Builder builder = new Builder(application);
+    Builder builder = application.builder();
     builder.setHighestSortingString(highestSortingString);
 
     synthesizeLambdaClasses(builder);
@@ -245,7 +245,7 @@
     return builder.build();
   }
 
-  private void updateMainDexListWithSynthesizedClassMap(Builder builder) {
+  private void updateMainDexListWithSynthesizedClassMap(Builder<?> builder) {
     Set<DexType> inputMainDexList = builder.getMainDexList();
     if (!inputMainDexList.isEmpty()) {
       Map<DexType, DexProgramClass> programClasses = builder.getProgramClasses().stream()
@@ -264,14 +264,14 @@
     }
   }
 
-  private void clearSynthesizedClassMapping(Builder builder) {
+  private void clearSynthesizedClassMapping(Builder<?> builder) {
     for (DexProgramClass programClass : builder.getProgramClasses()) {
       programClass.annotations =
           programClass.annotations.getWithout(builder.dexItemFactory.annotationSynthesizedClassMap);
     }
   }
 
-  private void updateSynthesizedClassMapping(Builder builder) {
+  private void updateSynthesizedClassMapping(Builder<?> builder) {
     ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
         ArrayListMultimap.create();
     for (DexProgramClass synthesized : builder.getSynthesizedClasses()) {
@@ -361,7 +361,7 @@
     }
 
     // Build a new application with jumbo string info.
-    Builder builder = new Builder(application);
+    Builder builder = application.builder();
     builder.setHighestSortingString(highestSortingString);
 
     // Second inlining pass for dealing with double inline callers.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index edf8b60..99417c3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -254,7 +254,7 @@
   }
 
   private Map<DexProgramClass, DexProgramClass> processInterfaces(
-      Builder builder, Flavor flavour) {
+      Builder<?> builder, Flavor flavour) {
     InterfaceProcessor processor = new InterfaceProcessor(this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, true)) {
@@ -264,7 +264,7 @@
     return processor.companionClasses;
   }
 
-  private Set<DexEncodedMethod> processClasses(Builder builder, Flavor flavour) {
+  private Set<DexEncodedMethod> processClasses(Builder<?> builder, Flavor flavour) {
     ClassProcessor processor = new ClassProcessor(this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, false)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 454d6cd..e87b29b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1646,7 +1646,7 @@
               }
             }
           }
-          if (!eliminated) {
+          if (!eliminated && !instruction.hasInValueWithLocalInfo()) {
             instructionToValue.put(equivalence.wrap(instruction), instruction.outValue());
           }
         }
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 2d38bf0..b8cbaa9 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jar;
 
-import static org.objectweb.asm.Opcodes.ASM6;
+import static org.objectweb.asm.Opcodes.ASM5;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -24,7 +24,7 @@
 
   public JarRegisterEffectsVisitor(DexType clazz, UseRegistry registry,
       JarApplicationReader application) {
-    super(ASM6);
+    super(ASM5);
     this.clazz = clazz;
     this.registry = registry;
     this.application = application;
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 74cec83..d4ff512 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.base.Equivalence.Wrapper;
@@ -40,7 +40,7 @@
 
 public class RootSetBuilder {
 
-  private DexApplication application;
+  private DirectMappedDexApplication application;
   private final AppInfo appInfo;
   private final List<ProguardConfigurationRule> rules;
   private final Map<DexItem, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
@@ -59,7 +59,7 @@
 
   public RootSetBuilder(DexApplication application, AppInfo appInfo,
       List<ProguardConfigurationRule> rules) {
-    this.application = application;
+    this.application = application.asDirect();
     this.appInfo = appInfo;
     this.rules = rules;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 3decec1..f3b712f 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -56,7 +56,7 @@
   }
 
   private DexApplication.Builder removeUnused(DexApplication application) {
-    return new DexApplication.Builder(application)
+    return application.builder()
         .replaceProgramClasses(getNewProgramClasses(application.classes()));
   }
 
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 1221234..205ae64 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -338,6 +338,14 @@
     return result;
   }
 
+  public static int localTriggeringCSE() {
+    int a = 1;
+    int b = 3;
+    int c = a + b;
+    int d = a + b;
+    return c + d;
+  }
+
   public static void main(String[] args) {
     noLocals();
     unusedLocals();
@@ -360,5 +368,6 @@
     regression65066975(false);
     System.out.println(localConstant(true));
     System.out.println(localConstantBis(true));
+    System.out.println(localTriggeringCSE());
   }
 }
diff --git a/src/test/examplesAndroidO/invokecustom/TestGenerator.java b/src/test/examplesAndroidO/invokecustom/TestGenerator.java
index 574fe14..5c550e9 100644
--- a/src/test/examplesAndroidO/invokecustom/TestGenerator.java
+++ b/src/test/examplesAndroidO/invokecustom/TestGenerator.java
@@ -39,7 +39,7 @@
     ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
     cr.accept(
-        new ClassVisitor(Opcodes.ASM6, cw) {
+        new ClassVisitor(Opcodes.ASM5, cw) {
           @Override
           public void visitEnd() {
             generateMethodTest1(cw);
diff --git a/src/test/examplesAndroidO/invokecustom2/TestGenerator.java b/src/test/examplesAndroidO/invokecustom2/TestGenerator.java
index ab77d32..48cfedb 100644
--- a/src/test/examplesAndroidO/invokecustom2/TestGenerator.java
+++ b/src/test/examplesAndroidO/invokecustom2/TestGenerator.java
@@ -40,7 +40,7 @@
     ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
     cr.accept(
-        new ClassVisitor(Opcodes.ASM6, cw) {
+        new ClassVisitor(Opcodes.ASM5, cw) {
           @Override
           public void visitEnd() {
             generateMethodTest1(cw);
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 61a049e..082b5ee 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -8,9 +8,9 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -33,8 +33,10 @@
     AndroidApp input = AndroidApp.fromProgramFiles(SMALI_DIR.resolve(name).resolve(name + ".dex"));
     ExecutorService executorService = Executors.newSingleThreadExecutor();
     Timing timing = new Timing("R8UnreachableCodeTest");
-    DexApplication application =
-        new ApplicationReader(input, new InternalOptions(), timing).read(executorService);
+    DirectMappedDexApplication application =
+        new ApplicationReader(input, new InternalOptions(), timing)
+            .read(executorService)
+            .toDirect();
     IRConverter converter =
         new IRConverter(application, new AppInfoWithSubtyping(application), new InternalOptions());
     converter.optimize();
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 1d48748..b751766 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -459,7 +459,8 @@
         AndroidApp.fromProgramFiles(ListUtils.map(fileNames, Paths::get)),
         new InternalOptions(),
         new Timing("ToolHelper buildApplication"))
-        .read();
+        .read()
+        .toDirect();
   }
 
   public static ProguardConfiguration loadProguardConfiguration(
@@ -549,10 +550,10 @@
 
   public static DexApplication optimizeWithR8(
       DexApplication application,
-      AppInfoWithSubtyping appInfo,
       InternalOptions options)
       throws CompilationException, ExecutionException, IOException {
-    return R8.optimize(application, appInfo, options);
+    application = application.toDirect();
+    return R8.optimize(application, new AppInfoWithSubtyping(application), options);
   }
 
   public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException {
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 9b0a888..177efbe 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -608,4 +608,44 @@
         checkNoLocal("result2"),
         run());
   }
+
+  @Test
+  public void testLocalTriggeringCSE() throws Throwable {
+    final String className = "Locals";
+    final String methodName = "localTriggeringCSE";
+    runDebugTest(className,
+        breakpoint(className, methodName),
+        run(),
+        checkLine(SOURCE_FILE, 342),
+        checkNoLocal("a"),
+        checkNoLocal("b"),
+        checkNoLocal("c"),
+        checkNoLocal("d"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 343),
+        checkLocal("a", Value.createInt(1)),
+        checkNoLocal("b"),
+        checkNoLocal("c"),
+        checkNoLocal("d"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 344),
+        checkLocal("a", Value.createInt(1)),
+        checkLocal("b", Value.createInt(3)),
+        checkNoLocal("c"),
+        checkNoLocal("d"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 345),
+        checkLocal("a", Value.createInt(1)),
+        checkLocal("b", Value.createInt(3)),
+        checkLocal("c", Value.createInt(4)),
+        checkNoLocal("d"),
+        setLocal("a", Value.createInt(2)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 346),
+        checkLocal("a", Value.createInt(2)),
+        checkLocal("b", Value.createInt(3)),
+        checkLocal("c", Value.createInt(4)),
+        checkLocal("d", Value.createInt(5)),
+        run());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index e5581a4..a08f274 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexDebugEvent;
@@ -24,7 +25,7 @@
   ObjectToOffsetMapping emptyObjectTObjectMapping() {
     return new ObjectToOffsetMapping(
         0,
-        new Builder(new DexItemFactory(), null).build(),
+        DexApplication.builder(new DexItemFactory(), null).build(),
         new DexProgramClass[] {},
         new DexProto[] {},
         new DexType[] {},
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 74e4e9b..30e02b5 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -9,10 +9,10 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -29,7 +29,7 @@
 
   static final String APP_DIR = "third_party/gmscore/v5/";
   private AndroidApp app;
-  private DexApplication program;
+  private DirectMappedDexApplication program;
   private AppInfoWithSubtyping appInfo;
 
   @Before
@@ -37,7 +37,8 @@
     app = AndroidApp.fromProgramDirectory(Paths.get(APP_DIR));
     ExecutorService executorService = Executors.newSingleThreadExecutor();
     Timing timing = new Timing("ReadGMSCore");
-    program = new ApplicationReader(app, new InternalOptions(), timing).read(executorService);
+    program = new ApplicationReader(app, new InternalOptions(), timing)
+        .read(executorService).toDirect();
     appInfo = new AppInfoWithSubtyping(program);
   }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 9da8e0e..8cf6e9b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
@@ -141,7 +140,7 @@
 
   protected static DexApplication process(DexApplication app, InternalOptions options)
       throws IOException, CompilationException, ExecutionException {
-    return ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), options);
+    return ToolHelper.optimizeWithR8(app, options);
   }
 
   protected DexApplication buildApplication(JasminBuilder builder, InternalOptions options) {
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index ab88a99..601ca23 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.AnnotationSubject;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.io.Closer;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -157,7 +158,7 @@
       throws IOException, ExecutionException {
     ClassReader classReader = new ClassReader(new FileInputStream(inputPath.toFile()));
     ReadSourceDebugExtensionAttribute sourceDebugExtensionReader =
-        new ReadSourceDebugExtensionAttribute(Opcodes.ASM6, null);
+        new ReadSourceDebugExtensionAttribute(Opcodes.ASM5, null);
     classReader.accept(sourceDebugExtensionReader, 0);
 
     DexInspector dexInspector =
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 0acd445..b0a683a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.IRCode;
@@ -440,7 +441,7 @@
       }
       builder.append("]");
       fail("Class " + clazz + " found in main dex, " +
-          "only expected explicit main dex classes " + builder +" in main dex file");
+          "only expected explicit main dex classes " + builder + " in main dex file");
     }
   }
 
@@ -551,7 +552,7 @@
     options.minApiLevel = minApi;
     options.intermediate = intermediate;
     DexItemFactory factory = options.itemFactory;
-    DexApplication.Builder builder = new DexApplication.Builder(factory, timing);
+    DexApplication.Builder builder = DexApplication.builder(factory, timing);
     for (String clazz : classes) {
       DexString desc = factory.createString(DescriptorUtils.javaTypeToDescriptor(clazz));
       DexType type = factory.createType(desc);
@@ -591,7 +592,7 @@
               directMethods,
               DexEncodedMethod.EMPTY_ARRAY));
     }
-    DexApplication application = builder.build();
+    DirectMappedDexApplication application = builder.build().toDirect();
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
     ApplicationWriter writer = new ApplicationWriter(
         application, appInfo, options, null, null, NamingLens.getIdentityLens(), null);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index c64e39c..1aa9be7 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -901,8 +901,7 @@
     // Process the application several times. Each time will outline the previous outline.
     for (int i = 0; i < count; i++) {
       // Build a new application with the Outliner class.
-      DexApplication.Builder appBuilder =
-          new DexApplication.Builder(processedApplication);
+      DexApplication.Builder appBuilder = processedApplication.builder();
       originalApplication = appBuilder.build();
       processedApplication = processApplication(originalApplication, options);
       assertEquals(i + 3, Iterables.size(processedApplication.classes()));
@@ -912,8 +911,7 @@
     options.outline.threshold = 2;
     for (int i = 0; i < count; i++) {
       // Build a new application with the Outliner class.
-      DexApplication.Builder appBuilder =
-          new DexApplication.Builder(processedApplication);
+      DexApplication.Builder appBuilder = processedApplication.builder();
       originalApplication = appBuilder.build();
       processedApplication = processApplication(originalApplication, options);
       assertEquals(count - 1 + 3, Iterables.size(processedApplication.classes()));
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 42e289a..c580f74 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -463,7 +462,7 @@
 
   protected DexApplication processApplication(DexApplication application, InternalOptions options) {
     try {
-      return ToolHelper.optimizeWithR8(application, new AppInfoWithSubtyping(application), options);
+      return ToolHelper.optimizeWithR8(application, options);
     } catch (IOException | CompilationException | ExecutionException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index 77366fb..3fc329c 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.PackedSwitch;
 import com.android.tools.r8.code.SparseSwitch;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -296,7 +295,7 @@
         "    return");
 
     DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
+    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
 
     MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
     DexEncodedMethod method = getMethod(app, signature);
@@ -354,7 +353,7 @@
         "    return");
 
     DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
+    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
 
     MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
     DexEncodedMethod method = getMethod(app, signature);
@@ -432,7 +431,7 @@
         "    return");
 
     DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
+    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
 
     MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
     DexEncodedMethod method = getMethod(app, signature);