RewrittenProto: use Int2RefMap

- RemovedArguments has now Int2Ref Map
  over a list of elements where each item
  has the index.
- RemovedArguments empty has now an empty map
  instead of null (1 extra instance), removed
  corresponding null checks
- Made sure empty RemovedArguments use the
  empty instance
- Fixed a bug where arg 0 of static method was
  never considered as unused.

Bug: 148271981
Change-Id: I2ee0e2c20c1c670a0079ab4f12c563e075bf7da1
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index cbc8303..5c8de46 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -469,7 +469,7 @@
       } else {
         continue;
       }
-      if (index >= 0) {
+      if (index >= 0 && indexToNumber.containsKey(index)) {
         registry.register(indexToNumber.get(index));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 5c22878..a58aace 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -8,11 +8,9 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.IteratorUtils;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
 import java.util.function.Consumer;
 
 public class RewrittenPrototypeDescription {
@@ -21,15 +19,9 @@
 
     public static class Builder {
 
-      private int argumentIndex = -1;
       private boolean isAlwaysNull = false;
       private DexType type = null;
 
-      public Builder setArgumentIndex(int argumentIndex) {
-        this.argumentIndex = argumentIndex;
-        return this;
-      }
-
       public Builder setIsAlwaysNull() {
         this.isAlwaysNull = true;
         return this;
@@ -41,18 +33,15 @@
       }
 
       public RemovedArgumentInfo build() {
-        assert argumentIndex >= 0;
         assert type != null;
-        return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type);
+        return new RemovedArgumentInfo(isAlwaysNull, type);
       }
     }
 
-    private final int argumentIndex;
     private final boolean isAlwaysNull;
     private final DexType type;
 
-    private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) {
-      this.argumentIndex = argumentIndex;
+    private RemovedArgumentInfo(boolean isAlwaysNull, DexType type) {
       this.isAlwaysNull = isAlwaysNull;
       this.type = type;
     }
@@ -61,10 +50,6 @@
       return new Builder();
     }
 
-    public int getArgumentIndex() {
-      return argumentIndex;
-    }
-
     public DexType getType() {
       return type;
     }
@@ -76,61 +61,48 @@
     public boolean isNeverUsed() {
       return !isAlwaysNull;
     }
-
-    RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
-      return this.argumentIndex != argumentIndex
-          ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
-          : this;
-    }
   }
 
