blob: 5c22878b11477e5d0aea29a6730de6dfe9b809bb [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 com.android.tools.r8.utils.IteratorUtils;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Consumer;
public class RewrittenPrototypeDescription {
public static class RemovedArgumentInfo {
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;
}
public Builder setType(DexType type) {
this.type = type;
return this;
}
public RemovedArgumentInfo build() {
assert argumentIndex >= 0;
assert type != null;
return new RemovedArgumentInfo(argumentIndex, 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;
this.isAlwaysNull = isAlwaysNull;
this.type = type;
}
public static Builder builder() {
return new Builder();
}
public int getArgumentIndex() {
return argumentIndex;
}
public DexType getType() {
return type;
}
public boolean isAlwaysNull() {
return isAlwaysNull;
}
public boolean isNeverUsed() {
return !isAlwaysNull;
}
RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
return this.argumentIndex != argumentIndex
? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
: this;
}
}
public static class RemovedArgumentsInfo {
private static final RemovedArgumentsInfo empty = new RemovedArgumentsInfo(null);
private final List<RemovedArgumentInfo> removedArguments;
public RemovedArgumentsInfo(List<RemovedArgumentInfo> removedArguments) {
assert verifyRemovedArguments(removedArguments);
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;
}
}
return true;
}
public static RemovedArgumentsInfo empty() {
return empty;
}
public ListIterator<RemovedArgumentInfo> iterator() {
return removedArguments == null
? Collections.emptyListIterator()
: removedArguments.listIterator();
}
public boolean hasRemovedArguments() {
return removedArguments != null && !removedArguments.isEmpty();
}
public boolean isArgumentRemoved(int argumentIndex) {
if (removedArguments != null) {
for (RemovedArgumentInfo info : removedArguments) {
if (info.getArgumentIndex() == argumentIndex) {
return true;
}
}
}
return false;
}
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 RemovedArgumentsInfo combine(RemovedArgumentsInfo info) {
assert info != null;
if (hasRemovedArguments()) {
if (!info.hasRemovedArguments()) {
return this;
}
} else {
return info;
}
List<RemovedArgumentInfo> newRemovedArguments = new LinkedList<>(removedArguments);
ListIterator<RemovedArgumentInfo> iterator = newRemovedArguments.listIterator();
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);
offset++;
}
iterator.add(pending.withArgumentIndex(pending.getArgumentIndex() + offset));
}
return new RemovedArgumentsInfo(newRemovedArguments);
}
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;
}
}
private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
private final boolean hasBeenChangedToReturnVoid;
private final boolean extraNullParameter;
private final RemovedArgumentsInfo removedArgumentsInfo;
private RewrittenPrototypeDescription() {
this(false, false, RemovedArgumentsInfo.empty());
}
private RewrittenPrototypeDescription(
boolean hasBeenChangedToReturnVoid,
boolean extraNullParameter,
RemovedArgumentsInfo removedArgumentsInfo) {
assert removedArgumentsInfo != null;
this.extraNullParameter = extraNullParameter;
this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid;
this.removedArgumentsInfo = removedArgumentsInfo;
}
public static RewrittenPrototypeDescription createForUninstantiatedTypes(
boolean hasBeenChangedToReturnVoid, RemovedArgumentsInfo removedArgumentsInfo) {
return new RewrittenPrototypeDescription(
hasBeenChangedToReturnVoid, false, removedArgumentsInfo);
}
public static RewrittenPrototypeDescription none() {
return none;
}
public boolean isEmpty() {
return !extraNullParameter
&& !hasBeenChangedToReturnVoid
&& !getRemovedArgumentsInfo().hasRemovedArguments();
}
public boolean hasExtraNullParameter() {
return extraNullParameter;
}
public boolean hasBeenChangedToReturnVoid() {
return hasBeenChangedToReturnVoid;
}
public RemovedArgumentsInfo getRemovedArgumentsInfo() {
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(RemovedArgumentsInfo other) {
return new RewrittenPrototypeDescription(
hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other));
}
public RewrittenPrototypeDescription withExtraNullParameter() {
return !extraNullParameter
? new RewrittenPrototypeDescription(hasBeenChangedToReturnVoid, true, removedArgumentsInfo)
: this;
}
}