blob: 3beb9ba4a784442530333f59cdc2f56ff6937b00 [file] [log] [blame]
// 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;
}
}