-  public static class RemovedArgumentsInfo {
+  public static class RemovedArgumentInfoCollection {
 
-    private static final RemovedArgumentsInfo empty = new RemovedArgumentsInfo(null);
+    private static final RemovedArgumentInfoCollection EMPTY = new RemovedArgumentInfoCollection();
 
-    private final List<RemovedArgumentInfo> removedArguments;
+    private final Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments;
 
-    public RemovedArgumentsInfo(List<RemovedArgumentInfo> removedArguments) {
-      assert verifyRemovedArguments(removedArguments);
+    // Specific constructor for empty.
+    private RemovedArgumentInfoCollection() {
+      this.removedArguments = new Int2ReferenceLinkedOpenHashMap<>();
+    }
+
+    private RemovedArgumentInfoCollection(
+        Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments) {
+      assert removedArguments != null : "should use empty.";
+      assert !removedArguments.isEmpty() : "should use empty.";
       this.removedArguments = removedArguments;
     }
 
-    private static boolean verifyRemovedArguments(List<RemovedArgumentInfo> removedArguments) {
-      if (removedArguments != null && !removedArguments.isEmpty()) {
-        // Check that list is sorted by argument indices.
-        int lastArgumentIndex = removedArguments.get(0).getArgumentIndex();
-        for (int i = 1; i < removedArguments.size(); ++i) {
-          int currentArgumentIndex = removedArguments.get(i).getArgumentIndex();
-          assert lastArgumentIndex < currentArgumentIndex;
-          lastArgumentIndex = currentArgumentIndex;
-        }
+    public static RemovedArgumentInfoCollection create(
+        Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments) {
+      if (removedArguments == null || removedArguments.isEmpty()) {
+        return EMPTY;
       }
-      return true;
+      return new RemovedArgumentInfoCollection(removedArguments);
     }
 
-    public static RemovedArgumentsInfo empty() {
-      return empty;
+    public static RemovedArgumentInfoCollection empty() {
+      return EMPTY;
     }
 
-    public ListIterator<RemovedArgumentInfo> iterator() {
-      return removedArguments == null
-          ? Collections.emptyListIterator()
-          : removedArguments.listIterator();
+    public RemovedArgumentInfo getArgumentInfo(int argIndex) {
+      return removedArguments.get(argIndex);
     }
 
     public boolean hasRemovedArguments() {
-      return removedArguments != null && !removedArguments.isEmpty();
+      return !removedArguments.isEmpty();
     }
 
     public boolean isArgumentRemoved(int argumentIndex) {
-      if (removedArguments != null) {
-        for (RemovedArgumentInfo info : removedArguments) {
-          if (info.getArgumentIndex() == argumentIndex) {
-            return true;
-          }
-        }
-      }
-      return false;
+      return removedArguments.containsKey(argumentIndex);
     }
 
     public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) {
@@ -156,8 +128,7 @@
       return removedArguments != null ? removedArguments.size() : 0;
     }
 
-    public RemovedArgumentsInfo combine(RemovedArgumentsInfo info) {
-      assert info != null;
+    public RemovedArgumentInfoCollection combine(RemovedArgumentInfoCollection info) {
       if (hasRemovedArguments()) {
         if (!info.hasRemovedArguments()) {
           return this;
@@ -166,19 +137,32 @@
         return info;
       }
 
-      List<RemovedArgumentInfo> newRemovedArguments = new LinkedList<>(removedArguments);
-      ListIterator<RemovedArgumentInfo> iterator = newRemovedArguments.listIterator();
+      Int2ReferenceSortedMap<RemovedArgumentInfo> newRemovedArguments =
+          new Int2ReferenceLinkedOpenHashMap<>();
+      newRemovedArguments.putAll(removedArguments);
+      IntBidirectionalIterator iterator = removedArguments.keySet().iterator();
       int offset = 0;
-      for (RemovedArgumentInfo pending : info.removedArguments) {
-        RemovedArgumentInfo next = IteratorUtils.peekNext(iterator);
-        while (next != null && next.getArgumentIndex() <= pending.getArgumentIndex() + offset) {
-          iterator.next();
-          next = IteratorUtils.peekNext(iterator);
+      for (int pendingArgIndex : info.removedArguments.keySet()) {
+        int nextArgindex = peekNextOrMax(iterator);
+        while (nextArgindex <= pendingArgIndex + offset) {
+          iterator.nextInt();
+          nextArgindex = peekNextOrMax(iterator);
           offset++;
         }
-        iterator.add(pending.withArgumentIndex(pending.getArgumentIndex() + offset));
+        assert !newRemovedArguments.containsKey(pendingArgIndex + offset);
+        newRemovedArguments.put(
+            pendingArgIndex + offset, info.removedArguments.get(pendingArgIndex));
       }
-      return new RemovedArgumentsInfo(newRemovedArguments);
+      return new RemovedArgumentInfoCollection(newRemovedArguments);
+    }
+
+    static int peekNextOrMax(IntBidirectionalIterator iterator) {
+      if (iterator.hasNext()) {
+        int i = iterator.nextInt();
+        iterator.previousInt();
+        return i;
+      }
+      return Integer.MAX_VALUE;
     }
 
     public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover(
@@ -198,16 +182,16 @@
 
   private final boolean hasBeenChangedToReturnVoid;
   private final boolean extraNullParameter;
-  private final RemovedArgumentsInfo removedArgumentsInfo;
+  private final RemovedArgumentInfoCollection removedArgumentsInfo;
 
   private RewrittenPrototypeDescription() {
-    this(false, false, RemovedArgumentsInfo.empty());
+    this(false, false, RemovedArgumentInfoCollection.empty());
   }
 
   private RewrittenPrototypeDescription(
       boolean hasBeenChangedToReturnVoid,
       boolean extraNullParameter,
-      RemovedArgumentsInfo removedArgumentsInfo) {
+      RemovedArgumentInfoCollection removedArgumentsInfo) {
     assert removedArgumentsInfo != null;
     this.extraNullParameter = extraNullParameter;
     this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid;
@@ -215,7 +199,7 @@
   }
 
   public static RewrittenPrototypeDescription createForUninstantiatedTypes(
-      boolean hasBeenChangedToReturnVoid, RemovedArgumentsInfo removedArgumentsInfo) {
+      boolean hasBeenChangedToReturnVoid, RemovedArgumentInfoCollection removedArgumentsInfo) {
     return new RewrittenPrototypeDescription(
         hasBeenChangedToReturnVoid, false, removedArgumentsInfo);
   }
@@ -227,7 +211,7 @@
   public boolean isEmpty() {
     return !extraNullParameter
         && !hasBeenChangedToReturnVoid
-        && !getRemovedArgumentsInfo().hasRemovedArguments();
+        && !getRemovedArgumentInfoCollection().hasRemovedArguments();
   }
 
   public boolean hasExtraNullParameter() {
@@ -238,7 +222,7 @@
     return hasBeenChangedToReturnVoid;
   }
 
-  public RemovedArgumentsInfo getRemovedArgumentsInfo() {
+  public RemovedArgumentInfoCollection getRemovedArgumentInfoCollection() {
     return removedArgumentsInfo;
   }
 
@@ -276,7 +260,7 @@
         : this;
   }
 
-  public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentsInfo other) {
+  public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentInfoCollection other) {
     return new RewrittenPrototypeDescription(
         hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 01dcf5d..a16e20e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -41,7 +41,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CanonicalPositions;
@@ -51,7 +51,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
@@ -158,11 +157,8 @@
       int register,
       DexEncodedMethod method,
       BiConsumer<Integer, DexType> writeCallback) {
-    RemovedArgumentsInfo removedArgumentsInfo =
-        builder.getPrototypeChanges().getRemovedArgumentsInfo();
-    ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator();
-    RemovedArgumentInfo nextRemovedArgument =
-        removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+    RemovedArgumentInfoCollection removedArgumentsInfo =
+        builder.getPrototypeChanges().getRemovedArgumentInfoCollection();
 
     // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
     // block.
@@ -182,14 +178,13 @@
 
     for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) {
       TypeLatticeElement type;
-      if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) {
-        writeCallback.accept(register, nextRemovedArgument.getType());
+      if (removedArgumentsInfo.isArgumentRemoved(argumentIndex)) {
+        RemovedArgumentInfo argumentInfo = removedArgumentsInfo.getArgumentInfo(argumentIndex);
+        writeCallback.accept(register, argumentInfo.getType());
         type =
             TypeLatticeElement.fromDexType(
-                nextRemovedArgument.getType(), Nullability.maybeNull(), builder.appView);
-        builder.addConstantOrUnusedArgument(register, nextRemovedArgument);
-        nextRemovedArgument =
-            removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+                argumentInfo.getType(), Nullability.maybeNull(), builder.appView);
+        builder.addConstantOrUnusedArgument(register, argumentInfo);
       } else {
         DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++];
         writeCallback.accept(register, dexType);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index a39a66b..d60b1e7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -448,11 +448,12 @@
     } else {
       this.prototypeChanges = appView.graphLense().lookupPrototypeChanges(method.method);
 
-      if (Log.ENABLED && prototypeChanges.getRemovedArgumentsInfo().hasRemovedArguments()) {
+      if (Log.ENABLED
+          && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
         Log.info(
             getClass(),
             "Removed "
-                + prototypeChanges.getRemovedArgumentsInfo().numberOfRemovedArguments()
+                + prototypeChanges.getRemovedArgumentInfoCollection().numberOfRemovedArguments()
                 + " arguments from "
                 + method.toSourceString());
       }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 246d865..7226924 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -29,7 +29,7 @@
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
@@ -177,7 +177,8 @@
           if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
             RewrittenPrototypeDescription prototypeChanges =
                 graphLense.lookupPrototypeChanges(actualTarget);
-            RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+            RemovedArgumentInfoCollection removedArgumentsInfo =
+                prototypeChanges.getRemovedArgumentInfoCollection();
 
             ConstInstruction constantReturnMaterializingInstruction = null;
             if (prototypeChanges.hasBeenChangedToReturnVoid() && invoke.outValue() != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c234bc3..2cba82b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.TypeChecker;
@@ -43,7 +43,8 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.BitSet;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -63,11 +64,11 @@
 
   public static class UninstantiatedTypeOptimizationGraphLense extends NestedGraphLense {
 
-    private final Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod;
+    private final Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod;
 
     UninstantiatedTypeOptimizationGraphLense(
         BiMap<DexMethod, DexMethod> methodMap,
-        Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod,
+        Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod,
         AppView<?> appView) {
       super(
           ImmutableMap.of(),
@@ -88,7 +89,8 @@
         if (method.proto.returnType.isVoidType() && !originalMethod.proto.returnType.isVoidType()) {
           result = result.withConstantReturn();
         }
-        RemovedArgumentsInfo removedArgumentsInfo = removedArgumentsInfoPerMethod.get(method);
+        RemovedArgumentInfoCollection removedArgumentsInfo =
+            removedArgumentsInfoPerMethod.get(method);
         if (removedArgumentsInfo != null) {
           result = result.withRemovedArguments(removedArgumentsInfo);
         }
@@ -125,7 +127,8 @@
 
     Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods = new HashMap<>();
     BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
-    Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod = new IdentityHashMap<>();
+    Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod =
+        new IdentityHashMap<>();
 
     TopDownClassHierarchyTraversal.forProgramClasses(appView)
         .visit(
@@ -150,7 +153,7 @@
       Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods,
       BiMap<DexMethod, DexMethod> methodMapping,
       MethodPoolCollection methodPoolCollection,
-      Map<DexMethod, RemovedArgumentsInfo> removedArgumentsInfoPerMethod) {
+      Map<DexMethod, RemovedArgumentInfoCollection> removedArgumentsInfoPerMethod) {
     MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
 
     if (clazz.isInterface()) {
@@ -198,7 +201,8 @@
       RewrittenPrototypeDescription prototypeChanges =
           prototypeChangesPerMethod.getOrDefault(
               encodedMethod, RewrittenPrototypeDescription.none());
-      RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+      RemovedArgumentInfoCollection removedArgumentsInfo =
+          prototypeChanges.getRemovedArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -231,7 +235,8 @@
       DexMethod method = encodedMethod.method;
       RewrittenPrototypeDescription prototypeChanges =
           getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+      RemovedArgumentInfoCollection removedArgumentsInfo =
+          prototypeChanges.getRemovedArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -259,7 +264,8 @@
       DexMethod method = encodedMethod.method;
       RewrittenPrototypeDescription prototypeChanges =
           getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      RemovedArgumentsInfo removedArgumentsInfo = prototypeChanges.getRemovedArgumentsInfo();
+      RemovedArgumentInfoCollection removedArgumentsInfo =
+          prototypeChanges.getRemovedArgumentInfoCollection();
       DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
       if (newMethod != method) {
         Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
@@ -298,32 +304,26 @@
         getRemovedArgumentsInfo(encodedMethod, strategy));
   }
 
-  private RemovedArgumentsInfo getRemovedArgumentsInfo(
+  private RemovedArgumentInfoCollection getRemovedArgumentsInfo(
       DexEncodedMethod encodedMethod, Strategy strategy) {
     if (strategy == DISALLOW_ARGUMENT_REMOVAL) {
-      return RemovedArgumentsInfo.empty();
+      return RemovedArgumentInfoCollection.empty();
     }
 
-    List<RemovedArgumentInfo> removedArgumentsInfo = null;
+    Int2ReferenceSortedMap<RemovedArgumentInfo> removedArgumentsInfo = null;
     DexProto proto = encodedMethod.method.proto;
     int offset = encodedMethod.isStatic() ? 0 : 1;
     for (int i = 0; i < proto.parameters.size(); ++i) {
       DexType type = proto.parameters.values[i];
       if (type.isAlwaysNull(appView)) {
         if (removedArgumentsInfo == null) {
-          removedArgumentsInfo = new ArrayList<>();
+          removedArgumentsInfo = new Int2ReferenceLinkedOpenHashMap<>();
         }
-        removedArgumentsInfo.add(
-            RemovedArgumentInfo.builder()
-                .setArgumentIndex(i + offset)
-                .setIsAlwaysNull()
-                .setType(type)
-                .build());
+        removedArgumentsInfo.put(
+            i + offset, RemovedArgumentInfo.builder().setIsAlwaysNull().setType(type).build());
       }
     }
-    return removedArgumentsInfo != null
-        ? new RemovedArgumentsInfo(removedArgumentsInfo)
-        : RemovedArgumentsInfo.empty();
+    return RemovedArgumentInfoCollection.create(removedArgumentsInfo);
   }
 
   private DexMethod getNewMethodSignature(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index d8568d5..f50f7fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentsInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -32,7 +32,8 @@
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Streams;
-import java.util.ArrayList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.BitSet;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -51,11 +52,12 @@
   private final MethodPoolCollection methodPoolCollection;
 
   private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
-  private final Map<DexMethod, RemovedArgumentsInfo> removedArguments = new IdentityHashMap<>();
+  private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments =
+      new IdentityHashMap<>();
 
   public static class UnusedArgumentsGraphLense extends NestedGraphLense {
 
-    private final Map<DexMethod, RemovedArgumentsInfo> removedArguments;
+    private final Map<DexMethod, RemovedArgumentInfoCollection> removedArguments;
 
     UnusedArgumentsGraphLense(
         Map<DexType, DexType> typeMap,
@@ -65,7 +67,7 @@
         BiMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLense previousLense,
         DexItemFactory dexItemFactory,
-        Map<DexMethod, RemovedArgumentsInfo> removedArguments) {
+        Map<DexMethod, RemovedArgumentInfoCollection> removedArguments) {
       super(
           typeMap,
           methodMap,
@@ -84,7 +86,7 @@
               ? originalMethodSignatures.getOrDefault(method, method)
               : method;
       RewrittenPrototypeDescription result = previousLense.lookupPrototypeChanges(originalMethod);
-      RemovedArgumentsInfo removedArguments = this.removedArguments.get(method);
+      RemovedArgumentInfoCollection removedArguments = this.removedArguments.get(method);
       return removedArguments != null ? result.withRemovedArguments(removedArguments) : result;
     }
   }
@@ -167,7 +169,7 @@
     }
 
     DexEncodedMethod removeArguments(
-        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) {
+        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
       boolean removed = usedSignatures.remove(equivalence.wrap(method.method));
       assert removed;
 
@@ -208,7 +210,7 @@
     }
 
     DexEncodedMethod removeArguments(
-        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentsInfo unused) {
+        DexEncodedMethod method, DexMethod newSignature, RemovedArgumentInfoCollection unused) {
       methodPool.seen(equivalence.wrap(newSignature));
       return method.toTypeSubstitutedMethod(
           newSignature, unused.createParameterAnnotationsRemover(method));
@@ -234,7 +236,7 @@
         continue;
       }
 
-      RemovedArgumentsInfo unused = collectUnusedArguments(method);
+      RemovedArgumentInfoCollection unused = collectUnusedArguments(method);
       if (unused != null && unused.hasRemovedArguments()) {
         DexProto newProto = createProtoWithRemovedArguments(method, unused);
         DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -259,7 +261,7 @@
     List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
     for (int i = 0; i < virtualMethods.size(); i++) {
       DexEncodedMethod method = virtualMethods.get(i);
-      RemovedArgumentsInfo unused = collectUnusedArguments(method, methodPool);
+      RemovedArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
       if (unused != null && unused.hasRemovedArguments()) {
         DexProto newProto = createProtoWithRemovedArguments(method, unused);
         DexMethod newSignature = signatures.getNewSignature(method, newProto);
@@ -279,11 +281,11 @@
     }
   }
 
-  private RemovedArgumentsInfo collectUnusedArguments(DexEncodedMethod method) {
+  private RemovedArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
     return collectUnusedArguments(method, null);
   }
 
-  private RemovedArgumentsInfo collectUnusedArguments(
+  private RemovedArgumentInfoCollection collectUnusedArguments(
       DexEncodedMethod method, MemberPool<DexMethod> methodPool) {
     if (ArgumentRemovalUtils.isPinned(method, appView)
         || appView.appInfo().keepUnusedArguments.contains(method.method)) {
@@ -314,23 +316,23 @@
     method.getCode().registerArgumentReferences(method, collector);
     BitSet used = collector.getUsedArguments();
     if (used.cardinality() < argumentCount) {
-      List<RemovedArgumentInfo> unused = new ArrayList<>();
+      Int2ReferenceSortedMap<RemovedArgumentInfo> unused = new Int2ReferenceLinkedOpenHashMap<>();
       for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
         if (!used.get(argumentIndex)) {
-          unused.add(
+          unused.put(
+              argumentIndex,
               RemovedArgumentInfo.builder()
-                  .setArgumentIndex(argumentIndex)
                   .setType(method.method.proto.parameters.values[argumentIndex - offset])
                   .build());
         }
       }
-      return new RemovedArgumentsInfo(unused);
+      return RemovedArgumentInfoCollection.create(unused);
     }
     return null;
   }
 
   private DexProto createProtoWithRemovedArguments(
-      DexEncodedMethod encodedMethod, RemovedArgumentsInfo unused) {
+      DexEncodedMethod encodedMethod, RemovedArgumentInfoCollection unused) {
     DexType[] parameters = unused.rewriteParameters(encodedMethod);
     return appView.dexItemFactory().createProto(encodedMethod.method.proto.returnType, parameters);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
new file mode 100644
index 0000000..af6296b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.unusedarguments;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 UnusedAndUninstantiatedTypesTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnusedAndUninstantiatedTypesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testUnusedAndUninstantiatedTypes() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(UnusedAndUninstantiatedTypesTest.class)
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertMethodsAreThere)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "nothing",
+            "uninstantiatedThenUnused null",
+            "unusedThenUninstantiated null",
+            "doubleUninstantiatedThenUnused null and null",
+            "doubleUnusedThenUninstantiated null and null",
+            "tripleUninstantiatedThenUnused null and null and null",
+            "tripleUnusedThenUninstantiated null and null and null",
+            "withWideParameters null and null and null");
+  }
+
+  private void assertMethodsAreThere(CodeInspector i) {
+    List<FoundMethodSubject> methods = i.clazz(Main.class).allMethods();
+    assertEquals(9, methods.size());
+    for (FoundMethodSubject method : methods) {
+      if (!method.getMethod().method.name.toString().equals("main")) {
+        assertEquals(0, method.getMethod().method.getArity());
+      }
+    }
+  }
+
+  @SuppressWarnings("SameParameterValue")
+  static class Main {
+
+    public static void main(String[] args) {
+      uninstantiatedAndUnused(null);
+      uninstantiatedThenUnused(null, new Unused());
+      unusedThenUninstantiated(new Unused(), null);
+      doubleUninstantiatedThenUnused(null, new Unused(), null, new Unused());
+      doubleUnusedThenUninstantiated(new Unused(), null, new Unused(), null);
+      tripleUninstantiatedThenUnused(null, new Unused(), new Unused(), null, null, new Unused());
+      tripleUnusedThenUninstantiated(new Unused(), null, null, new Unused(), new Unused(), null);
+      withWideParameters(0L, 1L, null, null, 2L, 3L, null);
+    }
+
+    @NeverInline
+    static void uninstantiatedAndUnused(UnInstantiated uninstantiated) {
+      System.out.println("nothing");
+    }
+
+    @NeverInline
+    static void uninstantiatedThenUnused(UnInstantiated uninstantiated, Unused unused) {
+      System.out.println("uninstantiatedThenUnused " + uninstantiated);
+    }
+
+    @NeverInline
+    static void unusedThenUninstantiated(Unused unused, UnInstantiated uninstantiated) {
+      System.out.println("unusedThenUninstantiated " + uninstantiated);
+    }
+
+    @NeverInline
+    static void doubleUninstantiatedThenUnused(
+        UnInstantiated uninstantiated1,
+        Unused unused1,
+        UnInstantiated uninstantiated2,
+        Unused unused2) {
+      System.out.println(
+          "doubleUninstantiatedThenUnused " + uninstantiated1 + " and " + uninstantiated2);
+    }
+
+    @NeverInline
+    static void doubleUnusedThenUninstantiated(
+        Unused unused1,
+        UnInstantiated uninstantiated1,
+        Unused unused2,
+        UnInstantiated uninstantiated2) {
+      System.out.println(
+          "doubleUnusedThenUninstantiated " + uninstantiated1 + " and " + uninstantiated2);
+    }
+
+    @NeverInline
+    static void tripleUninstantiatedThenUnused(
+        UnInstantiated uninstantiated1,
+        Unused unused0,
+        Unused unused1,
+        UnInstantiated uninstantiated0,
+        UnInstantiated uninstantiated2,
+        Unused unused2) {
+      System.out.println(
+          "tripleUninstantiatedThenUnused "
+              + uninstantiated1
+              + " and "
+              + uninstantiated2
+              + " and "
+              + uninstantiated0);
+    }
+
+    @NeverInline
+    static void tripleUnusedThenUninstantiated(
+        Unused unused1,
+        UnInstantiated uninstantiated0,
+        UnInstantiated uninstantiated1,
+        Unused unused0,
+        Unused unused2,
+        UnInstantiated uninstantiated2) {
+      System.out.println(
+          "tripleUnusedThenUninstantiated "
+              + uninstantiated1
+              + " and "
+              + uninstantiated2
+              + " and "
+              + uninstantiated0);
+    }
+
+    @NeverInline
+    static void withWideParameters(
+        long longUnused0,
+        long longUnused1,
+        UnInstantiated uninstantiated0,
+        UnInstantiated uninstantiated1,
+        long longUnused2,
+        long longUnused3,
+        UnInstantiated uninstantiated2) {
+      System.out.println(
+          "withWideParameters "
+              + uninstantiated1
+              + " and "
+              + uninstantiated2
+              + " and "
+              + uninstantiated0);
+    }
+  }
+
+  private static class UnInstantiated {}
+
+  private static class Unused {}
+}