| // 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.graph; |
| |
| import com.android.tools.r8.ir.code.ConstInstruction; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.utils.BooleanUtils; |
| 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 { |
| |
| public static class RemovedArgumentInfo { |
| |
| public static class Builder { |
| |
| private boolean isAlwaysNull = false; |
| private DexType type = null; |
| |
| public Builder setIsAlwaysNull() { |
| this.isAlwaysNull = true; |
| return this; |
| } |
| |
| public Builder setType(DexType type) { |
| this.type = type; |
| return this; |
| } |
| |
| public RemovedArgumentInfo build() { |
| assert type != null; |
| return new RemovedArgumentInfo(isAlwaysNull, type); |
| } |
| } |
| |
| private final boolean isAlwaysNull; |
| private final DexType type; |
| |
| private RemovedArgumentInfo(boolean isAlwaysNull, DexType type) { |
| this.isAlwaysNull = isAlwaysNull; |
| this.type = type; |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| public DexType getType() { |
| return type; |
| } |
| |
| public boolean isAlwaysNull() { |
| return isAlwaysNull; |
| } |
| |
| public boolean isNeverUsed() { |
| return !isAlwaysNull; |
| } |
| } |
| |
| public static class RemovedArgumentInfoCollection { |
| |
| private static final RemovedArgumentInfoCollection EMPTY = new RemovedArgumentInfoCollection(); |
| |
| private final Int2ReferenceSortedMap<RemovedArgumentInfo> 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; |
| } |
| |
| public static RemovedArgumentInfoCollection empty() { |
| return EMPTY; |
| } |
| |
| public RemovedArgumentInfo getArgumentInfo(int argIndex) { |
| return removedArguments.get(argIndex); |
| } |
| |
| public boolean hasRemovedArguments() { |
| return !removedArguments.isEmpty(); |
| } |
| |
| public boolean isArgumentRemoved(int argumentIndex) { |
| return removedArguments.containsKey(argumentIndex); |
| } |
| |
| public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) { |
| // Currently not allowed to remove the receiver of an instance method. This would involve |
| // changing invoke-direct/invoke-virtual into invoke-static. |
| assert encodedMethod.isStatic() || !isArgumentRemoved(0); |
| DexType[] params = encodedMethod.method.proto.parameters.values; |
| if (!hasRemovedArguments()) { |
| return params; |
| } |
| DexType[] newParams = new DexType[params.length - numberOfRemovedArguments()]; |
| int offset = encodedMethod.isStatic() ? 0 : 1; |
| int newParamIndex = 0; |
| for (int oldParamIndex = 0; oldParamIndex < params.length; ++oldParamIndex) { |
| if (!isArgumentRemoved(oldParamIndex + offset)) { |
| newParams[newParamIndex++] = params[oldParamIndex]; |
| } |
| } |
| return newParams; |
| } |
| |
| public int numberOfRemovedArguments() { |
| return removedArguments != null ? removedArguments.size() : 0; |
| } |
| |
| public RemovedArgumentInfoCollection combine(RemovedArgumentInfoCollection info) { |
| if (hasRemovedArguments()) { |
| if (!info.hasRemovedArguments()) { |
| return this; |
| } |
| } else { |
| return info; |
| } |
| |
| Int2ReferenceSortedMap<RemovedArgumentInfo> newRemovedArguments = |
| new Int2ReferenceLinkedOpenHashMap<>(); |
| newRemovedArguments.putAll(removedArguments); |
| IntBidirectionalIterator iterator = removedArguments.keySet().iterator(); |
| int offset = 0; |
| for (int pendingArgIndex : info.removedArguments.keySet()) { |
| int nextArgindex = peekNextOrMax(iterator); |
| while (nextArgindex <= pendingArgIndex + offset) { |
| iterator.nextInt(); |
| nextArgindex = peekNextOrMax(iterator); |
| offset++; |
| } |
| assert !newRemovedArguments.containsKey(pendingArgIndex + offset); |
| newRemovedArguments.put( |
| pendingArgIndex + offset, info.removedArguments.get(pendingArgIndex)); |
| } |
| 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( |
| DexEncodedMethod method) { |
| if (numberOfRemovedArguments() > 0 && !method.parameterAnnotationsList.isEmpty()) { |
| return builder -> { |
| int firstArgumentIndex = BooleanUtils.intValue(!method.isStatic()); |
| builder.removeParameterAnnotations( |
| oldIndex -> isArgumentRemoved(oldIndex + firstArgumentIndex)); |
| }; |
| } |
| return null; |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| public static class Builder { |
| private Int2ReferenceSortedMap<RemovedArgumentInfo> removedArguments; |
| |
| public Builder addRemovedArgument(int argIndex, RemovedArgumentInfo argInfo) { |
| if (removedArguments == null) { |
| removedArguments = new Int2ReferenceLinkedOpenHashMap<>(); |
| } |
| assert !removedArguments.containsKey(argIndex); |
| removedArguments.put(argIndex, argInfo); |
| return this; |
| } |
| |
| public RemovedArgumentInfoCollection build() { |
| if (removedArguments == null || removedArguments.isEmpty()) { |
| return EMPTY; |
| } |
| return new RemovedArgumentInfoCollection(removedArguments); |
| } |
| } |
| } |
| |
| private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription(); |
| |
| private final boolean hasBeenChangedToReturnVoid; |
| private final boolean extraNullParameter; |
| private final RemovedArgumentInfoCollection removedArgumentsInfo; |
| |
| private RewrittenPrototypeDescription() { |
| this(false, false, RemovedArgumentInfoCollection.empty()); |
| } |
| |
| private RewrittenPrototypeDescription( |
| boolean hasBeenChangedToReturnVoid, |
| boolean extraNullParameter, |
| RemovedArgumentInfoCollection removedArgumentsInfo) { |
| assert removedArgumentsInfo != null; |
| this.extraNullParameter = extraNullParameter; |
| this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid; |
| this.removedArgumentsInfo = removedArgumentsInfo; |
| } |
| |
| public static RewrittenPrototypeDescription createForUninstantiatedTypes( |
| boolean hasBeenChangedToReturnVoid, RemovedArgumentInfoCollection removedArgumentsInfo) { |
| return new RewrittenPrototypeDescription( |
| hasBeenChangedToReturnVoid, false, removedArgumentsInfo); |
| } |
| |
| public static RewrittenPrototypeDescription none() { |
| return none; |
| } |
| |
| public boolean isEmpty() { |
| return !extraNullParameter |
| && !hasBeenChangedToReturnVoid |
| && !getRemovedArgumentInfoCollection().hasRemovedArguments(); |
| } |
| |
| public boolean hasExtraNullParameter() { |
| return extraNullParameter; |
| } |
| |
| public boolean hasBeenChangedToReturnVoid() { |
| return hasBeenChangedToReturnVoid; |
| } |
| |
| public RemovedArgumentInfoCollection getRemovedArgumentInfoCollection() { |
| return removedArgumentsInfo; |
| } |
| |
| /** |
| * Returns the {@link ConstInstruction} that should be used to materialize the result of |
| * invocations to the method represented by this {@link RewrittenPrototypeDescription}. |
| * |
| * <p>This method should only be used for methods that return a constant value and whose return |
| * type has been changed to void. |
| * |
| * <p>Note that the current implementation always returns null at this point. |
| */ |
| public ConstInstruction getConstantReturn(IRCode code, Position position) { |
| assert hasBeenChangedToReturnVoid; |
| ConstInstruction instruction = code.createConstNull(); |
| instruction.setPosition(position); |
| return instruction; |
| } |
| |
| public DexProto rewriteProto(DexEncodedMethod encodedMethod, DexItemFactory dexItemFactory) { |
| if (isEmpty()) { |
| return encodedMethod.method.proto; |
| } |
| DexType newReturnType = |
| hasBeenChangedToReturnVoid |
| ? dexItemFactory.voidType |
| : encodedMethod.method.proto.returnType; |
| DexType[] newParameters = removedArgumentsInfo.rewriteParameters(encodedMethod); |
| return dexItemFactory.createProto(newReturnType, newParameters); |
| } |
| |
| public RewrittenPrototypeDescription withConstantReturn() { |
| return !hasBeenChangedToReturnVoid |
| ? new RewrittenPrototypeDescription(true, extraNullParameter, removedArgumentsInfo) |
| : this; |
| } |
| |
| public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentInfoCollection other) { |
| return new RewrittenPrototypeDescription( |
| hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other)); |
| } |
| |
| public RewrittenPrototypeDescription withExtraNullParameter() { |
| return !extraNullParameter |
| ? new RewrittenPrototypeDescription(hasBeenChangedToReturnVoid, true, removedArgumentsInfo) |
| : this; |
| } |
| } |