Rewrite non-rebound field references during class merging

Change-Id: I2577c6c5e600e75964eb6f974057efd35290752f
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 0f43472..5e8e3a6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -484,7 +484,7 @@
       }
 
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      appView.setGraphLens(new MemberRebindingAnalysis(appViewWithLiveness).run());
+      appView.setGraphLens(new MemberRebindingAnalysis(appViewWithLiveness).run(executorService));
       appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView);
 
       if (options.shouldDesugarNests()) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index a7bd6c1..877f2ba 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
+import com.android.tools.r8.optimize.MemberRebindingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -598,17 +599,26 @@
     assert application.verifyWithLens(lens);
 
     // The application has already been rewritten with the given applied lens. Therefore, we
-    // temporarily replace that lens with the identity lens to avoid the overhead of traversing
-    // the entire lens chain upon each lookup during the rewriting.
-    NonIdentityGraphLens temporaryRootLens = lens;
-    while (temporaryRootLens.getPrevious() != appliedLens) {
-      GraphLens previousLens = temporaryRootLens.getPrevious();
+    // temporarily replace that lens with a lens that does not have any rewritings to avoid the
+    // overhead of traversing the entire lens chain upon each lookup during the rewriting.
+    NonIdentityGraphLens firstUnappliedLens = lens;
+    while (firstUnappliedLens.getPrevious() != appliedLens) {
+      GraphLens previousLens = firstUnappliedLens.getPrevious();
       assert previousLens.isNonIdentityLens();
-      temporaryRootLens = previousLens.asNonIdentityLens();
+      firstUnappliedLens = previousLens.asNonIdentityLens();
     }
 
-    temporaryRootLens.withAlternativeParentLens(
-        GraphLens.getIdentityLens(),
+    // Insert a member rebinding lens above the first unapplied lens.
+    MemberRebindingLens appliedMemberRebindingLens =
+        firstUnappliedLens.findPrevious(GraphLens::isMemberRebindingLens);
+    GraphLens newMemberRebindingLens =
+        appliedMemberRebindingLens != null
+            ? appliedMemberRebindingLens.toRewrittenFieldRebindingLens(
+                appView.dexItemFactory(), appliedLens)
+            : GraphLens.getIdentityLens();
+
+    firstUnappliedLens.withAlternativeParentLens(
+        newMemberRebindingLens,
         () -> {
           appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
           appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index ecb09ca..977d06d 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -21,6 +21,10 @@
     return null;
   }
 
+  public DexField getResolvedFieldReference() {
+    return null;
+  }
+
   public boolean isSuccessfulResolution() {
     return false;
   }
@@ -78,6 +82,11 @@
     }
 
     @Override
+    public DexField getResolvedFieldReference() {
+      return resolvedField.toReference();
+    }
+
+    @Override
     public DexEncodedField getResolvedMember() {
       return resolvedField;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index f2de6db..b3f5d14 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -28,6 +28,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
 
 /**
  * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until
@@ -348,15 +349,21 @@
   public DexField lookupField(DexField field) {
     // Lookup the field using the graph lens and return the (non-rebound) reference from the lookup
     // result.
-    return internalLookupField(field, FieldLookupResult::getReference);
+    return lookupFieldResult(field).getReference();
   }
 
-  protected abstract DexField internalLookupField(
+  /** Lookup a rebound or non-rebound field reference using the current graph lens. */
+  public FieldLookupResult lookupFieldResult(DexField field) {
+    // Lookup the field using the graph lens and return the lookup result.
+    return internalLookupField(field, x -> x);
+  }
+
+  protected abstract FieldLookupResult internalLookupField(
       DexField reference, LookupFieldContinuation continuation);
 
   interface LookupFieldContinuation {
 
-    DexField lookupField(FieldLookupResult previous);
+    FieldLookupResult lookupField(FieldLookupResult previous);
   }
 
   public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
@@ -408,6 +415,10 @@
 
   public abstract boolean isIdentityLens();
 
+  public boolean isMemberRebindingLens() {
+    return false;
+  }
+
   public abstract boolean isNonIdentityLens();
 
   public NonIdentityGraphLens asNonIdentityLens() {
@@ -616,6 +627,19 @@
       return previousLens;
     }
 
+    @SuppressWarnings("unchecked")
+    public final <T extends GraphLens> T findPrevious(Predicate<NonIdentityGraphLens> predicate) {
+      GraphLens current = getPrevious();
+      while (current.isNonIdentityLens()) {
+        NonIdentityGraphLens nonIdentityGraphLens = current.asNonIdentityLens();
+        if (predicate.test(nonIdentityGraphLens)) {
+          return (T) nonIdentityGraphLens;
+        }
+        current = nonIdentityGraphLens.getPrevious();
+      }
+      return null;
+    }
+
     public final void withAlternativeParentLens(GraphLens lens, Action action) {
       GraphLens oldParent = getPrevious();
       previousLens = lens;
@@ -663,7 +687,7 @@
     }
 
     @Override
-    protected DexField internalLookupField(
+    protected FieldLookupResult internalLookupField(
         DexField reference, LookupFieldContinuation continuation) {
       return previousLens.internalLookupField(
           reference, previous -> continuation.lookupField(internalDescribeLookupField(previous)));
@@ -772,7 +796,7 @@
     }
 
     @Override
-    protected DexField internalLookupField(
+    protected FieldLookupResult internalLookupField(
         DexField reference, LookupFieldContinuation continuation) {
       // Passes the field reference back to the next graph lens. The identity lens intentionally
       // does not set the rebound field reference, since it does not know what that is.
@@ -847,7 +871,7 @@
     }
 
     @Override
-    protected DexField internalLookupField(
+    protected FieldLookupResult internalLookupField(
         DexField reference, LookupFieldContinuation continuation) {
       return getIdentityLens().internalLookupField(reference, continuation);
     }
@@ -981,12 +1005,15 @@
       if (previous.hasReboundReference()) {
         // Rewrite the rebound reference and then "fixup" the non-rebound reference.
         DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
+        DexField rewrittenNonReboundReference =
+            previous.getReference() == previous.getReboundReference()
+                ? rewrittenReboundReference
+                : rewrittenReboundReference.withHolder(
+                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                    dexItemFactory);
         return FieldLookupResult.builder(this)
             .setReboundReference(rewrittenReboundReference)
-            .setReference(
-                rewrittenReboundReference.withHolder(
-                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
-                    dexItemFactory))
+            .setReference(rewrittenNonReboundReference)
             .build();
       } else {
         // TODO(b/168282032): We should always have the rebound reference, so this should become
@@ -1011,8 +1038,8 @@
                     internalDescribeLookupClassType(previous.getReference().getHolderType()),
                     dexItemFactory);
         return MethodLookupResult.builder(this)
-            .setReboundReference(rewrittenReboundReference)
             .setReference(rewrittenReference)
+            .setReboundReference(rewrittenReboundReference)
             .setPrototypeChanges(
                 internalDescribePrototypeChanges(
                     previous.getPrototypeChanges(), rewrittenReboundReference))
@@ -1090,18 +1117,18 @@
      *
      * <p>Handle methods moved from interface to class or class to interface.
      */
-    protected final Type mapVirtualInterfaceInvocationTypes(
+    public static Type mapVirtualInterfaceInvocationTypes(
         DexDefinitionSupplier definitions,
         DexMethod newMethod,
         DexMethod originalMethod,
         Type type) {
       if (type == Type.VIRTUAL || type == Type.INTERFACE) {
         // Get the invoke type of the actual definition.
-        DexClass newTargetClass = definitions.definitionFor(newMethod.holder);
+        DexClass newTargetClass = definitions.definitionFor(newMethod.getHolderType());
         if (newTargetClass == null) {
           return type;
         }
-        DexClass originalTargetClass = definitions.definitionFor(originalMethod.holder);
+        DexClass originalTargetClass = definitions.definitionFor(originalMethod.getHolderType());
         if (originalTargetClass != null
             && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
           // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 8b827d3..0512e69 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -32,16 +32,20 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.FieldLookupResult;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
@@ -374,7 +378,7 @@
             {
               InstanceGet instanceGet = current.asInstanceGet();
               DexField field = instanceGet.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupGetFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -399,7 +403,7 @@
             {
               InstancePut instancePut = current.asInstancePut();
               DexField field = instancePut.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupPutFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -421,7 +425,7 @@
             {
               StaticGet staticGet = current.asStaticGet();
               DexField field = staticGet.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupGetFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -445,7 +449,7 @@
             {
               StaticPut staticPut = current.asStaticPut();
               DexField field = staticPut.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupPutFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -581,6 +585,22 @@
     assert code.hasNoVerticallyMergedClasses(appView);
   }
 
+  private DexField rewriteFieldReference(
+      DexField reference, ProgramMethod context, GraphLens graphLens) {
+    FieldLookupResult lookup = graphLens.lookupFieldResult(reference);
+    if (lookup.hasReboundReference()) {
+      DexClass holder = appView.definitionFor(lookup.getReboundReference().getHolderType());
+      DexEncodedField definition = lookup.getReboundReference().lookupOnClass(holder);
+      if (definition != null) {
+        DexClassAndField field = DexClassAndField.create(holder, definition);
+        if (AccessControl.isMemberAccessible(field, holder, context, appView).isTrue()) {
+          return lookup.getReboundReference();
+        }
+      }
+    }
+    return lookup.getReference();
+  }
+
   // If the initialValue is a default value and its type is rewritten from a reference type to a
   // primitive type, then the default value type lattice needs to be changed.
   private Value rewriteValueIfDefault(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
index 7bc5e6b..7f99055 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
@@ -22,7 +22,6 @@
   private final DexType nestConstructorType;
   private final Map<DexField, DexMethod> getFieldMap;
   private final Map<DexField, DexMethod> putFieldMap;
-  private final AppView<?> appView;
 
   NestedPrivateMethodLens(
       AppView<?> appView,
@@ -43,7 +42,6 @@
     assert methodMap instanceof IdentityHashMap;
     assert getFieldMap instanceof IdentityHashMap;
     assert putFieldMap instanceof IdentityHashMap;
-    this.appView = appView;
     this.nestConstructorType = nestConstructorType;
     this.getFieldMap = getFieldMap;
     this.putFieldMap = putFieldMap;
diff --git a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
new file mode 100644
index 0000000..7e66078
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2018, 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.optimize;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This lens is used to populate the rebound field reference during lookup, such that both the
+ * non-rebound and rebound field references are available to all descendants of this lens.
+ *
+ * <p>TODO(b/157616970): All uses of this should be replaced by {@link MemberRebindingIdentityLens}.
+ */
+public class FieldRebindingIdentityLens extends NonIdentityGraphLens {
+
+  private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
+
+  private FieldRebindingIdentityLens(
+      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap,
+      DexItemFactory dexItemFactory,
+      GraphLens previousLens) {
+    super(dexItemFactory, previousLens);
+    this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public boolean hasCodeRewritings() {
+    return false;
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    assert previous.getReboundReference() == null;
+    return FieldLookupResult.builder(this)
+        .setReference(previous.getReference())
+        .setReboundReference(getReboundFieldReference(previous.getReference()))
+        .build();
+  }
+
+  private DexField getReboundFieldReference(DexField field) {
+    return nonReboundFieldReferenceToDefinitionMap.getOrDefault(field, field);
+  }
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(type);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    return getPrevious().getOriginalFieldSignature(field);
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    return getPrevious().getOriginalMethodSignature(method);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    return getPrevious().getRenamedFieldSignature(originalField);
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    return getPrevious().getRenamedMethodSignature(originalMethod, applied);
+  }
+
+  @Override
+  public final DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
+  }
+
+  @Override
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    return previous;
+  }
+
+  @Override
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return method;
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  public static class Builder {
+
+    private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
+        new IdentityHashMap<>();
+
+    private Builder() {}
+
+    void recordDefinitionForNonReboundFieldReference(
+        DexField nonReboundFieldReference, DexField reboundFieldReference) {
+      nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
+    }
+
+    FieldRebindingIdentityLens build(DexItemFactory dexItemFactory) {
+      // This intentionally does not return null when the map is empty. In this case there are no
+      // non-rebound field references, but the member rebinding lens is still needed to populate the
+      // rebound reference during field lookup.
+      return new FieldRebindingIdentityLens(
+          nonReboundFieldReferenceToDefinitionMap, dexItemFactory, GraphLens.getIdentityLens());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index cf6f791..9d4121c 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,28 +3,31 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
-import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BiForEachable;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
@@ -34,14 +37,14 @@
   private final GraphLens lens;
   private final InternalOptions options;
 
-  private final MemberRebindingLens.Builder builder;
+  private final MemberRebindingLens.Builder lensBuilder;
 
   public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
     assert appView.graphLens().isContextFreeForMethods();
     this.appView = appView;
     this.lens = appView.graphLens();
     this.options = appView.options();
-    this.builder = MemberRebindingLens.builder(appView);
+    this.lensBuilder = MemberRebindingLens.builder(appView);
   }
 
   private DexMethod validTargetFor(DexMethod target, DexMethod original) {
@@ -192,7 +195,8 @@
                       method, target, originalClass, targetClass, lookupTarget);
             }
           }
-          builder.map(method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
+          lensBuilder.map(
+              method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
         });
   }
 
@@ -310,59 +314,132 @@
     return null;
   }
 
-  private void computeFieldRebinding() {
+  private void recordNonReboundFieldAccesses(ExecutorService executorService)
+      throws ExecutionException {
+    assert verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(executorService);
     FieldAccessInfoCollection<?> fieldAccessInfoCollection =
         appView.appInfo().getFieldAccessInfoCollection();
-    fieldAccessInfoCollection.forEach(this::computeFieldRebindingForIndirectAccesses);
+    fieldAccessInfoCollection.forEach(lensBuilder::recordNonReboundFieldAccesses);
   }
 
-  private void computeFieldRebindingForIndirectAccesses(FieldAccessInfo fieldAccessInfo) {
-    fieldAccessInfo.forEachIndirectAccessWithContexts(
-        this::computeFieldRebindingForIndirectAccessWithContexts);
-  }
-
-  private void computeFieldRebindingForIndirectAccessWithContexts(
-      DexField field, ProgramMethodSet contexts) {
-    SuccessfulFieldResolutionResult resolutionResult =
-        appView.appInfo().resolveField(field).asSuccessfulResolution();
-    if (resolutionResult == null) {
-      return;
-    }
-
-    DexClassAndField resolvedField = resolutionResult.getResolutionPair();
-    if (resolvedField.getReference() == field) {
-      assert false;
-      return;
-    }
-
-    // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
-    // are not visible from the access context.
-    boolean accessibleInAllContexts = true;
-    for (ProgramMethod context : contexts) {
-      boolean inaccessibleInContext =
-          AccessControl.isMemberAccessible(
-                  resolvedField, resolutionResult.getResolvedHolder(), context, appView)
-              .isPossiblyFalse();
-      if (inaccessibleInContext) {
-        accessibleInAllContexts = false;
-        break;
-      }
-    }
-
-    if (accessibleInAllContexts) {
-      builder.map(
-          field,
-          lens.lookupField(
-              validTargetFor(resolvedField.getReference(), field, DexClass::lookupField)));
-    }
-  }
-
-  public GraphLens run() {
+  public MemberRebindingLens run(ExecutorService executorService) throws ExecutionException {
     AppInfoWithLiveness appInfo = appView.appInfo();
     computeMethodRebinding(appInfo.getMethodAccessInfoCollection());
-    computeFieldRebinding();
-    GraphLens lens = builder.build(this.lens);
+    recordNonReboundFieldAccesses(executorService);
     appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
-    return lens;
+    return lensBuilder.build();
+  }
+
+  private boolean verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(
+      ExecutorService executorService) throws ExecutionException {
+    Set<DexField> nonReboundFieldReferences = computeNonReboundFieldReferences(executorService);
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+        appView.appInfo().getFieldAccessInfoCollection();
+    fieldAccessInfoCollection.forEach(
+        info -> {
+          DexField reboundFieldReference = info.getField();
+          info.forEachIndirectAccess(
+              nonReboundFieldReference -> {
+                assert reboundFieldReference != nonReboundFieldReference;
+                assert reboundFieldReference
+                    == appView
+                        .appInfo()
+                        .resolveField(nonReboundFieldReference)
+                        .getResolvedFieldReference();
+                nonReboundFieldReferences.remove(nonReboundFieldReference);
+              });
+        });
+    assert nonReboundFieldReferences.isEmpty();
+    return true;
+  }
+
+  private Set<DexField> computeNonReboundFieldReferences(ExecutorService executorService)
+      throws ExecutionException {
+    Set<DexField> nonReboundFieldReferences = Sets.newConcurrentHashSet();
+    ThreadUtils.processItems(
+        appView.appInfo()::forEachMethod,
+        method -> {
+          if (method.getDefinition().hasCode()) {
+            method.registerCodeReferences(
+                new UseRegistry(appView.dexItemFactory()) {
+
+                  @Override
+                  public void registerInstanceFieldRead(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  @Override
+                  public void registerInstanceFieldWrite(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  @Override
+                  public void registerStaticFieldRead(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  @Override
+                  public void registerStaticFieldWrite(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  private void registerFieldReference(DexField field) {
+                    SuccessfulFieldResolutionResult resolutionResult =
+                        appView.appInfo().resolveField(field).asSuccessfulResolution();
+                    if (resolutionResult != null
+                        && resolutionResult.getResolvedField().toReference() != field) {
+                      nonReboundFieldReferences.add(field);
+                    }
+                  }
+
+                  @Override
+                  public void registerInitClass(DexType type) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeDirect(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeInterface(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeStatic(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeSuper(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeVirtual(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerNewInstance(DexType type) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInstanceOf(DexType type) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerTypeReference(DexType type) {
+                    // Intentionally empty.
+                  }
+                });
+          }
+        },
+        executorService);
+    return nonReboundFieldReferences;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index 944af37..8e3f4c1 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -165,12 +165,14 @@
       synchronized (fieldAccessInfo) {
         // Record the fact that there is a non-rebound access to the given field. We don't
         // distinguish between non-rebound reads and writes, so we just record it as a read.
-        ConcreteAccessContexts accessContexts =
-            fieldAccessInfo.getReadsWithContexts().isConcrete()
-                ? fieldAccessInfo.getReadsWithContexts().asConcrete()
-                : new ConcreteAccessContexts();
+        if (fieldAccessInfo.getReadsWithContexts().isBottom()) {
+          fieldAccessInfo.setReadsWithContexts(new ConcreteAccessContexts());
+        } else {
+          assert fieldAccessInfo.getReadsWithContexts().isConcrete();
+        }
         // For the purpose of member rebinding, we don't care about the access contexts, so we
         // simply use the empty set.
+        ConcreteAccessContexts accessContexts = fieldAccessInfo.getReadsWithContexts().asConcrete();
         accessContexts.getAccessesWithContexts().put(field, ProgramMethodSet.empty());
       }
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index a6a6021..9e23dd0 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -4,88 +4,104 @@
 
 package com.android.tools.r8.optimize;
 
+import static com.android.tools.r8.graph.GraphLens.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.google.common.collect.ImmutableMap;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-public class MemberRebindingLens extends NestedGraphLens {
+public class MemberRebindingLens extends NonIdentityGraphLens {
 
-  public static class Builder {
-
-    private final AppView<?> appView;
-
-    private final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
-    private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps = new IdentityHashMap<>();
-
-    protected Builder(AppView<?> appView) {
-      this.appView = appView;
-    }
-
-    public void map(DexField from, DexField to) {
-      if (from == to) {
-        assert !fieldMap.containsKey(from);
-        return;
-      }
-      fieldMap.put(from, to);
-    }
-
-    public void map(DexMethod from, DexMethod to, Invoke.Type type) {
-      if (from == to) {
-        assert !methodMaps.containsKey(type) || methodMaps.get(type).getOrDefault(from, to) == to;
-        return;
-      }
-      Map<DexMethod, DexMethod> methodMap =
-          methodMaps.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
-      assert methodMap.getOrDefault(from, to) == to;
-      methodMap.put(from, to);
-    }
-
-    public GraphLens build(GraphLens previousLens) {
-      if (fieldMap.isEmpty() && methodMaps.isEmpty()) {
-        return previousLens;
-      }
-      return new MemberRebindingLens(appView, methodMaps, fieldMap, previousLens);
-    }
-  }
-
-  private final AppView<?> appView;
+  private final AppView<AppInfoWithLiveness> appView;
   private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps;
+  private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
 
   public MemberRebindingLens(
-      AppView<?> appView,
+      AppView<AppInfoWithLiveness> appView,
       Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps,
-      Map<DexField, DexField> fieldMap,
-      GraphLens previousLens) {
-    super(
-        ImmutableMap.of(),
-        ImmutableMap.of(),
-        fieldMap,
-        null,
-        null,
-        previousLens,
-        appView.dexItemFactory());
+      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap) {
+    super(appView.dexItemFactory(), appView.graphLens());
     this.appView = appView;
     this.methodMaps = methodMaps;
+    this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
   }
 
-  public static Builder builder(AppView<?> appView) {
+  public static Builder builder(AppView<AppInfoWithLiveness> appView) {
     return new Builder(appView);
   }
 
   @Override
-  public boolean isLegitimateToHaveEmptyMappings() {
+  public boolean isMemberRebindingLens() {
     return true;
   }
 
   @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(type);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    return getPrevious().getOriginalFieldSignature(field);
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    return getPrevious().getOriginalMethodSignature(method);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    return getPrevious().getRenamedFieldSignature(originalField);
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    return this != applied
+        ? getPrevious().getRenamedMethodSignature(originalMethod, applied)
+        : originalMethod;
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  @Override
+  protected DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    assert previous.getReboundReference() == null;
+    return FieldLookupResult.builder(this)
+        .setReference(previous.getReference())
+        .setReboundReference(getReboundFieldReference(previous.getReference()))
+        .build();
+  }
+
+  private DexField getReboundFieldReference(DexField field) {
+    return nonReboundFieldReferenceToDefinitionMap.getOrDefault(field, field);
+  }
+
+  @Override
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context) {
     Map<DexMethod, DexMethod> methodMap =
@@ -97,12 +113,68 @@
     return MethodLookupResult.builder(this)
         .setReference(newMethod)
         .setPrototypeChanges(previous.getPrototypeChanges())
-        .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+        .setType(
+            mapVirtualInterfaceInvocationTypes(
+                appView, newMethod, previous.getReference(), previous.getType()))
         .build();
   }
 
   @Override
-  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return method;
+  }
+
+  public FieldRebindingIdentityLens toRewrittenFieldRebindingLens(
+      DexItemFactory dexItemFactory, GraphLens lens) {
+    FieldRebindingIdentityLens.Builder builder = FieldRebindingIdentityLens.builder();
+    nonReboundFieldReferenceToDefinitionMap.forEach(
+        (nonReboundFieldReference, reboundFieldReference) -> {
+          DexField rewrittenReboundFieldReference = lens.lookupField(reboundFieldReference);
+          DexField rewrittenNonReboundFieldReference =
+              rewrittenReboundFieldReference.withHolder(
+                  lens.lookupType(nonReboundFieldReference.getHolderType()), dexItemFactory);
+          builder.recordDefinitionForNonReboundFieldReference(
+              rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
+        });
+    return builder.build(dexItemFactory);
+  }
+
+  public static class Builder {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps = new IdentityHashMap<>();
+    private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
+        new IdentityHashMap<>();
+
+    private Builder(AppView<AppInfoWithLiveness> appView) {
+      this.appView = appView;
+    }
+
+    public void map(DexMethod from, DexMethod to, Invoke.Type type) {
+      if (from == to) {
+        assert !methodMaps.containsKey(type) || methodMaps.get(type).getOrDefault(from, to) == to;
+        return;
+      }
+      Map<DexMethod, DexMethod> methodMap =
+          methodMaps.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
+      assert methodMap.getOrDefault(from, to) == to;
+      methodMap.put(from, to);
+    }
+
+    void recordNonReboundFieldAccesses(FieldAccessInfo info) {
+      DexField reboundFieldReference = info.getField();
+      info.forEachIndirectAccess(
+          nonReboundFieldReference ->
+              recordNonReboundFieldAccess(nonReboundFieldReference, reboundFieldReference));
+    }
+
+    private void recordNonReboundFieldAccess(
+        DexField nonReboundFieldReference, DexField reboundFieldReference) {
+      nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
+    }
+
+    public MemberRebindingLens build() {
+      return new MemberRebindingLens(appView, methodMaps, nonReboundFieldReferenceToDefinitionMap);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
index 5d2fef0..52e7b3d 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -40,6 +40,11 @@
   }
 
   @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    return previous;
+  }
+
+  @Override
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context) {
     if (previous.getType() == Type.DIRECT && publicizedMethods.contains(previous.getReference())) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index 2848d3f..a95cfb4 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -50,7 +50,7 @@
 
   interface GraphLensLookupResultProvider {
 
-    abstract MethodLookupResult get(RewrittenPrototypeDescription prototypeChanges);
+    MethodLookupResult get(RewrittenPrototypeDescription prototypeChanges);
   }
 
   private final AppView<?> appView;
@@ -133,7 +133,7 @@
 
   @Override
   protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
+    return mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 3a3355e..070d6c8 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -72,13 +72,10 @@
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    assertThrowsWithHorizontalClassMerging(
-        () -> {
-          if (data().stream().count() > 0) {
-            r8R8Debug = compileR8(CompilationMode.DEBUG);
-            r8R8Release = compileR8(CompilationMode.RELEASE);
-          }
-        });
+    if (data().stream().count() > 0) {
+      r8R8Debug = compileR8(CompilationMode.DEBUG);
+      r8R8Release = compileR8(CompilationMode.RELEASE);
+    }
   }
 
   @Parameters(name = "{0}")
@@ -214,7 +211,6 @@
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     Path helloJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
     ProcessResult runResult = ToolHelper.runJava(helloJar, "hello.Hello");
     assertEquals(0, runResult.exitCode);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
index 91dc3b1..e357250 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
@@ -21,19 +21,16 @@
 
   @Test
   public void test() throws Exception {
-    if (enableHorizontalClassMerging) {
-      thrown.expect(Throwable.class);
-    }
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessOnMergedClassTestClasses.class)
         .addKeepMainRule(Main.class)
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
-        .addHorizontallyMergedLambdaClassesInspector(
+        .addHorizontallyMergedClassesInspector(
             inspector -> {
               if (enableHorizontalClassMerging) {
-                inspector.assertMerged(C.class, D.class);
+                inspector.assertMerged(C.class, D.class).assertMergedIntoDifferentType(D.class);
               }
             })
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
index 3581801..3afcfb2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
@@ -22,9 +22,6 @@
 
   @Test
   public void test() throws Exception {
-    if (enableHorizontalClassMerging) {
-      thrown.expect(Throwable.class);
-    }
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessWithMergedTypeTestClasses.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
index ecf49fc..0f28809 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.classmerging.vertical.NonReboundFieldAccessWithMergedTypeTest.GreetingBase;
 import com.android.tools.r8.classmerging.vertical.testclasses.NonReboundFieldAccessOnMergedClassTestClasses;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,13 +30,12 @@
 
   @Test
   public void test() throws Exception {
-    thrown.expect(Throwable.class);
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessOnMergedClassTestClasses.class)
         .addKeepMainRule(Main.class)
         .addVerticallyMergedClassesInspector(
-            inspector -> inspector.assertMergedIntoSubtype(GreetingBase.class))
+            inspector -> inspector.assertMergedIntoSubtype(C.class))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
index 5a3f831..6d8cef1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
@@ -30,7 +30,6 @@
 
   @Test
   public void test() throws Exception {
-    thrown.expect(Throwable.class);
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessWithMergedTypeTestClasses.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index cbd5710..a04b0c1 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -97,7 +97,6 @@
 
   @Test
   public void testHello() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     Assume.assumeTrue(!ToolHelper.isWindows());
     Path prevGeneratedJar = null;
     String prevRunResult = null;
@@ -131,7 +130,6 @@
 
   @Test
   public void testR8() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     Assume.assumeTrue(!ToolHelper.isWindows());
     Assume.assumeTrue(parameters.isCfRuntime());
     Assume.assumeTrue(CfVm.JDK11 == parameters.getRuntime().asCf().getVm());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index a684ea3..9ebb411 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -41,7 +41,6 @@
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(SingleTargetAfterInliningTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
deleted file mode 100644
index 59cfbe8..0000000
--- a/src/test/java/com/android/tools/r8/regress/B76025099.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2018, 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.regress;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.function.Function;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class B76025099 extends TestBase {
-
-  private Backend backend;
-
-  @Parameterized.Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
-  }
-
-  public B76025099(Backend backend) {
-    this.backend = backend;
-  }
-
-  private static final String PRG =
-      ToolHelper.EXAMPLES_BUILD_DIR + "regress_76025099" + FileUtils.JAR_EXTENSION;
-
-  private AndroidApp runR8(AndroidApp app) throws Exception {
-    R8Command command =
-        ToolHelper.addProguardConfigurationConsumer(
-                ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend)),
-                pgConfig -> {
-                  pgConfig.setPrintMapping(true);
-                  pgConfig.setPrintMappingFile(map);
-                })
-            .addLibraryFiles(runtimeJar(backend))
-            .addProguardConfigurationFiles(pgConfig)
-            .setDisableMinification(true)
-            .setOutput(tempRoot.toPath(), outputMode(backend))
-            .build();
-    return ToolHelper.runR8(command);
-  }
-
-  private File tempRoot;
-  private Path jarPath;
-  private AndroidApp originalApp;
-  private String mainName;
-  private Path pgConfig;
-  private Path map;
-
-  @Before
-  public void setUp() throws Exception {
-    tempRoot = temp.getRoot();
-    jarPath = Paths.get(PRG);
-    originalApp = readJar(jarPath);
-    mainName = "regress_76025099.Main";
-    pgConfig = File.createTempFile("keep-rules", ".config", tempRoot).toPath();
-    String config = keepMainProguardConfiguration(mainName);
-    config += System.lineSeparator() + "-dontobfuscate";
-    Files.write(pgConfig, config.getBytes());
-    map = File.createTempFile("proguard", ".map", tempRoot).toPath();
-  }
-
-  @Test
-  public void testProguardAndD8() throws Exception {
-    Assume.assumeTrue(backend == Backend.DEX);
-    if (!isRunProguard()) {
-      return;
-    }
-
-    ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
-    assertEquals(0, jvmOutput.exitCode);
-
-    Path proguarded =
-        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, tempRoot).toPath();
-    ProcessResult proguardResult = ToolHelper.runProguardRaw(jarPath, proguarded, pgConfig, map);
-    assertEquals(0, proguardResult.exitCode);
-
-    AndroidApp processedApp = ToolHelper.runD8(readJar(proguarded));
-    verifyFieldAccess(processedApp, jvmOutput);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
-    assertEquals(0, jvmOutput.exitCode);
-
-    AndroidApp processedApp = runR8(originalApp);
-    verifyFieldAccess(processedApp, jvmOutput);
-  }
-
-  private static InstructionSubject findInstructionOrNull(
-      Iterator<InstructionSubject> iterator, Function<InstructionSubject, Boolean> predicate) {
-    while (iterator.hasNext()) {
-      InstructionSubject instruction = iterator.next();
-      if (predicate.apply(instruction)) {
-        return instruction;
-      }
-    }
-    return null;
-  }
-
-  private void verifyFieldAccess(AndroidApp processedApp, ProcessResult jvmOutput)
-      throws Exception {
-    CodeInspector inspector = new CodeInspector(processedApp);
-    ClassSubject impl = inspector.clazz("regress_76025099.impl.Impl");
-    assertThat(impl, isPresent());
-    MethodSubject init = impl.init("java.lang.String");
-    assertThat(init, isPresent());
-    Iterator<InstructionSubject> iterator = init.iterateInstructions();
-
-    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isInvoke));
-
-    InstructionSubject instruction =
-        findInstructionOrNull(iterator, InstructionSubject::isInstancePut);
-    assertNotNull(instruction);
-    FieldAccessInstructionSubject fieldAccessInstruction =
-        (FieldAccessInstructionSubject) instruction;
-    assertEquals("name", fieldAccessInstruction.name());
-    assertTrue(fieldAccessInstruction.holder().is(impl.getDexProgramClass().type.toString()));
-
-    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isReturnVoid));
-
-    ProcessResult output;
-    if (backend == Backend.DEX) {
-      output = runOnArtRaw(processedApp, mainName);
-    } else {
-      assert backend == Backend.CF;
-      output = runOnJavaRaw(processedApp, mainName, Collections.emptyList());
-    }
-    assertEquals(0, output.exitCode);
-    assertEquals(jvmOutput.stdout, output.stdout);
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/regress/b76025099/B76025099.java b/src/test/java/com/android/tools/r8/regress/b76025099/B76025099.java
new file mode 100644
index 0000000..e179440
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/B76025099.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2018, 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.regress.b76025099;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardTestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.regress.b76025099.testclasses.Main;
+import com.android.tools.r8.regress.b76025099.testclasses.impl.Impl;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Iterator;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B76025099 extends TestBase {
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines(Main.class.getTypeName());
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B76025099(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testProguardAndD8() throws Exception {
+    assumeTrue(isRunProguard());
+
+    ProguardTestCompileResult proguardCompileResult =
+        testForProguard()
+            .addProgramFiles(ToolHelper.getClassFilesForTestPackage(Main.class.getPackage()))
+            .addKeepMainRule(Main.class)
+            .noMinification()
+            .compile();
+
+    if (parameters.isDexRuntime()) {
+      testForD8()
+          .addProgramFiles(proguardCompileResult.outputJar())
+          .setMinApi(parameters.getApiLevel())
+          .compile()
+          .inspect(this::verifyFieldAccess)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    } else {
+      proguardCompileResult
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getClassFilesForTestPackage(Main.class.getPackage()))
+        .addKeepMainRule(Main.class)
+        .enableNoVerticalClassMergingAnnotations()
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::verifyFieldAccess)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private static InstructionSubject findInstructionOrNull(
+      Iterator<InstructionSubject> iterator, Function<InstructionSubject, Boolean> predicate) {
+    while (iterator.hasNext()) {
+      InstructionSubject instruction = iterator.next();
+      if (predicate.apply(instruction)) {
+        return instruction;
+      }
+    }
+    return null;
+  }
+
+  private void verifyFieldAccess(CodeInspector inspector) {
+    ClassSubject impl = inspector.clazz(Impl.class);
+    assertThat(impl, isPresent());
+    MethodSubject init = impl.init("java.lang.String");
+    assertThat(init, isPresent());
+    Iterator<InstructionSubject> iterator = init.iterateInstructions();
+
+    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isInvoke));
+
+    InstructionSubject instruction =
+        findInstructionOrNull(iterator, InstructionSubject::isInstancePut);
+    assertNotNull(instruction);
+    FieldAccessInstructionSubject fieldAccessInstruction =
+        (FieldAccessInstructionSubject) instruction;
+    assertEquals("name", fieldAccessInstruction.name());
+    assertTrue(fieldAccessInstruction.holder().is(impl.getDexProgramClass().type.toString()));
+
+    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isReturnVoid));
+  }
+}
diff --git a/src/test/examples/regress_76025099/Logger.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Logger.java
similarity index 81%
rename from src/test/examples/regress_76025099/Logger.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Logger.java
index d6e9f95..b0178e0 100644
--- a/src/test/examples/regress_76025099/Logger.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Logger.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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 regress_76025099;
+package com.android.tools.r8.regress.b76025099.testclasses;
 
 public interface Logger {
   String getName();
diff --git a/src/test/examples/regress_76025099/Main.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Main.java
similarity index 74%
rename from src/test/examples/regress_76025099/Main.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Main.java
index 659bdbd..3d49201 100644
--- a/src/test/examples/regress_76025099/Main.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Main.java
@@ -1,9 +1,9 @@
 // Copyright (c) 2018, 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 regress_76025099;
+package com.android.tools.r8.regress.b76025099.testclasses;
 
-import regress_76025099.impl.Factory;
+import com.android.tools.r8.regress.b76025099.testclasses.impl.Factory;
 
 public class Main {
   public static void main(String[] args) {
diff --git a/src/test/examples/regress_76025099/helper/AbstractBase.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractBase.java
similarity index 62%
rename from src/test/examples/regress_76025099/helper/AbstractBase.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractBase.java
index 046e12f..9d5694c 100644
--- a/src/test/examples/regress_76025099/helper/AbstractBase.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractBase.java
@@ -1,10 +1,12 @@
 // Copyright (c) 2018, 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 regress_76025099.helper;
+package com.android.tools.r8.regress.b76025099.testclasses.helper;
 
-import regress_76025099.Logger;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.regress.b76025099.testclasses.Logger;
 
+@NoVerticalClassMerging
 abstract class AbstractBase implements Logger {
   protected String name;
 
diff --git a/src/test/examples/regress_76025099/helper/AbstractSub.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractSub.java
similarity index 63%
rename from src/test/examples/regress_76025099/helper/AbstractSub.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractSub.java
index 9240e4a..0dd1af7 100644
--- a/src/test/examples/regress_76025099/helper/AbstractSub.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractSub.java
@@ -1,7 +1,6 @@
 // Copyright (c) 2018, 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 regress_76025099.helper;
+package com.android.tools.r8.regress.b76025099.testclasses.helper;
 
-public abstract class AbstractSub extends AbstractBase {
-}
+public abstract class AbstractSub extends AbstractBase {}
diff --git a/src/test/examples/regress_76025099/impl/Factory.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Factory.java
similarity index 82%
rename from src/test/examples/regress_76025099/impl/Factory.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Factory.java
index 0cc1cc9..fd98f42 100644
--- a/src/test/examples/regress_76025099/impl/Factory.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Factory.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, 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 regress_76025099.impl;
+package com.android.tools.r8.regress.b76025099.testclasses.impl;
 
 public class Factory {
   public static Impl getImpl(String name) {
diff --git a/src/test/examples/regress_76025099/impl/Impl.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Impl.java
similarity index 67%
rename from src/test/examples/regress_76025099/impl/Impl.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Impl.java
index 74eefcf..b24bde9 100644
--- a/src/test/examples/regress_76025099/impl/Impl.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Impl.java
@@ -1,12 +1,12 @@
 // Copyright (c) 2018, 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 regress_76025099.impl;
+package com.android.tools.r8.regress.b76025099.testclasses.impl;
 
-import regress_76025099.helper.AbstractSub;
+import com.android.tools.r8.regress.b76025099.testclasses.helper.AbstractSub;
 
 public class Impl extends AbstractSub {
   Impl(String name) {
     this.name = name;
   }
-}
\ No newline at end of file
+}