Enum unboxing: support initializer signature changes
Bug: 160939354
Change-Id: I7fa00ae43db3447c3c47abb8c2c50f41c03f7cca
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 dd2cde4..2c50bf8 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -325,20 +325,20 @@
private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
- private final boolean extraNullParameter;
+ private final int extraUnusedNullParameters;
private final ArgumentInfoCollection argumentInfoCollection;
private final RewrittenTypeInfo rewrittenReturnInfo;
private RewrittenPrototypeDescription() {
- this(false, null, ArgumentInfoCollection.empty());
+ this(0, null, ArgumentInfoCollection.empty());
}
private RewrittenPrototypeDescription(
- boolean extraNullParameter,
+ int extraUnusedNullParameters,
RewrittenTypeInfo rewrittenReturnInfo,
ArgumentInfoCollection argumentsInfo) {
assert argumentsInfo != null;
- this.extraNullParameter = extraNullParameter;
+ this.extraUnusedNullParameters = extraUnusedNullParameters;
this.rewrittenReturnInfo = rewrittenReturnInfo;
this.argumentInfoCollection = argumentsInfo;
}
@@ -350,12 +350,12 @@
DexType returnType = method.proto.returnType;
RewrittenTypeInfo returnInfo =
returnType.isAlwaysNull(appView) ? RewrittenTypeInfo.toVoid(returnType, appView) : null;
- return new RewrittenPrototypeDescription(false, returnInfo, removedArgumentsInfo);
+ return new RewrittenPrototypeDescription(0, returnInfo, removedArgumentsInfo);
}
public static RewrittenPrototypeDescription createForRewrittenTypes(
RewrittenTypeInfo returnInfo, ArgumentInfoCollection rewrittenArgumentsInfo) {
- return new RewrittenPrototypeDescription(false, returnInfo, rewrittenArgumentsInfo);
+ return new RewrittenPrototypeDescription(0, returnInfo, rewrittenArgumentsInfo);
}
public static RewrittenPrototypeDescription none() {
@@ -363,11 +363,13 @@
}
public boolean isEmpty() {
- return !extraNullParameter && rewrittenReturnInfo == null && argumentInfoCollection.isEmpty();
+ return extraUnusedNullParameters == 0
+ && rewrittenReturnInfo == null
+ && argumentInfoCollection.isEmpty();
}
- public boolean hasExtraNullParameter() {
- return extraNullParameter;
+ public int numberOfExtraUnusedNullParameters() {
+ return extraUnusedNullParameters;
}
public boolean hasBeenChangedToReturnVoid(AppView<?> appView) {
@@ -418,7 +420,7 @@
assert rewrittenReturnInfo == null;
return !hasBeenChangedToReturnVoid(appView)
? new RewrittenPrototypeDescription(
- extraNullParameter,
+ extraUnusedNullParameters,
RewrittenTypeInfo.toVoid(oldReturnType, appView),
argumentInfoCollection)
: this;
@@ -426,12 +428,21 @@
public RewrittenPrototypeDescription withRemovedArguments(ArgumentInfoCollection other) {
return new RewrittenPrototypeDescription(
- extraNullParameter, rewrittenReturnInfo, argumentInfoCollection.combine(other));
+ extraUnusedNullParameters, rewrittenReturnInfo, argumentInfoCollection.combine(other));
}
- public RewrittenPrototypeDescription withExtraNullParameter() {
- return !extraNullParameter
- ? new RewrittenPrototypeDescription(true, rewrittenReturnInfo, argumentInfoCollection)
- : this;
+ public RewrittenPrototypeDescription withExtraUnusedNullParameter() {
+ return withExtraUnusedNullParameters(1);
+ }
+
+ public RewrittenPrototypeDescription withExtraUnusedNullParameters(
+ int numberOfExtraUnusedNullParameters) {
+ if (numberOfExtraUnusedNullParameters == 0) {
+ return this;
+ }
+ return new RewrittenPrototypeDescription(
+ extraUnusedNullParameters + numberOfExtraUnusedNullParameters,
+ rewrittenReturnInfo,
+ argumentInfoCollection);
}
}
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 6b7b402..dfce72e 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
@@ -538,13 +538,14 @@
register++;
}
- int numberOfArguments =
+ int originalNumberOfArguments =
method.method.proto.parameters.values.length
+ argumentsInfo.numberOfRemovedArguments()
- + (method.isStatic() ? 0 : 1);
+ + (method.isStatic() ? 0 : 1)
+ - prototypeChanges.numberOfExtraUnusedNullParameters();
int usedArgumentIndex = 0;
- while (argumentIndex < numberOfArguments) {
+ while (argumentIndex < originalNumberOfArguments) {
TypeElement type;
ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(argumentIndex);
if (argumentInfo.isRemovedArgumentInfo()) {
@@ -558,14 +559,14 @@
DexType argType;
if (argumentInfo.isRewrittenTypeInfo()) {
RewrittenTypeInfo argumentRewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
- assert method.method.proto.parameters.values[usedArgumentIndex]
+ assert method.method.proto.getParameter(usedArgumentIndex)
== argumentRewrittenTypeInfo.getNewType();
// The old type is used to prevent that a changed value from reference to primitive
// type breaks IR building. Rewriting from the old to the new type will be done in the
// IRConverter (typically through the lensCodeRewriter).
argType = argumentRewrittenTypeInfo.getOldType();
} else {
- argType = method.method.proto.parameters.values[usedArgumentIndex];
+ argType = method.method.proto.getParameter(usedArgumentIndex);
}
usedArgumentIndex++;
writeCallback.accept(register, argType);
@@ -579,6 +580,16 @@
register += type.requiredRegisters();
argumentIndex++;
}
+
+ for (int i = 0; i < prototypeChanges.numberOfExtraUnusedNullParameters(); i++) {
+ DexType argType = method.method.proto.getParameter(usedArgumentIndex);
+ assert argType.isClassType();
+ TypeElement type = TypeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+ register += type.requiredRegisters();
+ usedArgumentIndex++;
+ addExtraUnusedNullArgument(register);
+ }
+
flushArgumentInstructions();
}
@@ -954,6 +965,14 @@
value.markAsThis();
}
+ private void addExtraUnusedNullArgument(int register) {
+ // Extra unused null arguments should bypass the register check, they may use registers
+ // beyond the limit of what the method can use. They don't have debug information and are
+ // always null.
+ Value value = new Value(valueNumberGenerator.next(), TypeElement.getNull(), null);
+ addNonThisArgument(new Argument(value, currentBlock.size(), false));
+ }
+
public void addNonThisArgument(int register, TypeElement typeLattice) {
DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
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 6c9877c..3e59e47 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
@@ -31,6 +31,7 @@
import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
import static com.android.tools.r8.utils.ObjectUtils.getBooleanOrElse;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
@@ -283,12 +284,21 @@
? null
: makeOutValue(invoke, code);
- if (prototypeChanges.hasExtraNullParameter()) {
+ if (prototypeChanges.numberOfExtraUnusedNullParameters() > 0) {
iterator.previous();
- Value extraNullValue =
+ Value nullInstruction =
iterator.insertConstNullInstruction(code, appView.options());
iterator.next();
- newInValues.add(extraNullValue);
+ for (int i = 0; i < prototypeChanges.numberOfExtraUnusedNullParameters(); i++) {
+ newInValues.add(nullInstruction);
+ }
+ // TODO(b/164901008): Fix when the number of arguments overflows.
+ if (newInValues.size() > 255) {
+ throw new CompilationError(
+ "The addition of extra unused null parameters in R8 led to the overflow of"
+ + " the number of arguments of the method "
+ + actualTarget);
+ }
}
assert newInValues.size()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
index cdca27a..2669724 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
@@ -100,7 +100,8 @@
if (isConstructorBridge(method)) {
// TODO (b/132767654): Try to write a test which breaks that assertion.
assert previousLens.lookupPrototypeChanges(method).isEmpty();
- return RewrittenPrototypeDescription.none().withExtraNullParameter();
+ // TODO(b/164901008): Fix when the number of arguments overflows.
+ return RewrittenPrototypeDescription.none().withExtraUnusedNullParameter();
} else {
return previousLens.lookupPrototypeChanges(method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index a74b4b6..12a23f1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -58,7 +58,6 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -908,11 +907,9 @@
});
clazz.getMethodCollection().removeMethods(methodsToRemove);
} else {
- IntBox index = new IntBox(0);
clazz
.getMethodCollection()
- .replaceMethods(
- encodedMethod -> fixupEncodedMethod(encodedMethod, index.getAndIncrement()));
+ .replaceMethods(encodedMethod -> fixupEncodedMethod(encodedMethod));
fixupFields(clazz.staticFields(), clazz::setStaticField);
fixupFields(clazz.instanceFields(), clazz::setInstanceField);
}
@@ -962,22 +959,44 @@
return encodedMethod.toTypeSubstitutedMethod(newMethod);
}
- private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod, int index) {
+ private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
DexProto newProto = fixupProto(encodedMethod.proto());
- if (newProto != encodedMethod.proto()) {
- DexString newMethodName =
- factory.createString(
- EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX
- + index
- + "$"
- + encodedMethod.getName().toString());
- DexMethod newMethod = factory.createMethod(encodedMethod.holder(), newProto, newMethodName);
- assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
- boolean isStatic = encodedMethod.isStatic();
- lensBuilder.move(encodedMethod.method, isStatic, newMethod, isStatic);
- return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ if (newProto == encodedMethod.proto()) {
+ return encodedMethod;
}
- return encodedMethod;
+ assert !encodedMethod.isClassInitializer();
+ DexMethod newMethod =
+ factory.createMethod(encodedMethod.holder(), newProto, encodedMethod.getName());
+ newMethod = ensureUniqueMethod(encodedMethod, newMethod);
+ int numberOfExtraNullParameters = newMethod.getArity() - encodedMethod.method.getArity();
+ boolean isStatic = encodedMethod.isStatic();
+ lensBuilder.move(
+ encodedMethod.method, isStatic, newMethod, isStatic, numberOfExtraNullParameters);
+ return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ }
+
+ private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
+ DexClass holder = appView.definitionFor(encodedMethod.holder());
+ assert holder != null;
+ if (encodedMethod.isInstanceInitializer()) {
+ while (holder.lookupMethod(newMethod) != null) {
+ newMethod =
+ factory.createMethod(
+ newMethod.holder,
+ factory.appendTypeToProto(newMethod.proto, factory.enumUnboxingUtilityType),
+ newMethod.name);
+ }
+ } else {
+ int index = 0;
+ while (holder.lookupMethod(newMethod) != null) {
+ newMethod =
+ factory.createMethod(
+ newMethod.holder,
+ newMethod.proto,
+ encodedMethod.getName().toString() + "$enumunboxing$" + index++);
+ }
+ }
+ return newMethod;
}
private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
@@ -1091,6 +1110,15 @@
new IdentityHashMap<>();
public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
+ move(from, fromStatic, to, toStatic, 0);
+ }
+
+ public void move(
+ DexMethod from,
+ boolean fromStatic,
+ DexMethod to,
+ boolean toStatic,
+ int numberOfExtraNullParameters) {
super.move(from, to);
int offsetDiff = 0;
int toOffset = BooleanUtils.intValue(!toStatic);
@@ -1114,7 +1142,9 @@
? null
: new RewrittenTypeInfo(from.proto.returnType, to.proto.returnType);
prototypeChanges.put(
- to, RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build()));
+ to,
+ RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build())
+ .withExtraUnusedNullParameters(numberOfExtraNullParameters));
}
public EnumUnboxingLens build(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index 758ed36..393ef89 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -75,7 +75,7 @@
codeInspector
.clazz(CompanionHost.class)
.uniqueMethodWithName(
- EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "1$method");
+ EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "0$method");
assertThat(method, isPresent());
assertEquals("int", method.getMethod().method.proto.parameters.toString());
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
index 8e206b8..bf038fa 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
@@ -72,11 +72,37 @@
C;
}
+ @NeverClassInline
+ enum MyEnum3 {
+ A,
+ B,
+ C;
+ }
+
static class TestClass {
public static void main(String[] args) {
virtualTest();
staticTest();
+ constructorTest();
+ }
+
+ @NeverInline
+ private static void constructorTest() {
+ new TestClass(42);
+ System.out.println("42");
+ new TestClass(MyEnum1.A);
+ System.out.println("0");
+ new TestClass(MyEnum1.B);
+ System.out.println("1");
+ new TestClass(MyEnum2.A);
+ System.out.println("0");
+ new TestClass(MyEnum2.B);
+ System.out.println("1");
+ new TestClass(MyEnum3.A);
+ System.out.println("0");
+ new TestClass(MyEnum3.B);
+ System.out.println("1");
}
@NeverInline
@@ -91,6 +117,10 @@
System.out.println("0");
staticMethod(MyEnum2.B);
System.out.println("1");
+ staticMethod(MyEnum3.A);
+ System.out.println("0");
+ staticMethod(MyEnum3.B);
+ System.out.println("1");
}
@NeverInline
@@ -106,6 +136,32 @@
System.out.println("0");
testClass.virtualMethod(MyEnum2.B);
System.out.println("1");
+ testClass.virtualMethod(MyEnum3.A);
+ System.out.println("0");
+ testClass.virtualMethod(MyEnum3.B);
+ System.out.println("1");
+ }
+
+ public TestClass() {}
+
+ @NeverInline
+ public TestClass(MyEnum1 e1) {
+ System.out.println(e1.ordinal());
+ }
+
+ @NeverInline
+ public TestClass(MyEnum2 e2) {
+ System.out.println(e2.ordinal());
+ }
+
+ @NeverInline
+ public TestClass(MyEnum3 e3) {
+ System.out.println(e3.ordinal());
+ }
+
+ @NeverInline
+ public TestClass(int i) {
+ System.out.println(i);
}
@NeverInline
@@ -114,8 +170,13 @@
}
@NeverInline
- public void virtualMethod(MyEnum2 e1) {
- System.out.println(e1.ordinal());
+ public void virtualMethod(MyEnum2 e2) {
+ System.out.println(e2.ordinal());
+ }
+
+ @NeverInline
+ public void virtualMethod(MyEnum3 e3) {
+ System.out.println(e3.ordinal());
}
@NeverInline
@@ -129,8 +190,13 @@
}
@NeverInline
- public static void staticMethod(MyEnum2 e1) {
- System.out.println(e1.ordinal());
+ public static void staticMethod(MyEnum2 e2) {
+ System.out.println(e2.ordinal());
+ }
+
+ @NeverInline
+ public static void staticMethod(MyEnum3 e3) {
+ System.out.println(e3.ordinal());
}
@NeverInline