Remove annotations from non-kept items in non-compat mode

RELNOTES: Remove annotations from non-kept items in non-compat mode.

When building with R8 in Proguard compatibility mode, R8 does not remove any annotations from the program that are matched by a `-keepattributes` rule (e.g., the `RuntimeVisibleAnnnotations` attribute). In non-compatibility mode (also called full mode), R8 now removes all annotations from the program, except annotations on classes, fields, and methods that are matched by a `-keep` rule.

For example, to retain all runtime visible annotations for a class `C` it is no longer sufficient to add the rule '-keepattributes RuntimeVisibleAnnotations' in non-compatibility mode. The configuration must also explicitly add a `-keep` rule for class `C`, which can be done with the rule `-keep class C`, or the rule `-keep,allowobfuscation,allowshrinking class C`, which allows both renaming and shrinking of `C`, but still preserves the annotations of `C` if the class is not removed.

Change-Id: Ife273adbcfa1c9a690fc905566b6a87c66353737
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1a78e63..e02ccfb 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -408,7 +408,7 @@
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
                   .build(appViewWithLiveness, removedClasses);
-          annotationRemover.ensureValid().run();
+          annotationRemover.ensureValid().run(executorService);
           new GenericSignatureRewriter(appView, NamingLens.getIdentityLens(), genericContextBuilder)
               .run(appView.appInfo().classes(), executorService);
         }
@@ -642,7 +642,7 @@
             // Remove annotations that refer to types that no longer exist.
             AnnotationRemover.builder()
                 .build(appView.withLiveness(), removedClasses)
-                .run();
+                .run(executorService);
             new GenericSignatureRewriter(
                     appView, NamingLens.getIdentityLens(), genericContextBuilder)
                 .run(appView.appInfo().classes(), executorService);
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 0576326..1141602 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -25,6 +25,30 @@
 import java.util.function.Function;
 
 public class DexAnnotation extends DexItem implements StructuralItem<DexAnnotation> {
+
+  public enum AnnotatedKind {
+    FIELD,
+    METHOD,
+    TYPE,
+    PARAMETER;
+
+    public static AnnotatedKind from(DexDefinition definition) {
+      return from(definition.getReference());
+    }
+
+    public static AnnotatedKind from(ProgramDefinition definition) {
+      return from(definition.getReference());
+    }
+
+    public static AnnotatedKind from(DexReference reference) {
+      return reference.apply(type -> TYPE, field -> FIELD, method -> METHOD);
+    }
+
+    public boolean isParameter() {
+      return this == PARAMETER;
+    }
+  }
+
   public static final DexAnnotation[] EMPTY_ARRAY = {};
   public static final int VISIBILITY_BUILD = 0x00;
   public static final int VISIBILITY_RUNTIME = 0x01;
@@ -300,6 +324,11 @@
     return annotation.annotation.type == factory.annotationDefault;
   }
 
+  public static boolean isJavaLangRetentionAnnotation(
+      DexAnnotation annotation, DexItemFactory factory) {
+    return annotation.getAnnotationType() == factory.retentionType;
+  }
+
   public static boolean isSourceDebugExtension(DexAnnotation annotation,
       DexItemFactory factory) {
     return annotation.annotation.type == factory.annotationSourceDebugExtension;
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index 651a484..454be57 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -44,6 +44,18 @@
     this.annotations = annotations;
   }
 
+  public DexAnnotationSet create(DexAnnotation[] annotations) {
+    return ArrayUtils.isEmpty(annotations) ? empty() : new DexAnnotationSet(annotations);
+  }
+
+  public DexAnnotation get(int index) {
+    return annotations[index];
+  }
+
+  public DexAnnotation getFirst() {
+    return get(0);
+  }
+
   @Override
   public DexAnnotationSet self() {
     return this;
@@ -192,14 +204,8 @@
     if (isEmpty()) {
       return this;
     }
-    DexAnnotation[] rewritten = ArrayUtils.map(DexAnnotation[].class, annotations, rewriter);
-    if (rewritten == annotations) {
-      return this;
-    }
-    if (rewritten.length == 0) {
-      return DexAnnotationSet.empty();
-    }
-    return new DexAnnotationSet(rewritten);
+    DexAnnotation[] rewritten = ArrayUtils.map(annotations, rewriter, DexAnnotation.EMPTY_ARRAY);
+    return rewritten != annotations ? create(rewritten) : this;
   }
 
   public DexAnnotationSet methodParametersWithFakeThisArguments(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index 8eeda6b..2695b1f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.shaking.AnnotationRemover;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -31,15 +31,14 @@
 
   public abstract AccessFlags<?> getAccessFlags();
 
-  public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
-    return annotations.keepIf(
-        annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
-  }
-
   public void clearAnnotations() {
     setAnnotations(DexAnnotationSet.empty());
   }
 
+  public void clearAllAnnotations() {
+    clearAnnotations();
+  }
+
   public void setAnnotations(DexAnnotationSet annotations) {
     this.annotations = annotations;
   }
@@ -48,6 +47,12 @@
     setAnnotations(annotations().removeIf(predicate));
   }
 
+  public void rewriteAllAnnotations(
+      BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+    setAnnotations(
+        annotations().rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.from(this))));
+  }
+
   public boolean isDexClass() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index 98df8c7..e338ef5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -112,7 +112,7 @@
       Function<DexAnnotationElement, DexAnnotationElement> elementRewriter) {
     DexType rewrittenType = typeRewriter.apply(type);
     DexAnnotationElement[] rewrittenElements =
-        ArrayUtils.map(DexAnnotationElement[].class, elements, elementRewriter);
+        ArrayUtils.map(elements, elementRewriter, DexAnnotationElement.EMPTY_ARRAY);
     if (rewrittenType == type && rewrittenElements == elements) {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 449747b..f8a2537 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.code.IRCode;
@@ -72,8 +73,6 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.shaking.AnnotationRemover;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -92,6 +91,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.IntPredicate;
@@ -399,11 +399,6 @@
     return getReference().proto.returnType;
   }
 
-  public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
-    return parameterAnnotationsList.keepIf(
-        annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
-  }
-
   public OptionalBool isLibraryMethodOverride() {
     return isNonPrivateVirtualMethod() ? isLibraryMethodOverride : OptionalBool.FALSE;
   }
@@ -894,14 +889,38 @@
     return builder.toString();
   }
 
-  public ParameterAnnotationsList getParameterAnnotations() {
-    return parameterAnnotationsList;
+  @Override
+  public void clearAllAnnotations() {
+    clearAnnotations();
+    clearParameterAnnotations();
+  }
+
+  @Override
+  public void rewriteAllAnnotations(
+      BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+    setAnnotations(
+        annotations().rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.METHOD)));
+    setParameterAnnotations(
+        getParameterAnnotations()
+            .rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.PARAMETER)));
   }
 
   public void clearParameterAnnotations() {
     parameterAnnotationsList = ParameterAnnotationsList.empty();
   }
 
+  public DexAnnotationSet getParameterAnnotation(int index) {
+    return getParameterAnnotations().get(index);
+  }
+
+  public ParameterAnnotationsList getParameterAnnotations() {
+    return parameterAnnotationsList;
+  }
+
+  public void setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
+    this.parameterAnnotationsList = parameterAnnotations;
+  }
+
   public String toSmaliString(ClassNameMapper naming) {
     checkIfObsolete();
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index ca8ba66..d3e536e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -451,6 +451,8 @@
       createStaticallyKnownType("Ljava/util/function/LongConsumer;");
   public final DexType intConsumer = createStaticallyKnownType("Ljava/util/function/IntConsumer;");
 
+  public final DexType retentionType =
+      createStaticallyKnownType("Ljava/lang/annotation/Retention;");
   public final DexType runtimeExceptionType = createStaticallyKnownType(runtimeExceptionDescriptor);
   public final DexType throwableType = createStaticallyKnownType(throwableDescriptor);
   public final DexType illegalAccessErrorType =
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
index dee2c8d..ba98190 100644
--- a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -70,6 +70,13 @@
     this.missingParameterAnnotations = missingParameterAnnotations;
   }
 
+  public static ParameterAnnotationsList create(
+      DexAnnotationSet[] values, int missingParameterAnnotations) {
+    return ArrayUtils.isEmpty(values)
+        ? empty()
+        : new ParameterAnnotationsList(values, missingParameterAnnotations);
+  }
+
   @Override
   public ParameterAnnotationsList self() {
     return this;
@@ -226,11 +233,15 @@
     return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
   }
 
-  public ParameterAnnotationsList rewrite(Function<DexAnnotationSet, DexAnnotationSet> mapper) {
-    DexAnnotationSet[] rewritten = ArrayUtils.map(DexAnnotationSet[].class, values, mapper);
-    if (rewritten != values) {
-      return new ParameterAnnotationsList(rewritten, missingParameterAnnotations);
+  public ParameterAnnotationsList rewrite(Function<DexAnnotation, DexAnnotation> mapper) {
+    if (isEmpty()) {
+      return this;
     }
-    return this;
+    DexAnnotationSet[] rewritten =
+        ArrayUtils.map(
+            values, annotations -> annotations.rewrite(mapper), DexAnnotationSet.EMPTY_ARRAY);
+    return rewritten != values
+        ? ParameterAnnotationsList.create(rewritten, missingParameterAnnotations)
+        : this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 17e779e..c1dedd5 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public interface ProgramDefinition extends Definition, ProgramDerivedContext {
@@ -21,6 +23,15 @@
     return this;
   }
 
+  default void clearAllAnnotations() {
+    getDefinition().clearAllAnnotations();
+  }
+
+  default void rewriteAllAnnotations(
+      BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+    getDefinition().rewriteAllAnnotations(rewriter);
+  }
+
   DexProgramClass getContextClass();
 
   AccessFlags<?> getAccessFlags();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 3e2a276..aec6be0 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 
 public class ProgramField extends DexClassAndField
     implements ProgramMember<DexEncodedField, DexField> {
@@ -60,12 +60,7 @@
   }
 
   @Override
-  public KotlinMemberLevelInfo getKotlinInfo() {
+  public KotlinFieldLevelInfo getKotlinInfo() {
     return getDefinition().getKotlinInfo();
   }
-
-  @Override
-  public void clearKotlinInfo() {
-    getDefinition().clearKotlinInfo();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
index 1234378..fa77682 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMember.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -23,5 +23,11 @@
 
   KotlinMemberLevelInfo getKotlinInfo();
 
-  void clearKotlinInfo();
+  default void clearGenericSignature() {
+    getDefinition().clearGenericSignature();
+  }
+
+  default void clearKotlinInfo() {
+    getDefinition().clearKotlinInfo();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 5a58189..5faf268 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -95,15 +95,10 @@
   }
 
   @Override
-  public KotlinMemberLevelInfo getKotlinInfo() {
+  public KotlinMethodLevelInfo getKotlinInfo() {
     return getDefinition().getKotlinInfo();
   }
 
-  @Override
-  public void clearKotlinInfo() {
-    getDefinition().clearKotlinInfo();
-  }
-
   public MethodPosition getPosition() {
     return getDefinition().getPosition();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
index 5b058c9..191adc4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
@@ -111,9 +111,9 @@
   private DexProto rewriteProto(DexProto proto, Map<DexType, MergeGroup> groups) {
     DexType[] parameters =
         ArrayUtils.map(
-            DexType[].class,
             proto.getParameters().values,
-            parameter -> rewriteType(parameter, groups));
+            parameter -> rewriteType(parameter, groups),
+            DexType.EMPTY_ARRAY);
     return dexItemFactory.createProto(rewriteType(proto.getReturnType(), groups), parameters);
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
index 4fdad6a..fedb75d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
@@ -40,10 +40,8 @@
   }
 
   private void processMethod(DexEncodedMethod method) {
-    method.setAnnotations(method.annotations().rewrite(this::rewriteAnnotation));
-    method.parameterAnnotationsList =
-        method.parameterAnnotationsList.rewrite(
-            dexAnnotationSet -> dexAnnotationSet.rewrite(this::rewriteAnnotation));
+    method.rewriteAllAnnotations(
+        (annotation, isParameterAnnotation) -> rewriteAnnotation(annotation));
   }
 
   private void processField(DexEncodedField field) {
@@ -73,7 +71,7 @@
     if (value.isDexValueArray()) {
       DexValue[] originalValues = value.asDexValueArray().getValues();
       DexValue[] rewrittenValues =
-          ArrayUtils.map(DexValue[].class, originalValues, this::rewriteComplexValue);
+          ArrayUtils.map(originalValues, this::rewriteComplexValue, DexValue.EMPTY_ARRAY);
       if (rewrittenValues != originalValues) {
         return new DexValueArray(rewrittenValues);
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 567b7cb..ac1ad75 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -6,24 +6,28 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
 import com.android.tools.r8.kotlin.KotlinPropertyInfo;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public class AnnotationRemover {
 
@@ -49,49 +53,50 @@
   }
 
   /** Used to filter annotations on classes, methods and fields. */
-  private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
+  private boolean filterAnnotations(
+      ProgramDefinition holder, DexAnnotation annotation, AnnotatedKind kind) {
     return annotationsToRetain.contains(annotation)
-        || shouldKeepAnnotation(appView, holder, annotation, isAnnotationTypeLive(annotation));
-  }
-
-  public static boolean shouldKeepAnnotation(
-      AppView<AppInfoWithLiveness> appView, DexDefinition holder, DexAnnotation annotation) {
-    return shouldKeepAnnotation(
-        appView, holder, annotation, isAnnotationTypeLive(annotation, appView));
+        || shouldKeepAnnotation(
+            appView, holder, annotation, isAnnotationTypeLive(annotation), kind);
   }
 
   public static boolean shouldKeepAnnotation(
       AppView<?> appView,
-      DexDefinition holder,
+      ProgramDefinition holder,
       DexAnnotation annotation,
-      boolean isAnnotationTypeLive) {
+      boolean isAnnotationTypeLive,
+      AnnotatedKind kind) {
     // If we cannot run the AnnotationRemover we are keeping the annotation.
     if (!appView.options().isShrinking()) {
       return true;
     }
+
+    InternalOptions options = appView.options();
     ProguardKeepAttributes config =
-        appView.options().getProguardConfiguration() != null
-            ? appView.options().getProguardConfiguration().getKeepAttributes()
+        options.getProguardConfiguration() != null
+            ? options.getProguardConfiguration().getKeepAttributes()
             : ProguardKeepAttributes.fromPatterns(ImmutableList.of());
 
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-
     switch (annotation.visibility) {
       case DexAnnotation.VISIBILITY_SYSTEM:
+        if (kind.isParameter()) {
+          return false;
+        }
         // InnerClass and EnclosingMember are represented in class attributes, not annotations.
         assert !DexAnnotation.isInnerClassAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isMemberClassesAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingMethodAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingClassAnnotation(annotation, dexItemFactory);
-        assert appView.options().passthroughDexCode
+        assert options.passthroughDexCode
             || !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
         if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
           return true;
         }
         if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
-          assert holder.isDexClass();
+          assert holder.isProgramClass();
           appView.setSourceDebugExtensionForType(
-              holder.asDexClass(), annotation.annotation.elements[0].value.asDexValueString());
+              holder.asProgramClass(), annotation.annotation.elements[0].value.asDexValueString());
           return config.sourceDebugExtension;
         }
         if (config.methodParameters
@@ -106,14 +111,35 @@
         return false;
 
       case DexAnnotation.VISIBILITY_RUNTIME:
-        if (!config.runtimeVisibleAnnotations) {
-          return false;
+        // We always keep the @java.lang.Retention annotation on annotation classes, since the
+        // removal of this annotation may change the annotation from being runtime visible to
+        // runtime invisible.
+        if (holder.isProgramClass()
+            && holder.asProgramClass().isAnnotation()
+            && DexAnnotation.isJavaLangRetentionAnnotation(annotation, dexItemFactory)) {
+          return true;
+        }
+
+        if (kind.isParameter()) {
+          if (!options.isKeepRuntimeVisibleParameterAnnotationsEnabled()) {
+            return false;
+          }
+        } else {
+          if (!options.isKeepRuntimeVisibleAnnotationsEnabled()) {
+            return false;
+          }
         }
         return isAnnotationTypeLive;
 
       case DexAnnotation.VISIBILITY_BUILD:
-        if (!config.runtimeInvisibleAnnotations) {
-          return false;
+        if (kind.isParameter()) {
+          if (!options.isKeepRuntimeInvisibleParameterAnnotationsEnabled()) {
+            return false;
+          }
+        } else {
+          if (!options.isKeepRuntimeInvisibleAnnotationsEnabled()) {
+            return false;
+          }
         }
         return isAnnotationTypeLive;
 
@@ -132,60 +158,35 @@
     return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
   }
 
-  /**
-   * Used to filter annotations on parameters.
-   */
-  private boolean filterParameterAnnotations(DexAnnotation annotation) {
-    if (annotationsToRetain.contains(annotation)) {
-      return true;
-    }
-    switch (annotation.visibility) {
-      case DexAnnotation.VISIBILITY_SYSTEM:
-        return false;
-      case DexAnnotation.VISIBILITY_RUNTIME:
-        if (!keep.runtimeVisibleParameterAnnotations) {
-          return false;
-        }
-        break;
-      case DexAnnotation.VISIBILITY_BUILD:
-        if (!keep.runtimeInvisibleParameterAnnotations) {
-          return false;
-        }
-        break;
-      default:
-        throw new Unreachable("Unexpected annotation visibility.");
-    }
-    return isAnnotationTypeLive(annotation);
-  }
-
   public AnnotationRemover ensureValid() {
     keep.ensureValid(appView.options().forceProguardCompatibility);
     return this;
   }
 
-  public void run() {
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      stripAttributes(clazz);
-      clazz.setAnnotations(
-          clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
-      // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin
-      // properties are split over fields and methods. Check if any is pinned before pruning the
-      // information.
-      Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
-      clazz.forEachProgramMember(
-          member -> processMember(member.getDefinition(), clazz, pinnedKotlinProperties));
-      clazz.forEachProgramMember(
-          member -> {
-            KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
-            if (kotlinInfo.isProperty()
-                && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) {
-              member.clearKotlinInfo();
-            }
-          });
-    }
+  public void run(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(appView.appInfo().classes(), this::run, executorService);
     assert verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo();
   }
 
+  private void run(DexProgramClass clazz) {
+    KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz);
+    removeAnnotations(clazz, keepInfo);
+    stripAttributes(clazz, keepInfo);
+    // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin
+    // properties are split over fields and methods. Check if any is pinned before pruning the
+    // information.
+    Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
+    clazz.forEachProgramMember(member -> processMember(member, clazz, pinnedKotlinProperties));
+    clazz.forEachProgramMember(
+        member -> {
+          KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
+          if (kotlinInfo.isProperty()
+              && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) {
+            member.clearKotlinInfo();
+          }
+        });
+  }
+
   private boolean verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (clazz.getKotlinInfo().isNoKotlinInformation()) {
@@ -200,17 +201,11 @@
   }
 
   private void processMember(
-      DexEncodedMember<?, ?> member,
+      ProgramMember<?, ?> member,
       DexProgramClass clazz,
       Set<KotlinPropertyInfo> pinnedKotlinProperties) {
-    member.setAnnotations(
-        member.annotations().rewrite(annotation -> rewriteAnnotation(member, annotation)));
-    if (member.isDexEncodedMethod()) {
-      DexEncodedMethod method = member.asDexEncodedMethod();
-      method.parameterAnnotationsList =
-          method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
-    }
-    KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member, clazz);
+    KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member);
+    removeAnnotations(member, memberInfo);
     if (memberInfo.isSignatureAttributeRemovalAllowed(options)) {
       member.clearGenericSignature();
     }
@@ -223,9 +218,10 @@
     }
   }
 
-  private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
+  private DexAnnotation rewriteAnnotation(
+      ProgramDefinition holder, DexAnnotation original, AnnotatedKind kind) {
     // Check if we should keep this annotation first.
-    if (filterAnnotations(holder, original)) {
+    if (filterAnnotations(holder, original, kind)) {
       // Then, filter out values that refer to dead definitions.
       return original.rewrite(this::rewriteEncodedAnnotation);
     }
@@ -265,12 +261,28 @@
     return liveGetter ? original : null;
   }
 
-  private void stripAttributes(DexProgramClass clazz) {
+  private void removeAnnotations(ProgramDefinition definition, KeepInfo<?, ?> keepInfo) {
+    boolean isAnnotation =
+        definition.isProgramClass() && definition.asProgramClass().isAnnotation();
+    if ((options.isForceProguardCompatibilityEnabled() || keepInfo.isPinned())) {
+      definition.rewriteAllAnnotations(
+          (annotation, kind) -> rewriteAnnotation(definition, annotation, kind));
+    } else if (!isAnnotation) {
+      definition.clearAllAnnotations();
+    } else {
+      definition.rewriteAllAnnotations(
+          (annotation, isParameterAnnotation) ->
+              DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())
+                  ? annotation
+                  : null);
+    }
+  }
+
+  private void stripAttributes(DexProgramClass clazz, KeepClassInfo keepInfo) {
     // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
     // need to keep the enclosing method and inner classes attributes, if requested. In Proguard
     // compatibility mode we keep these attributes independent of whether the given class is kept.
     // In full mode we remove the attribute if not both sides are kept.
-    KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz);
     clazz.removeEnclosingMethodAttribute(
         enclosingMethodAttribute ->
             keepInfo.isEnclosingMethodAttributeRemovalAllowed(
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 339a56d..4378c0f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.ClasspathOrLibraryDefinition;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
@@ -169,11 +170,11 @@
 
 /**
  * Approximates the runtime dependencies for the given set of roots.
- * <p>
+ *
  * <p>The implementation filters the static call-graph with liveness information on classes to
  * remove virtual methods that are reachable by their static type but are unreachable at runtime as
  * they are not visible from any instance.
- * <p>
+ *
  * <p>As result of the analysis, an instance of {@link AppInfoWithLiveness} is returned. See the
  * field descriptions for details.
  */
@@ -393,6 +394,13 @@
   private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
       new IdentityHashMap<>();
 
+  /**
+   * A map from annotation classes to parameter annotations that need to be processed should the
+   * classes ever become live.
+   */
+  private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>>
+      deferredParameterAnnotations = new IdentityHashMap<>();
+
   /** Map of active if rules to speed up aapt2 generated keep rules. */
   private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
 
@@ -1828,16 +1836,9 @@
 
     // If this type has deferred annotations, we have to process those now, too.
     if (clazz.isAnnotation()) {
-      Map<DexAnnotation, List<ProgramDefinition>> annotations =
-          deferredAnnotations.remove(clazz.getType());
-      if (annotations != null) {
-        assert annotations.keySet().stream()
-            .allMatch(a -> a.getAnnotationType() == clazz.getType());
-        annotations.forEach(
-            (annotation, annotatedItems) ->
-                annotatedItems.forEach(
-                    annotatedItem -> processAnnotation(annotatedItem, annotation)));
-      }
+      processDeferredAnnotations(clazz, deferredAnnotations, AnnotatedKind::from);
+      processDeferredAnnotations(
+          clazz, deferredParameterAnnotations, annotatedItem -> AnnotatedKind.PARAMETER);
     }
 
     rootSet.forEachDependentInstanceConstructor(
@@ -1849,6 +1850,24 @@
     analyses.forEach(analysis -> analysis.processNewlyLiveClass(clazz, workList));
   }
 
+  private void processDeferredAnnotations(
+      DexProgramClass clazz,
+      Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations,
+      Function<ProgramDefinition, AnnotatedKind> kindProvider) {
+    Map<DexAnnotation, List<ProgramDefinition>> annotations =
+        deferredAnnotations.remove(clazz.getType());
+    if (annotations != null) {
+      assert annotations.keySet().stream()
+          .allMatch(annotation -> annotation.getAnnotationType() == clazz.getType());
+      annotations.forEach(
+          (annotation, annotatedItems) ->
+              annotatedItems.forEach(
+                  annotatedItem ->
+                      processAnnotation(
+                          annotatedItem, annotation, kindProvider.apply(annotatedItem))));
+    }
+  }
+
   private void ensureMethodsContinueToWidenAccess(ClassDefinition clazz) {
     assert !clazz.isProgramClass();
     ScopedDexMethodSet seen =
@@ -1931,27 +1950,35 @@
   }
 
   private void processAnnotations(ProgramDefinition annotatedItem) {
-    processAnnotations(annotatedItem, annotatedItem.getDefinition().annotations());
+    processAnnotations(
+        annotatedItem,
+        annotatedItem.getDefinition().annotations(),
+        AnnotatedKind.from(annotatedItem));
   }
 
-  private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotationSet annotations) {
-    processAnnotations(annotatedItem, annotations.annotations);
+  private void processAnnotations(
+      ProgramDefinition annotatedItem, DexAnnotationSet annotations, AnnotatedKind kind) {
+    processAnnotations(annotatedItem, annotations.annotations, kind);
   }
 
-  private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotation[] annotations) {
+  private void processAnnotations(
+      ProgramDefinition annotatedItem, DexAnnotation[] annotations, AnnotatedKind kind) {
     for (DexAnnotation annotation : annotations) {
-      processAnnotation(annotatedItem, annotation);
+      processAnnotation(annotatedItem, annotation, kind);
     }
   }
 
-  private void processAnnotation(ProgramDefinition annotatedItem, DexAnnotation annotation) {
+  private void processAnnotation(
+      ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind kind) {
     DexType type = annotation.getAnnotationType();
     DexClass clazz = definitionFor(type, annotatedItem);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
-    if (!shouldKeepAnnotation(appView, annotatedItem.getDefinition(), annotation, isLive)) {
+    if (!shouldKeepAnnotation(appView, annotatedItem, annotation, isLive, kind)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
+        Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
+            kind.isParameter() ? deferredParameterAnnotations : this.deferredAnnotations;
         Map<DexAnnotation, List<ProgramDefinition>> deferredAnnotationsForAnnotationType =
             deferredAnnotations.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
         deferredAnnotationsForAnnotationType
@@ -3915,7 +3942,8 @@
     method
         .getDefinition()
         .getParameterAnnotations()
-        .forEachAnnotation(annotation -> processAnnotation(method, annotation));
+        .forEachAnnotation(
+            annotation -> processAnnotation(method, annotation, AnnotatedKind.PARAMETER));
   }
 
   private void traceNonDesugaredCode(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index 07f625d..e509230 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -22,5 +22,11 @@
 
   boolean isKeepInnerClassesAttributeEnabled();
 
+  boolean isKeepRuntimeInvisibleAnnotationsEnabled();
+
+  boolean isKeepRuntimeInvisibleParameterAnnotationsEnabled();
+
   boolean isKeepRuntimeVisibleAnnotationsEnabled();
+
+  boolean isKeepRuntimeVisibleParameterAnnotationsEnabled();
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 7e9b166..ddd5bfc 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
 import com.android.tools.r8.utils.InternalOptions;
@@ -86,6 +87,10 @@
     return clazz == null ? keepInfoForNonProgramClass() : getClassInfo(clazz);
   }
 
+  public final KeepMemberInfo<?, ?> getMemberInfo(ProgramMember<?, ?> member) {
+    return getMemberInfo(member.getDefinition(), member.getHolder());
+  }
+
   public final KeepMethodInfo getMethodInfo(ProgramMethod method) {
     return getMethodInfo(method.getDefinition(), method.getHolder());
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 4fc6914..2616514 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -80,6 +80,10 @@
         clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
   }
 
+  public static <T> boolean isEmpty(T[] array) {
+    return array.length == 0;
+  }
+
   public static boolean isSorted(int[] array) {
     for (int i = 0; i < array.length - 1; i++) {
       assert array[i] < array[i + 1];
@@ -94,18 +98,6 @@
   /**
    * Rewrites the input array based on the given function.
    *
-   * @param clazz target type's Class to cast
-   * @param original an array of original elements
-   * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
-   * @return an array with written elements
-   */
-  public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
-    return map(original, mapper, clazz.cast(Array.newInstance(clazz.getComponentType(), 0)));
-  }
-
-  /**
-   * Rewrites the input array based on the given function.
-   *
    * @param original an array of original elements
    * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
    * @param emptyArray an empty array
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 89303ae..b75ac5a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -618,10 +618,25 @@
   }
 
   @Override
+  public boolean isKeepRuntimeInvisibleAnnotationsEnabled() {
+    return proguardConfiguration.getKeepAttributes().runtimeInvisibleAnnotations;
+  }
+
+  @Override
+  public boolean isKeepRuntimeInvisibleParameterAnnotationsEnabled() {
+    return proguardConfiguration.getKeepAttributes().runtimeInvisibleParameterAnnotations;
+  }
+
+  @Override
   public boolean isKeepRuntimeVisibleAnnotationsEnabled() {
     return proguardConfiguration.getKeepAttributes().runtimeVisibleAnnotations;
   }
 
+  @Override
+  public boolean isKeepRuntimeVisibleParameterAnnotationsEnabled() {
+    return proguardConfiguration.getKeepAttributes().runtimeVisibleParameterAnnotations;
+  }
+
   /**
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
diff --git a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index e9e7a58..064d98f 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -7,6 +7,7 @@
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
 import lambdadesugaringnplus.other.ClassWithDefaultPackagePrivate;
 import lambdadesugaringnplus.other.InterfaceWithDefaultPackagePrivate;
 
@@ -372,8 +373,15 @@
       // I don't know how to keep this method moved to the companion class
       // without the direct call.
       AnnotatedInterface.annotatedStaticMethod();
-      if (checkAnnotationValue(
-          getCompanionClassOrInterface().getMethod("annotatedStaticMethod").getAnnotations(), 4)) {
+      Method annotatedStaticMethod =
+          getCompanionClassOrInterface().getMethod("annotatedStaticMethod");
+      if (checkAnnotationValue(annotatedStaticMethod.getAnnotations(), 4)) {
+        System.out.println("Check 4: OK");
+      } else if (isR8()
+          && !isProguardCompatibilityMode()
+          && annotatedStaticMethod.getAnnotations().length == 0) {
+        // There is currently no way to retain annotations on static interface methods, since we
+        // drop -keep rules for such methods.
         System.out.println("Check 4: OK");
       } else {
         System.out.println("Check 4: NOT OK");
@@ -515,4 +523,12 @@
     B62168701.test();
     B78901754.test();
   }
+
+  public static boolean isR8() {
+    return false;
+  }
+
+  public static boolean isProguardCompatibilityMode() {
+    return false;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 1a9800a..f13a704 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -39,7 +39,7 @@
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
@@ -171,10 +171,10 @@
       D8Command.Builder builder = D8Command.builder();
       addClasspathReference(testJarFile, builder);
       for (String inputFile : inputFiles) {
-        builder = builder.addProgramFiles(Paths.get(inputFile));
+        builder.addProgramFiles(Paths.get(inputFile));
       }
-      for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
       if (outputPath != null) {
         builder.setOutput(outputPath, outputMode);
@@ -207,8 +207,8 @@
       for (ProgramResource dexFile : dexFiles) {
         builder.addDexProgramData(readResource(dexFile), dexFile.getOrigin());
       }
-      for (UnaryOperator<Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
       if (outputPath != null) {
         builder.setOutput(outputPath, outputMode);
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index 1bf86e3..b4cc992 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.OffOrAuto;
 import java.nio.file.Path;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
@@ -34,8 +34,8 @@
     @Override
     void build(Path inputFile, Path out, OutputMode mode) throws CompilationFailedException {
       D8Command.Builder builder = D8Command.builder().setOutput(out, mode);
-      for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
       builder.addLibraryFiles(
           ToolHelper.getAndroidJar(
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index eebc9fe..ba0ced5 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -26,7 +26,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,16 +41,24 @@
       "-allowaccessmodification"
   );
 
-  private static final ArrayList<String> PROGUARD_OPTIONS_N_PLUS =
-      Lists.newArrayList(
-          "-keepclasseswithmembers public class * {",
-          "    public static void main(java.lang.String[]);",
-          "}",
-          "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
-          "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
-          "-keepattributes *Annotation*",
-          "-dontobfuscate",
-          "-allowaccessmodification");
+  private static ArrayList<String> getProguardOptionsNPlus(
+      boolean enableProguardCompatibilityMode) {
+    return Lists.newArrayList(
+        "-keepclasseswithmembers public class * {",
+        "    public static void main(java.lang.String[]);",
+        "}",
+        "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
+        "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
+        "-keepattributes *Annotation*",
+        "-dontobfuscate",
+        "-allowaccessmodification",
+        "-assumevalues class lambdadesugaringnplus.LambdasWithStaticAndDefaultMethods {",
+        "  public static boolean isR8() return true;",
+        "  public static boolean isProguardCompatibilityMode() return "
+            + enableProguardCompatibilityMode
+            + ";",
+        "}");
+  }
 
   private static Map<DexVm.Version, List<String>> alsoFailsOn =
       ImmutableMap.<DexVm.Version, List<String>>builder()
@@ -161,22 +169,37 @@
   @Override
   @Test
   public void lambdaDesugaringNPlus() throws Throwable {
+    lambdaDesugaringNPlus(false);
+  }
+
+  @Test
+  public void lambdaDesugaringNPlusCompat() throws Throwable {
+    lambdaDesugaringNPlus(true);
+  }
+
+  private void lambdaDesugaringNPlus(boolean enableProguardCompatibilityMode) throws Throwable {
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
         .run();
   }
@@ -184,22 +207,39 @@
   @Test
   @IgnoreIfVmOlderThan(Version.V7_0_0)
   public void lambdaDesugaringNPlusWithDefaultMethods() throws Throwable {
+    lambdaDesugaringNPlusWithDefaultMethods(false);
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void lambdaDesugaringNPlusWithDefaultMethodsCompat() throws Throwable {
+    lambdaDesugaringNPlusWithDefaultMethods(true);
+  }
+
+  private void lambdaDesugaringNPlusWithDefaultMethods(boolean enableProguardCompatibilityMode)
+      throws Throwable {
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+        .withProguardCompatibilityMode(enableProguardCompatibilityMode)
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
-            b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+            b ->
+                b.addProguardConfiguration(
+                    getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
         .run();
   }
@@ -236,6 +276,8 @@
 
   class R8TestRunner extends TestRunner<R8TestRunner> {
 
+    private boolean enableProguardCompatibilityMode = false;
+
     R8TestRunner(String testName, String packageName, String mainClass) {
       super(testName, packageName, mainClass);
     }
@@ -253,11 +295,18 @@
               .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
     }
 
+    public R8TestRunner withProguardCompatibilityMode(boolean enableProguardCompatibilityMode) {
+      this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+      return this;
+    }
+
     @Override
     void build(Path inputFile, Path out, OutputMode mode) throws Throwable {
-      R8Command.Builder builder = R8Command.builder().setOutput(out, mode);
-      for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
-        builder = transformation.apply(builder);
+      CompatProguardCommandBuilder builder =
+          new CompatProguardCommandBuilder(enableProguardCompatibilityMode);
+      builder.setOutput(out, mode);
+      for (Consumer<R8Command.Builder> transformation : builderTransformations) {
+        transformation.accept(builder);
       }
 
       builder.addLibraryFiles(ToolHelper.getAndroidJar(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index bd9bd27..1dd7194 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -49,7 +49,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 import java.util.zip.ZipFile;
 import org.junit.Assert;
@@ -74,7 +73,7 @@
 
     final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
     final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
-    final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+    final List<Consumer<B>> builderTransformations = new ArrayList<>();
 
     TestRunner(String testName, String packageName, String mainClass) {
       this.testName = testName;
@@ -131,7 +130,6 @@
             } else {
               fail("Unexpected builder type: " + builder.getClass());
             }
-            return builder;
           });
     }
 
@@ -143,7 +141,7 @@
       return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
     }
 
-    C withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+    C withBuilderTransformation(Consumer<B> builderTransformation) {
       builderTransformations.add(builderTransformation);
       return self();
     }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index c421c83..73abcee 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -351,6 +351,14 @@
     return addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
   }
 
+  public T addKeepRuntimeInvisibleAnnotations() {
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS);
+  }
+
+  public T addKeepRuntimeInvisibleParameterAnnotations() {
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
+  }
+
   public T addKeepRuntimeVisibleAnnotations() {
     return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS);
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index 385a3fe..3858a1e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -6,45 +6,72 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.List;
 import org.junit.Test;
+import org.junit.runners.Parameterized;
 
 public class NoClassesOrMembersWithAnnotationsTest extends HorizontalClassMergingTestBase {
 
-  public NoClassesOrMembersWithAnnotationsTest(TestParameters parameters) {
+  private final boolean enableProguardCompatibilityMode;
+
+  @Parameterized.Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public NoClassesOrMembersWithAnnotationsTest(
+      boolean enableProguardCompatibilityMode, TestParameters parameters) {
     super(parameters);
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
   }
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(A.class, C.class))
+            inspector -> {
+              if (enableProguardCompatibilityMode) {
+                inspector.assertIsCompleteMergeGroup(A.class, C.class);
+              } else {
+                inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class);
+              }
+            })
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(
-            "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2")
+        .applyIf(
+            enableProguardCompatibilityMode,
+            result ->
+                result.assertSuccessWithOutputLines(
+                    "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2"),
+            result ->
+                result.assertSuccessWithOutputLines("a", "b", "c", "foo", "null", "annotation 2"))
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(TypeAnnotation.class), isPresent());
               assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class),
+                  onlyIf(enableProguardCompatibilityMode, isPresent()));
               assertThat(codeInspector.clazz(C.class), isAbsent());
             });
   }
@@ -110,14 +137,18 @@
               return null;
             }
           });
-      System.out.println(
-          b.getClass().getAnnotations()[0].toString().replaceFirst(".*", "annotation 1"));
-      System.out.println(
-          c.getClass()
-              .getMethods()[0]
-              .getAnnotations()[0]
-              .toString()
-              .replaceFirst(".*", "annotation 2"));
+      if (b.getClass().getAnnotations().length > 0) {
+        System.out.println(
+            b.getClass().getAnnotations()[0].toString().replaceFirst(".*", "annotation 1"));
+      }
+      if (c.getClass().getMethods()[0].getAnnotations().length > 0) {
+        System.out.println(
+            c.getClass()
+                .getMethods()[0]
+                .getAnnotations()[0]
+                .toString()
+                .replaceFirst(".*", "annotation 2"));
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
index 795837e..c3a7cfc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.classmerging.vertical;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -41,7 +40,7 @@
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(VerticalClassMergingWithNonVisibleAnnotationTestClasses.class)
         .addProgramClasses(Sub.class)
         .setMinApi(parameters.getApiLevel())
@@ -50,14 +49,14 @@
             VerticalClassMergingWithNonVisibleAnnotationTestClasses.class.getTypeName()
                 + "$Private* { *; }")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(Base.class))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .run(parameters.getRuntime(), Sub.class)
         .assertSuccessWithOutputLines("Base::foo()", "Sub::bar()")
         .inspect(
             codeInspector -> {
-              // Assert that base has been vertically merged into Sub.
-              assertThat(codeInspector.clazz(Base.class), not(isPresent()));
               ClassSubject sub = codeInspector.clazz(Sub.class);
               assertThat(sub, isPresent());
               // Assert that the merged class has no annotations from Base
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
index 6382436..c6094ae 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -42,9 +42,8 @@
     Assume.assumeFalse(
         "The methods values and valueOf are required for reflection.",
         enumKeepRules.toString().equals("none"));
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(AnnotationEnumUnboxingTest.class)
-        .noMinification()
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepClassRules(ClassAnnotationDefault.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
index 2e3904a..339bbbc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -36,35 +36,42 @@
 @RunWith(Parameterized.class)
 public class UninstantiatedAnnotatedArgumentsTest extends TestBase {
 
+  private final boolean enableProguardCompatibilityMode;
   private final boolean keepUninstantiatedArguments;
   private final TestParameters parameters;
 
-  @Parameters(name = "{1}, keep uninstantiated arguments: {0}")
+  @Parameters(name = "{2}, compat: {0}, keep uninstantiated arguments: {1}")
   public static List<Object[]> params() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public UninstantiatedAnnotatedArgumentsTest(
-      boolean keepUninstantiatedArguments, TestParameters parameters) {
+      boolean enableProguardCompatibilityMode,
+      boolean keepUninstantiatedArguments,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
     this.keepUninstantiatedArguments = keepUninstantiatedArguments;
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(UninstantiatedAnnotatedArgumentsTest.class)
         .addConstantArgumentAnnotations()
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(Instantiated.class, Uninstantiated.class)
-        .addKeepAttributes("RuntimeVisibleParameterAnnotations")
+        .addKeepRuntimeVisibleParameterAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableConstantArgumentAnnotations(keepUninstantiatedArguments)
         .enableInliningAnnotations()
         .enableUnusedArgumentAnnotations()
         // TODO(b/123060011): Mapping not working in presence of argument removal.
         .minification(keepUninstantiatedArguments)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyOutput)
         .run(parameters.getRuntime(), TestClass.class)
@@ -94,38 +101,35 @@
       assertThat(methodSubject, isPresent());
 
       // TODO(b/131735725): Should also remove arguments from the virtual methods.
-      if (keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual")) {
-        assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
+      boolean shouldHaveArgumentRemoval =
+          keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual");
+      if (shouldHaveArgumentRemoval) {
+        assertEquals(3, methodSubject.getMethod().getParameters().size());
 
-        for (int i = 0; i < 3; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
-
-          DexAnnotation annotation = annotationSet.annotations[0];
-          if (i == getPositionOfUnusedArgument(methodSubject)) {
-            assertEquals(
-                uninstantiatedClassSubject.getFinalName(),
-                annotation.annotation.type.toSourceString());
-          } else {
-            assertEquals(
-                instantiatedClassSubject.getFinalName(),
-                annotation.annotation.type.toSourceString());
-          }
-        }
+        // In non-compat mode, R8 removes annotations from non-pinned items.
+        assertEquals(
+            enableProguardCompatibilityMode ? 3 : 0,
+            methodSubject.getMethod().getParameterAnnotations().size());
       } else {
         assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
+        assertEquals(
+            enableProguardCompatibilityMode ? 2 : 0,
+            methodSubject.getMethod().getParameterAnnotations().size());
+      }
 
-        for (int i = 0; i < 2; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
+      for (int i = 0; i < methodSubject.getMethod().getParameterAnnotations().size(); ++i) {
+        DexAnnotationSet annotationSet = methodSubject.getMethod().getParameterAnnotation(i);
+        assertEquals(1, annotationSet.size());
 
-          DexAnnotation annotation = annotationSet.annotations[0];
+        DexAnnotation annotation = annotationSet.getFirst();
+        if (shouldHaveArgumentRemoval && i == getPositionOfUnusedArgument(methodSubject)) {
           assertEquals(
-              instantiatedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
+              uninstantiatedClassSubject.getFinalName(),
+              annotation.getAnnotationType().getTypeName());
+        } else {
+          assertEquals(
+              instantiatedClassSubject.getFinalName(),
+              annotation.getAnnotationType().getTypeName());
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
index 955c5d4..7cf6193 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -35,22 +35,30 @@
 @RunWith(Parameterized.class)
 public class UnusedAnnotatedArgumentsTest extends TestBase {
 
+  private final boolean enableProguardCompatibilityMode;
   private final boolean keepUnusedArguments;
   private final TestParameters parameters;
 
-  @Parameters(name = "{1}, keep unused arguments: {0}")
+  @Parameters(name = "{2}, compat: {0}, keep unused arguments: {1}")
   public static List<Object[]> params() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public UnusedAnnotatedArgumentsTest(boolean keepUnusedArguments, TestParameters parameters) {
+  public UnusedAnnotatedArgumentsTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepUnusedArguments,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
     this.keepUnusedArguments = keepUnusedArguments;
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(UnusedAnnotatedArgumentsTest.class)
         .addUnusedArgumentAnnotations()
         .addKeepMainRule(TestClass.class)
@@ -61,7 +69,7 @@
         .enableUnusedArgumentAnnotations(keepUnusedArguments)
         // TODO(b/123060011): Mapping not working in presence of unused argument removal.
         .minification(keepUnusedArguments)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyOutput)
         .run(parameters.getRuntime(), TestClass.class)
@@ -90,36 +98,24 @@
     for (MethodSubject methodSubject : methodSubjects) {
       assertThat(methodSubject, isPresent());
 
-      if (keepUnusedArguments) {
-        assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
+      assertEquals(keepUnusedArguments ? 3 : 2, methodSubject.getMethod().getParameters().size());
 
-        for (int i = 0; i < 3; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
+      // R8 non-compat removes annotations from non-pinned items.
+      assertEquals(
+          enableProguardCompatibilityMode ? methodSubject.getMethod().getParameters().size() : 0,
+          methodSubject.getMethod().getParameterAnnotations().size());
 
-          DexAnnotation annotation = annotationSet.annotations[0];
-          if (i == getPositionOfUnusedArgument(methodSubject)) {
-            assertEquals(
-                unusedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
-          } else {
-            assertEquals(
-                usedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
-          }
-        }
-      } else {
-        assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
-        assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
+      for (int i = 0; i < methodSubject.getMethod().getParameterAnnotations().size(); ++i) {
+        DexAnnotationSet annotationSet = methodSubject.getMethod().getParameterAnnotation(i);
+        assertEquals(1, annotationSet.size());
 
-        for (int i = 0; i < 2; ++i) {
-          DexAnnotationSet annotationSet =
-              methodSubject.getMethod().parameterAnnotationsList.get(i);
-          assertEquals(1, annotationSet.annotations.length);
-
-          DexAnnotation annotation = annotationSet.annotations[0];
+        DexAnnotation annotation = annotationSet.annotations[0];
+        if (keepUnusedArguments && i == getPositionOfUnusedArgument(methodSubject)) {
           assertEquals(
-              usedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
+              unusedClassSubject.getFinalName(), annotation.getAnnotationType().toSourceString());
+        } else {
+          assertEquals(
+              usedClassSubject.getFinalName(), annotation.getAnnotationType().toSourceString());
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
index 49b90e7..c008972 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
@@ -11,10 +11,11 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,14 +29,18 @@
 public class UnusedAnnotatedArgumentsWithMissingAnnotationsTest extends TestBase
     implements Opcodes {
 
+  private final boolean enableProguardCompatibilityMode;
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameterized.Parameters(name = "{1}, compat: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(TestParameters parameters) {
+  public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(
+      boolean enableProguardCompatibilityMode, TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
     this.parameters = parameters;
   }
 
@@ -43,26 +48,28 @@
     assertThat(clazz, isPresent());
     MethodSubject init = clazz.init("Test", "java.lang.String");
     assertThat(init, isPresent());
-    assertEquals(2, init.getMethod().parameterAnnotationsList.size());
-    assertTrue(init.getMethod().parameterAnnotationsList.get(0).isEmpty());
-    assertEquals(1, init.getMethod().parameterAnnotationsList.get(1).annotations.length);
-    assertEquals(
-        "L" + expectedAnnotationClass + ";",
-        init.getMethod()
-            .parameterAnnotationsList
-            .get(1)
-            .annotations[0]
-            .annotation
-            .type
-            .descriptor
-            .toString());
+    if (enableProguardCompatibilityMode) {
+      assertEquals(2, init.getMethod().getParameterAnnotations().size());
+      assertTrue(init.getMethod().getParameterAnnotation(0).isEmpty());
+      assertEquals(1, init.getMethod().getParameterAnnotation(1).size());
+      assertEquals(
+          "L" + expectedAnnotationClass + ";",
+          init.getMethod()
+              .getParameterAnnotation(1)
+              .annotations[0]
+              .annotation
+              .type
+              .toDescriptorString());
+    } else {
+      assertTrue(init.getMethod().getParameterAnnotations().isEmpty());
+    }
   }
 
   @Test
   public void test() throws Exception {
     String expectedOutput =
         StringUtils.lines("In Inner1() used", "In Inner2() used", "In Inner3() used", "In main()");
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addProgramClassFileData(
             dumpTest(),
             dumpInner1(),
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index cc558a1..5ed7de6 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -171,8 +171,7 @@
         .inspector();
   }
 
-  private CodeInspector compileR8(Class... classes)
-      throws CompilationFailedException, IOException, ExecutionException {
+  private CodeInspector compileR8(Class... classes) throws CompilationFailedException, IOException {
     List<String> keepRules =
         Arrays.stream(classes)
             .map(c -> "-keep class " + c.getCanonicalName() + " { <methods>; }")
@@ -186,11 +185,16 @@
         // Keep the input class and its methods.
         .addKeepRules(keepRules)
         // Keep the annotation class.
-        .addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
+        .addKeepRules(
+            "-keep class dalvik.annotation.optimization.ReachabilitySensitive",
+            "-keep,allowshrinking,allowobfuscation class * {",
+            "  @dalvik.annotation.optimization.ReachabilitySensitive <fields>;",
+            "  @dalvik.annotation.optimization.ReachabilitySensitive <methods>;",
+            "}")
         // Keep the annotation so R8 can find it and honor it. It also needs to be available
         // at runtime so that the Art runtime can honor it as well, so if it is not kept we
         // do not have to honor it as the runtime will not know to do so in any case.
-        .addKeepRules("-keepattributes RuntimeVisibleAnnotations")
+        .addKeepRuntimeVisibleAnnotations()
         .compile()
         .inspector();
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
index 61526d4..fe19c51 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
@@ -4,29 +4,61 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class RepackageAnnotationTest extends RepackageTestBase {
 
-  private final String EXPECTED = "Hello World";
+  private static final String EXPECTED = "Hello World";
+  private static final String EXPECTED_WITH_ANNOTATION_REMOVAL = "null";
+
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepAllowShrinking;
+
+  @Parameters(name = "{3}, compat: {0}, keep: {1}, kind: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
 
   public RepackageAnnotationTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+      boolean enableProguardCompatibilityMode,
+      boolean keepAllowShrinking,
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters) {
     super(flattenPackageHierarchyOrRepackageClasses, parameters);
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepAllowShrinking = keepAllowShrinking;
   }
 
   @Test
   public void testRuntime() throws Exception {
+    assumeFalse(enableProguardCompatibilityMode);
+    assumeFalse(keepAllowShrinking);
+    assumeFalse(isFlattenPackageHierarchy());
     testForRuntime(parameters)
         .addProgramClasses(Main.class, Annotation.class, A.class)
         .run(parameters.getRuntime(), Main.class)
@@ -35,8 +67,17 @@
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
+    assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addProgramClasses(Main.class, A.class, Annotation.class)
+        .applyIf(
+            keepAllowShrinking,
+            builder -> {
+              // Add a keep rule to ensure annotation is retained with R8 non-compat.
+              assertFalse(enableProguardCompatibilityMode);
+              builder.addKeepRules(
+                  "-keep,allowshrinking,allowobfuscation class " + A.class.getTypeName());
+            })
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
@@ -47,8 +88,13 @@
             "  *;",
             "}")
         .apply(this::configureRepackaging)
+        .compile()
+        .inspect(inspector -> assertThat(Annotation.class, isRepackaged(inspector)))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(EXPECTED);
+        .assertSuccessWithOutputLines(
+            enableProguardCompatibilityMode || keepAllowShrinking
+                ? EXPECTED
+                : EXPECTED_WITH_ANNOTATION_REMOVAL);
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
index 4672079..34cd1af 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
@@ -30,7 +30,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
index 46a83ff..af48e82 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
@@ -31,7 +31,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
index 068cc89..20f8704 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
@@ -30,7 +30,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
index c7cd698..d9833ae 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
@@ -30,7 +30,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
index 597c835..6e25e4ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.shaking.annotations;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assume.assumeTrue;
@@ -77,14 +79,19 @@
 
               AnnotationSubject targetAnnotationSubject =
                   annotationClassSubject.annotation(Target.class.getTypeName());
-              assertThat(targetAnnotationSubject, isPresent());
+              assertThat(targetAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
 
               AnnotationSubject myAnnotationAnnotationSubject =
                   annotationClassSubject.annotation(MyAnnotation.class.getTypeName());
-              assertThat(myAnnotationAnnotationSubject, isPresent());
+              assertThat(
+                  myAnnotationAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
             })
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("3");
+        .assertSuccessWithOutputLines(shouldOnlyRetainRetention() ? "1" : "3");
+  }
+
+  private boolean shouldOnlyRetainRetention() {
+    return !enableProguardCompatibilityMode && !keepAllowShrinking;
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
index 9c98c6e..e98541c 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
@@ -59,6 +59,10 @@
         .addProgramClasses(Factory.class, MainWithMethodAnnotation.class, C.class)
         .addKeepMainRule(MainWithMethodAnnotation.class)
         .addKeepClassAndMembersRules(Factory.class)
+        .addKeepRules(
+            "-keepclassmembers,allowobfuscation,allowshrinking class "
+                + MainWithMethodAnnotation.class.getTypeName()
+                + "{void test();}")
         .addKeepAttributes("*Annotation*")
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -123,7 +127,7 @@
       test();
     }
 
-    @Factory(ref = C.class) // <-- We are not explicitly saying that test() should be kept.
+    @Factory(ref = C.class) // <-- We are explicitly saying that test() should be kept.
     @NeverInline
     public static void test() {
       System.out.println("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
index 9246164..d737c41 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -4,13 +4,16 @@
 
 package com.android.tools.r8.shaking.annotations;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
@@ -18,6 +21,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,29 +36,56 @@
           "In OtherInterfaceImpl.targetedMethod()",
           MyAnnotation.class.getName());
 
+  private static final String expectedOutputWithAnnotationRemoval =
+      StringUtils.lines(
+          "In InterfaceImpl.targetedMethod()", "In OtherInterfaceImpl.targetedMethod()");
+
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepAllowShrinking;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{2}, compat: {0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public AnnotationsOnTargetedMethodTest(TestParameters parameters) {
+  public AnnotationsOnTargetedMethodTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepAllowShrinking,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepAllowShrinking = keepAllowShrinking;
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
+    // No need to run R8 compat mode with extra -keep,allowshrinking rule.
+    assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
     if (parameters.isCfRuntime()) {
       testForJvm()
           .addTestClasspath()
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutput(expectedOutput);
     }
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(AnnotationsOnTargetedMethodTest.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRuntimeVisibleAnnotations()
+        .applyIf(
+            keepAllowShrinking,
+            builder -> {
+              // Add extra rule to retain the annotation on Interface.targetedMethod() in non-compat
+              // mode.
+              assertFalse(enableProguardCompatibilityMode); // See assumeTrue() above.
+              builder.addKeepRules(
+                  "-keepclassmembers,allowshrinking class " + Interface.class.getTypeName() + " {",
+                  "  void targetedMethod();",
+                  "}");
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -62,7 +93,10 @@
         .noMinification()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
+        .assertSuccessWithOutput(
+            !enableProguardCompatibilityMode && !keepAllowShrinking
+                ? expectedOutputWithAnnotationRemoval
+                : expectedOutput);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
index 13fbb3e..da0b781 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -43,12 +45,21 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void testCompat() throws Exception {
+    runTest(true);
+  }
+
+  @Test
+  public void testNonCompat() throws Exception {
+    runTest(false);
+  }
+
+  private void runTest(boolean enableCompatMode) throws Exception {
     R8TestRunResult result =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend(), enableCompatMode)
             .addInnerClasses(ProgramAnnotationRemovalTest.class)
             .addKeepMainRule(TestClass.class)
-            .addKeepAttributes("RuntimeVisibleAnnotations")
+            .addKeepRuntimeVisibleAnnotations()
             .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
@@ -68,14 +79,19 @@
     MethodSubject methodWithLiveProgramAnnotationSubject =
         testClassSubject.uniqueMethodWithName("methodWithLiveProgramAnnotation");
     assertThat(methodWithLiveProgramAnnotationSubject, isPresent());
-    assertEquals(1, methodWithLiveProgramAnnotationSubject.getMethod().annotations().size());
+    assertEquals(
+        BooleanUtils.intValue(enableCompatMode),
+        methodWithLiveProgramAnnotationSubject.getMethod().annotations().size());
 
     MethodSubject methodWithDeadProgramAnnotationSubject =
         testClassSubject.uniqueMethodWithName("methodWithDeadProgramAnnotation");
     assertThat(methodWithDeadProgramAnnotationSubject, isPresent());
     assertEquals(0, methodWithDeadProgramAnnotationSubject.getMethod().annotations().size());
 
-    result.assertSuccessWithOutputLines("@" + liveAnnotationClassSubject.getFinalName() + "()");
+    result.applyIf(
+        enableCompatMode,
+        r -> r.assertSuccessWithOutputLines("@" + liveAnnotationClassSubject.getFinalName() + "()"),
+        TestRunResult::assertSuccessWithEmptyOutput);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
index 16c7d4f..187d79c 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
@@ -8,21 +8,27 @@
 import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,27 +38,50 @@
 @RunWith(Parameterized.class)
 public class PrunedOrMergedAnnotationTest extends TestBase {
 
+  private final boolean enableProguardCompatibilityMode;
+  private final boolean keepForAnnotations;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{2}, compat: {0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public PrunedOrMergedAnnotationTest(TestParameters parameters) {
+  public PrunedOrMergedAnnotationTest(
+      boolean enableProguardCompatibilityMode,
+      boolean keepForAnnotations,
+      TestParameters parameters) {
+    this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+    this.keepForAnnotations = keepForAnnotations;
     this.parameters = parameters;
   }
 
   @Test
   public void testRewritingInFactory()
       throws IOException, CompilationFailedException, ExecutionException {
-    testForR8(parameters.getBackend())
+    // No need to add extra keep rules for retaining annotations in compat mode.
+    assumeTrue(!enableProguardCompatibilityMode || !keepForAnnotations);
+    testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
         .addInnerClasses(PrunedOrMergedAnnotationTest.class)
         .addKeepMainRule(Main.class)
-        .addKeepAttributes("*Annotation*")
         .addKeepClassAndMembersRules(Factory.class)
+        .addKeepRuntimeInvisibleAnnotations()
+        .addKeepRuntimeInvisibleParameterAnnotations()
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(A.class))
+        .applyIf(
+            keepForAnnotations,
+            builder -> {
+              assertFalse(enableProguardCompatibilityMode);
+              builder.addKeepRules(
+                  "-keep,allowshrinking,allowobfuscation class " + C.class.getTypeName());
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello", "World!")
@@ -62,15 +91,21 @@
               DexType mergedType = inspector.clazz(B.class).getDexProgramClass().type;
               ClassSubject classC = inspector.clazz(C.class);
               assertThat(classC, isPresent());
-              DexEncodedAnnotation annotation =
-                  classC.annotation(Factory.class.getTypeName()).getAnnotation();
-              assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
-              assertTrue(
-                  Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
-                      .allMatch(value -> valueIsDexType(mergedType, value)));
-              // Check that method parameter annotations are rewritten as well.
-              DexEncodedMethod method = inspector.clazz(Main.class).mainMethod().getMethod();
-              DexAnnotationSet annotationSet = method.parameterAnnotationsList.get(0);
+
+              MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+              if (enableProguardCompatibilityMode || keepForAnnotations) {
+                DexEncodedAnnotation annotation =
+                    classC.annotation(Factory.class.getTypeName()).getAnnotation();
+                assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
+                assertTrue(
+                    Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
+                        .allMatch(value -> valueIsDexType(mergedType, value)));
+              } else {
+                assertTrue(classC.getDexProgramClass().annotations().isEmpty());
+              }
+
+              // Check that method parameter annotations are rewritten.
+              DexAnnotationSet annotationSet = mainMethod.getMethod().getParameterAnnotation(0);
               DexEncodedAnnotation parameterAnnotation = annotationSet.annotations[0].annotation;
               assertTrue(valueIsDexType(mergedType, parameterAnnotation.elements[0].value));
             });
@@ -82,6 +117,7 @@
     return true;
   }
 
+  @Retention(RetentionPolicy.CLASS)
   public @interface Factory {
 
     Class<?> extending() default Object.class;
@@ -92,6 +128,7 @@
   public static class A {}
 
   @NeverClassInline
+  @NoHorizontalClassMerging
   public static class B extends A {
     @NeverInline
     public void world() {
@@ -102,6 +139,7 @@
   @Factory(
       extending = A.class,
       other = {A.class, B.class})
+  @NoHorizontalClassMerging
   public static class C {
     @NeverInline
     public static void hello() {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index f102535..662ad5e 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -95,7 +95,7 @@
   @Test
   public void b120951621_keepAll() throws Exception {
     CodeInspector inspector =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend())
             .addProgramFiles(
                 compiledJars.getForConfiguration(kotlinc, targetVersion),
                 getKotlinAnnotationJar(kotlinc))
@@ -133,7 +133,7 @@
   @Test
   public void b120951621_partiallyKeep() throws Exception {
     CodeInspector inspector =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend())
             .addProgramFiles(
                 compiledJars.getForConfiguration(kotlinc, targetVersion),
                 getKotlinAnnotationJar(kotlinc))
@@ -175,7 +175,7 @@
   @Test
   public void b120951621_keepAnnotation() throws Exception {
     CodeInspector inspector =
-        testForR8(parameters.getBackend())
+        testForR8Compat(parameters.getBackend())
             .addProgramFiles(
                 compiledJars.getForConfiguration(kotlinc, targetVersion),
                 getKotlinAnnotationJar(kotlinc))
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
index 61a9845..50f1954 100644
--- a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
@@ -50,6 +50,7 @@
                     "-keepclassmembernames class " + Enum.class.getTypeName() + " { <fields>; }"))
         .setMinApi(parameters.getApiLevel())
         .addKeepRuntimeVisibleAnnotations()
+        .compile()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("TEST_ONE");
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 9933b84..e00615a 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -6,7 +6,9 @@
 
 import static com.android.tools.r8.references.Reference.classFromClass;
 import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -122,7 +124,6 @@
                 mainClass, MentionedClass.class, mentionedClassWithAnnotations, annotationClass)
             .addKeepMainRule(mainClass)
             .allowAccessModification()
-            .noMinification()
             .addKeepClassAndMembersRules(annotationClass)
             .map(b -> keepAnnotations ? b.addKeepAttributes("*Annotation*") : b)
             .setMinApi(parameters.getApiLevel())
@@ -134,10 +135,15 @@
     assertThat(clazz, isPresent());
 
     // The test contains only a member class so the enclosing-method attribute will be null.
-    assertEquals(
-        forceProguardCompatibility, !clazz.getDexProgramClass().getInnerClasses().isEmpty());
-    assertEquals(forceProguardCompatibility || keepAnnotations,
-        clazz.annotation(annotationClass.getCanonicalName()).isPresent());
+    assertTrue(clazz.getDexProgramClass().getInnerClasses().isEmpty());
+
+    if (keepAnnotations) {
+      assertThat(
+          clazz.annotation(annotationClass.getCanonicalName()),
+          onlyIf(forceProguardCompatibility, isPresent()));
+    } else {
+      assertThat(clazz.annotation(annotationClass.getCanonicalName()), isAbsent());
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
index 880ed52..edbcd98 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -54,8 +54,11 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(codeInspector,
-        UsedAnnotation.class, UsedAnnotationDependent.class);
+    if (shrinker.isFullModeR8()) {
+      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    } else {
+      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    }
   }
 
   @Test
@@ -80,8 +83,11 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(codeInspector,
-        UsedAnnotation.class, UsedAnnotationDependent.class);
+    if (shrinker.isFullModeR8()) {
+      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    } else {
+      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    }
   }
 
   @Test
@@ -104,8 +110,11 @@
     CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(codeInspector,
-        UsedAnnotation.class, UsedAnnotationDependent.class);
+    if (shrinker.isFullModeR8()) {
+      verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    } else {
+      verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
index d578ae2..b97fd41 100644
--- a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -6,12 +6,29 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import java.util.Map;
 import java.util.function.Function;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
-public class ArrayUtilsTest {
+@RunWith(Parameterized.class)
+public class ArrayUtilsTest extends TestBase {
+
+  private static final Integer[] EMPTY_INTEGER_ARRAY = new Integer[0];
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ArrayUtilsTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
 
   private Integer[] createInputData(int size) {
     Integer[] input = new Integer[size];
@@ -124,7 +141,7 @@
   public void testMap_identity() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, Function.identity());
+    Integer[] output = ArrayUtils.map(input, Function.identity(), EMPTY_INTEGER_ARRAY);
     assertEquals(input, output);
   }
 
@@ -132,7 +149,7 @@
   public void testMap_dropOdd() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? null : x);
+    Integer[] output = ArrayUtils.map(input, x -> x % 2 != 0 ? null : x, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(2, output.length);
     assertEquals(0, (int) output[0]);
@@ -143,7 +160,7 @@
   public void testMap_dropAll() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> null);
+    Integer[] output = ArrayUtils.map(input, x -> null, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(0, output.length);
   }
@@ -152,7 +169,7 @@
   public void testMap_double() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> 2 * x);
+    Integer[] output = ArrayUtils.map(input, x -> 2 * x, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(size, output.length);
     for (int i = 0; i < size; i++) {
@@ -164,7 +181,7 @@
   public void testMap_double_onlyOdd() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? 2 * x : x);
+    Integer[] output = ArrayUtils.map(input, x -> x % 2 != 0 ? 2 * x : x, EMPTY_INTEGER_ARRAY);
     assertNotEquals(input, output);
     assertEquals(size, output.length);
     for (int i = 0; i < size; i++) {