Enum unboxing: unbox simple enums
- disable enum unboxing when enums are stored into fields.
Bug: 147860220
Change-Id: I9d10bcce9babad4a06a4258f81c4565c1842f96f
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4c4d2d5..3a4f267 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -660,6 +660,7 @@
// assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
assert appView.allMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
+ assert appView.validateUnboxedEnumsHaveBeenPruned();
processWhyAreYouKeepingAndCheckDiscarded(
appView.rootSet(),
@@ -893,7 +894,7 @@
private void computeKotlinInfoForProgramClasses(
DexApplication application, AppView<?> appView, ExecutorService executorService)
- throws ExecutionException{
+ throws ExecutionException {
if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
return;
}
@@ -906,8 +907,7 @@
programClass.setKotlinInfo(kotlinInfo);
KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
},
- executorService
- );
+ executorService);
}
private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 1f24d35..8e0dfc4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.utils.OptionalBool;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -64,6 +65,7 @@
private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
private VerticallyMergedClasses verticallyMergedClasses;
+ private Set<DexType> unboxedEnums = Collections.emptySet();
private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
@@ -368,6 +370,20 @@
this.verticallyMergedClasses = verticallyMergedClasses;
}
+ public void setUnboxedEnums(Set<DexType> unboxedEnums) {
+ this.unboxedEnums = unboxedEnums;
+ }
+
+ public boolean validateUnboxedEnumsHaveBeenPruned() {
+ for (DexType unboxedEnum : unboxedEnums) {
+ assert definitionForProgramType(unboxedEnum) == null
+ : "Enum " + unboxedEnum + " has been unboxed but is still in the program.";
+ assert appInfo().withLiveness().wasPruned(unboxedEnum)
+ : "Enum " + unboxedEnum + " has been unboxed but was not pruned.";
+ }
+ return true;
+ }
+
@SuppressWarnings("unchecked")
public AppView<AppInfoWithClassHierarchy> withClassHierarchy() {
return appInfo.hasClassHierarchy()
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 08d4d61..c046e6a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -11,6 +11,7 @@
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
+import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
@@ -272,6 +273,7 @@
public boolean isD8R8SynthesizedClassType() {
String name = toSourceString();
return name.contains(COMPANION_CLASS_NAME_SUFFIX)
+ || name.contains(ENUM_UNBOXING_UTILITY_CLASS_NAME)
|| name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
|| name.contains(DISPATCH_CLASS_NAME_SUFFIX)
|| name.contains(TYPE_WRAPPER_SUFFIX)
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 3beb9ba..c128d5c 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -4,11 +4,15 @@
package com.android.tools.r8.graph;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
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.ir.code.ValueType;
import com.android.tools.r8.utils.BooleanUtils;
import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
import java.util.function.Consumer;
@@ -194,30 +198,169 @@
}
}
+ public static class RewrittenTypeInfo {
+
+ private final DexType oldType;
+ private final DexType newType;
+
+ public RewrittenTypeInfo(DexType oldType, DexType newType) {
+ this.oldType = oldType;
+ this.newType = newType;
+ }
+
+ public DexType getNewType() {
+ return newType;
+ }
+
+ public DexType getOldType() {
+ return oldType;
+ }
+
+ public boolean defaultValueHasChanged() {
+ if (newType.isPrimitiveType()) {
+ if (oldType.isPrimitiveType()) {
+ return ValueType.fromDexType(newType) != ValueType.fromDexType(oldType);
+ }
+ return true;
+ } else if (oldType.isPrimitiveType()) {
+ return true;
+ }
+ // All reference types uses null as default value.
+ assert newType.isReferenceType();
+ assert oldType.isReferenceType();
+ return false;
+ }
+
+ public TypeLatticeElement defaultValueLatticeElement(AppView<?> appView) {
+ if (newType.isPrimitiveType()) {
+ return TypeLatticeElement.fromDexType(newType, null, appView);
+ }
+ return TypeLatticeElement.getNull();
+ }
+ }
+
+ public static class RewrittenTypeArgumentInfoCollection {
+
+ private static final RewrittenTypeArgumentInfoCollection EMPTY =
+ new RewrittenTypeArgumentInfoCollection();
+ private final Int2ReferenceMap<RewrittenTypeInfo> rewrittenArgumentsInfo;
+
+ private RewrittenTypeArgumentInfoCollection() {
+ this.rewrittenArgumentsInfo = new Int2ReferenceOpenHashMap<>(0);
+ }
+
+ private RewrittenTypeArgumentInfoCollection(
+ Int2ReferenceMap<RewrittenTypeInfo> rewrittenArgumentsInfo) {
+ this.rewrittenArgumentsInfo = rewrittenArgumentsInfo;
+ }
+
+ public static RewrittenTypeArgumentInfoCollection empty() {
+ return EMPTY;
+ }
+
+ public boolean isEmpty() {
+ return rewrittenArgumentsInfo.isEmpty();
+ }
+
+ public RewrittenTypeInfo getArgumentRewrittenTypeInfo(int argIndex) {
+ return rewrittenArgumentsInfo.get(argIndex);
+ }
+
+ public boolean isArgumentRewrittenTypeInfo(int argIndex) {
+ return rewrittenArgumentsInfo.containsKey(argIndex);
+ }
+
+ public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) {
+ DexType[] params = encodedMethod.method.proto.parameters.values;
+ if (isEmpty()) {
+ return params;
+ }
+ DexType[] newParams = new DexType[params.length];
+ int offset = encodedMethod.isStatic() ? 0 : 1;
+ for (int index = 0; index < params.length; index++) {
+ RewrittenTypeInfo argInfo = getArgumentRewrittenTypeInfo(index + offset);
+ if (argInfo != null) {
+ assert params[index] == argInfo.oldType;
+ newParams[index] = argInfo.newType;
+ } else {
+ newParams[index] = params[index];
+ }
+ }
+ return newParams;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private Int2ReferenceMap<RewrittenTypeInfo> rewrittenArgumentsInfo;
+
+ public Builder rewriteArgument(int argIndex, DexType oldType, DexType newType) {
+ if (rewrittenArgumentsInfo == null) {
+ rewrittenArgumentsInfo = new Int2ReferenceOpenHashMap<>();
+ }
+ rewrittenArgumentsInfo.put(argIndex, new RewrittenTypeInfo(oldType, newType));
+ return this;
+ }
+
+ public RewrittenTypeArgumentInfoCollection build() {
+ if (rewrittenArgumentsInfo == null) {
+ return EMPTY;
+ }
+ assert !rewrittenArgumentsInfo.isEmpty();
+ return new RewrittenTypeArgumentInfoCollection(rewrittenArgumentsInfo);
+ }
+ }
+ }
+
private static final RewrittenPrototypeDescription none = new RewrittenPrototypeDescription();
+ // TODO(b/149681096): Unify RewrittenPrototypeDescription.
private final boolean hasBeenChangedToReturnVoid;
private final boolean extraNullParameter;
- private final RemovedArgumentInfoCollection removedArgumentsInfo;
+ private final RemovedArgumentInfoCollection removedArgumentInfoCollection;
+ private final RewrittenTypeInfo rewrittenReturnInfo;
+ private final RewrittenTypeArgumentInfoCollection rewrittenTypeArgumentInfoCollection;
private RewrittenPrototypeDescription() {
- this(false, false, RemovedArgumentInfoCollection.empty());
+ this(
+ false,
+ false,
+ RemovedArgumentInfoCollection.empty(),
+ null,
+ RewrittenTypeArgumentInfoCollection.empty());
}
private RewrittenPrototypeDescription(
boolean hasBeenChangedToReturnVoid,
boolean extraNullParameter,
- RemovedArgumentInfoCollection removedArgumentsInfo) {
+ RemovedArgumentInfoCollection removedArgumentsInfo,
+ RewrittenTypeInfo rewrittenReturnInfo,
+ RewrittenTypeArgumentInfoCollection rewrittenArgumentsInfo) {
assert removedArgumentsInfo != null;
this.extraNullParameter = extraNullParameter;
this.hasBeenChangedToReturnVoid = hasBeenChangedToReturnVoid;
- this.removedArgumentsInfo = removedArgumentsInfo;
+ this.removedArgumentInfoCollection = removedArgumentsInfo;
+ this.rewrittenReturnInfo = rewrittenReturnInfo;
+ this.rewrittenTypeArgumentInfoCollection = rewrittenArgumentsInfo;
}
public static RewrittenPrototypeDescription createForUninstantiatedTypes(
boolean hasBeenChangedToReturnVoid, RemovedArgumentInfoCollection removedArgumentsInfo) {
return new RewrittenPrototypeDescription(
- hasBeenChangedToReturnVoid, false, removedArgumentsInfo);
+ hasBeenChangedToReturnVoid,
+ false,
+ removedArgumentsInfo,
+ null,
+ RewrittenTypeArgumentInfoCollection.empty());
+ }
+
+ public static RewrittenPrototypeDescription createForRewrittenTypes(
+ RewrittenTypeInfo returnInfo, RewrittenTypeArgumentInfoCollection rewrittenArgumentsInfo) {
+ return new RewrittenPrototypeDescription(
+ false, false, RemovedArgumentInfoCollection.empty(), returnInfo, rewrittenArgumentsInfo);
}
public static RewrittenPrototypeDescription none() {
@@ -227,7 +370,9 @@
public boolean isEmpty() {
return !extraNullParameter
&& !hasBeenChangedToReturnVoid
- && !getRemovedArgumentInfoCollection().hasRemovedArguments();
+ && !removedArgumentInfoCollection.hasRemovedArguments()
+ && rewrittenReturnInfo == null
+ && rewrittenTypeArgumentInfoCollection.isEmpty();
}
public boolean hasExtraNullParameter() {
@@ -239,7 +384,19 @@
}
public RemovedArgumentInfoCollection getRemovedArgumentInfoCollection() {
- return removedArgumentsInfo;
+ return removedArgumentInfoCollection;
+ }
+
+ public RewrittenTypeArgumentInfoCollection getRewrittenTypeArgumentInfoCollection() {
+ return rewrittenTypeArgumentInfoCollection;
+ }
+
+ public boolean hasRewrittenReturnInfo() {
+ return rewrittenReturnInfo != null;
+ }
+
+ public RewrittenTypeInfo getRewrittenReturnInfo() {
+ return rewrittenReturnInfo;
}
/**
@@ -258,32 +415,63 @@
return instruction;
}
+ @SuppressWarnings("ConstantConditions")
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);
+ // TODO(b/149681096): Unify RewrittenPrototypeDescription, have a single variable for return.
+ if (rewrittenReturnInfo != null || !rewrittenTypeArgumentInfoCollection.isEmpty()) {
+ assert !hasBeenChangedToReturnVoid;
+ assert !removedArgumentInfoCollection.hasRemovedArguments();
+ DexType newReturnType =
+ rewrittenReturnInfo != null
+ ? rewrittenReturnInfo.newType
+ : encodedMethod.method.proto.returnType;
+ DexType[] newParameters =
+ rewrittenTypeArgumentInfoCollection.rewriteParameters(encodedMethod);
+ return dexItemFactory.createProto(newReturnType, newParameters);
+ } else {
+ assert rewrittenReturnInfo == null;
+ assert rewrittenTypeArgumentInfoCollection.isEmpty();
+ DexType newReturnType =
+ hasBeenChangedToReturnVoid
+ ? dexItemFactory.voidType
+ : encodedMethod.method.proto.returnType;
+ DexType[] newParameters = removedArgumentInfoCollection.rewriteParameters(encodedMethod);
+ return dexItemFactory.createProto(newReturnType, newParameters);
+ }
}
public RewrittenPrototypeDescription withConstantReturn() {
+ assert rewrittenReturnInfo == null;
return !hasBeenChangedToReturnVoid
- ? new RewrittenPrototypeDescription(true, extraNullParameter, removedArgumentsInfo)
+ ? new RewrittenPrototypeDescription(
+ true,
+ extraNullParameter,
+ removedArgumentInfoCollection,
+ rewrittenReturnInfo,
+ rewrittenTypeArgumentInfoCollection)
: this;
}
public RewrittenPrototypeDescription withRemovedArguments(RemovedArgumentInfoCollection other) {
return new RewrittenPrototypeDescription(
- hasBeenChangedToReturnVoid, extraNullParameter, removedArgumentsInfo.combine(other));
+ hasBeenChangedToReturnVoid,
+ extraNullParameter,
+ removedArgumentInfoCollection.combine(other),
+ rewrittenReturnInfo,
+ rewrittenTypeArgumentInfoCollection);
}
public RewrittenPrototypeDescription withExtraNullParameter() {
return !extraNullParameter
- ? new RewrittenPrototypeDescription(hasBeenChangedToReturnVoid, true, removedArgumentsInfo)
+ ? new RewrittenPrototypeDescription(
+ hasBeenChangedToReturnVoid,
+ true,
+ removedArgumentInfoCollection,
+ rewrittenReturnInfo,
+ rewrittenTypeArgumentInfoCollection)
: this;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index eff9681..0499222 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -145,9 +145,12 @@
}
@Override
- public ClassTypeLatticeElement fixupClassTypeReferences(
+ public TypeLatticeElement fixupClassTypeReferences(
Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
DexType mappedType = mapping.apply(type);
+ if (mappedType.isPrimitiveType()) {
+ return PrimitiveTypeLatticeElement.fromDexType(mappedType, false);
+ }
if (mappedType != type) {
return create(mappedType, nullability, appView);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 32484c3..cd71750 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -34,9 +34,23 @@
public class InstancePut extends FieldInstruction {
public InstancePut(DexField field, Value object, Value value) {
+ this(field, object, value, false);
+ }
+
+ // During structural changes, IRCode is not valid from IR building until the point where
+ // several passes, such as the lens code rewriter, has been run. At this point, it can happen,
+ // for example in the context of enum unboxing, that some InstancePut have temporarily
+ // a primitive type as the object. Skip assertions in this case.
+ public static InstancePut createPotentiallyInvalid(DexField field, Value object, Value value) {
+ return new InstancePut(field, object, value, true);
+ }
+
+ private InstancePut(DexField field, Value object, Value value, boolean skipAssertion) {
super(field, null, Arrays.asList(object, value));
- assert object().verifyCompatible(ValueType.OBJECT);
- assert value().verifyCompatible(ValueType.fromDexType(field.type));
+ if (!skipAssertion) {
+ assert object().verifyCompatible(ValueType.OBJECT);
+ assert value().verifyCompatible(ValueType.fromDexType(field.type));
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 8f6ea1e..1f1d2e5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -385,7 +385,7 @@
inPrelude = true;
state.buildPrelude(canonicalPositions.getPreamblePosition());
setLocalVariableLists();
- builder.buildArgumentsWithUnusedArgumentStubs(0, method, state::write);
+ builder.buildArgumentsWithRewrittenPrototypeChanges(0, method, state::write);
// Add debug information for all locals at the initial label.
Int2ReferenceMap<DebugLocalInfo> locals = getLocalVariables(0).locals;
if (!locals.isEmpty()) {
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 2436ed1..f19ab4d 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
@@ -137,7 +137,7 @@
if (code.incomingRegisterSize == 0) {
return;
}
- builder.buildArgumentsWithUnusedArgumentStubs(
+ builder.buildArgumentsWithRewrittenPrototypeChanges(
code.registerSize - code.incomingRegisterSize,
method,
DexSourceCode::doNothingWriteConsumer);
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 0e24fe9..6e5a0a4 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
@@ -36,6 +36,8 @@
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -192,6 +194,7 @@
}
private static class MoveExceptionWorklistItem extends WorklistItem {
+
private final DexType guard;
private final int sourceOffset;
private final int targetOffset;
@@ -206,6 +209,7 @@
}
private static class SplitBlockWorklistItem extends WorklistItem {
+
private final int sourceOffset;
private final int targetOffset;
private final Position position;
@@ -224,9 +228,8 @@
}
/**
- * Representation of lists of values that can be used as keys in maps. A list of
- * values is equal to another list of values if it contains exactly the same values
- * in the same order.
+ * Representation of lists of values that can be used as keys in maps. A list of values is equal
+ * to another list of values if it contains exactly the same values in the same order.
*/
private static class ValueList {
@@ -267,6 +270,7 @@
}
public static class BlockInfo {
+
BasicBlock block = new BasicBlock();
IntSet normalPredecessors = new IntArraySet();
IntSet normalSuccessors = new IntArraySet();
@@ -423,11 +427,10 @@
// then the IR does not necessarily contain a const-string instruction).
private final IRMetadata metadata = new IRMetadata();
- public static IRBuilder create(DexEncodedMethod method,
- AppView<?> appView,
- SourceCode source,
- Origin origin) {
- return new IRBuilder(method,
+ public static IRBuilder create(
+ DexEncodedMethod method, AppView<?> appView, SourceCode source, Origin origin) {
+ return new IRBuilder(
+ method,
appView,
source,
origin,
@@ -435,29 +438,25 @@
new ValueNumberGenerator());
}
- public static IRBuilder createForInlining(DexEncodedMethod method,
+ public static IRBuilder createForInlining(
+ DexEncodedMethod method,
AppView<?> appView,
SourceCode source,
Origin origin,
MethodProcessor processor,
ValueNumberGenerator valueNumberGenerator) {
- RewrittenPrototypeDescription protoChanges = processor.shouldApplyCodeRewritings(method) ?
- lookupPrototypeChanges(appView, method.method) :
- RewrittenPrototypeDescription.none();
- return new IRBuilder(method,
- appView,
- source,
- origin,
- protoChanges,
- valueNumberGenerator);
+ RewrittenPrototypeDescription protoChanges =
+ processor.shouldApplyCodeRewritings(method)
+ ? lookupPrototypeChanges(appView, method.method)
+ : RewrittenPrototypeDescription.none();
+ return new IRBuilder(method, appView, source, origin, protoChanges, valueNumberGenerator);
}
- private static RewrittenPrototypeDescription lookupPrototypeChanges(AppView<?> appView,
- DexMethod method) {
- RewrittenPrototypeDescription prototypeChanges = appView.graphLense()
- .lookupPrototypeChanges(method);
- if (Log.ENABLED
- && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
+ private static RewrittenPrototypeDescription lookupPrototypeChanges(
+ AppView<?> appView, DexMethod method) {
+ RewrittenPrototypeDescription prototypeChanges =
+ appView.graphLense().lookupPrototypeChanges(method);
+ if (Log.ENABLED && prototypeChanges.getRemovedArgumentInfoCollection().hasRemovedArguments()) {
Log.info(
IRBuilder.class,
"Removed "
@@ -521,10 +520,13 @@
currentBlock = block;
}
- public void buildArgumentsWithUnusedArgumentStubs(
+ public void buildArgumentsWithRewrittenPrototypeChanges(
int register, DexEncodedMethod method, BiConsumer<Integer, DexType> writeCallback) {
RemovedArgumentInfoCollection removedArgumentsInfo =
prototypeChanges.getRemovedArgumentInfoCollection();
+ RewrittenTypeArgumentInfoCollection rewrittenArgumentsInfo =
+ prototypeChanges.getRewrittenTypeArgumentInfoCollection();
+ assert !removedArgumentsInfo.hasRemovedArguments() || rewrittenArgumentsInfo.isEmpty();
// Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
// block.
@@ -553,10 +555,23 @@
argumentInfo.getType(), Nullability.maybeNull(), appView);
addConstantOrUnusedArgument(register, argumentInfo);
} else {
- DexType dexType = method.method.proto.parameters.values[usedArgumentIndex++];
- writeCallback.accept(register, dexType);
- type = TypeLatticeElement.fromDexType(dexType, Nullability.maybeNull(), appView);
- if (dexType.isBooleanType()) {
+ DexType argType;
+ if (rewrittenArgumentsInfo.isArgumentRewrittenTypeInfo(argumentIndex)) {
+ RewrittenTypeInfo argumentRewrittenTypeInfo =
+ rewrittenArgumentsInfo.getArgumentRewrittenTypeInfo(argumentIndex);
+ assert method.method.proto.parameters.values[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];
+ }
+ usedArgumentIndex++;
+ writeCallback.accept(register, argType);
+ type = TypeLatticeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+ if (argType.isBooleanType()) {
addBooleanNonThisArgument(register);
} else {
addNonThisArgument(register, type);
@@ -572,7 +587,6 @@
* Build the high-level IR in SSA form.
*
* @param context Under what context this IRCode is built. Either the current method or caller.
- *
* @return The list of basic blocks. First block is the main entry.
*/
public IRCode build(DexEncodedMethod context) {
@@ -935,14 +949,14 @@
}
public void addNonThisArgument(int register, TypeLatticeElement typeLattice) {
- DebugLocalInfo local = getOutgoingLocal(register);
- Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
+ DebugLocalInfo local = getOutgoingLocal(register);
+ Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
addNonThisArgument(new Argument(value, currentBlock.size(), false));
}
public void addBooleanNonThisArgument(int register) {
- DebugLocalInfo local = getOutgoingLocal(register);
- Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
+ DebugLocalInfo local = getOutgoingLocal(register);
+ Value value = writeRegister(register, getInt(), ThrowingInfo.NO_THROW, local);
addNonThisArgument(new Argument(value, currentBlock.size(), true));
}
@@ -1765,7 +1779,10 @@
addReturn();
} else {
ValueTypeConstraint returnTypeConstraint =
- ValueTypeConstraint.fromDexType(method.method.proto.returnType);
+ prototypeChanges.hasRewrittenReturnInfo()
+ ? ValueTypeConstraint.fromDexType(
+ prototypeChanges.getRewrittenReturnInfo().getOldType())
+ : ValueTypeConstraint.fromDexType(method.method.proto.returnType);
Value in = readRegister(value, returnTypeConstraint);
addReturn(new Return(in));
}
@@ -2567,10 +2584,10 @@
}
/**
- * Change to control-flow graph to avoid repeated phi operands when all the same values
- * flow in from multiple predecessors.
+ * Change to control-flow graph to avoid repeated phi operands when all the same values flow in
+ * from multiple predecessors.
*
- * <p> As an example:
+ * <p>As an example:
*
* <pre>
*
@@ -2582,7 +2599,7 @@
* v3 = phi(v1, v1, v2)
* </pre>
*
- * <p> Is rewritten to:
+ * <p>Is rewritten to:
*
* <pre>
* b1 b2 b3
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 94e5cf4..98c1915 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -451,6 +451,13 @@
backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
}
+ private void synthesizeEnumUnboxingUtilityClass(
+ Builder<?> builder, ExecutorService executorService) throws ExecutionException {
+ if (enumUnboxer != null) {
+ enumUnboxer.synthesizeUtilityClass(builder, this, executorService);
+ }
+ }
+
private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
if (covariantReturnTypeAnnotationTransformer != null) {
covariantReturnTypeAnnotationTransformer.process(builder);
@@ -674,16 +681,12 @@
// All the code has been processed so the rewriting required by the lenses is done everywhere,
// we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
// lenses with code rewriting are added.
- graphLenseForIR = appView.clearCodeRewritings();
+ appView.clearCodeRewritings();
if (libraryMethodOverrideAnalysis != null) {
libraryMethodOverrideAnalysis.finish();
}
- if (enumUnboxer != null) {
- enumUnboxer.finishEnumAnalysis();
- }
-
// Post processing:
// 1) Second pass for methods whose collected call site information become more precise.
// 2) Second inlining pass for dealing with double inline callers.
@@ -694,8 +697,12 @@
if (inliner != null) {
postMethodProcessorBuilder.put(inliner);
}
+ if (enumUnboxer != null) {
+ enumUnboxer.finishAnalysis();
+ enumUnboxer.unboxEnums(postMethodProcessorBuilder);
+ }
timing.begin("IR conversion phase 2");
- assert graphLenseForIR == appView.graphLense();
+ graphLenseForIR = appView.graphLense();
PostMethodProcessor postMethodProcessor =
postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
if (postMethodProcessor != null) {
@@ -732,10 +739,11 @@
desugarInterfaceMethods(builder, IncludeAllResources, executorService);
feedback.updateVisibleOptimizationInfo();
- printPhase("Twr close resource utility class synthesis");
+ printPhase("Utility classes synthesis");
synthesizeTwrCloseResourceUtilityClass(builder, executorService);
synthesizeJava8UtilityClass(builder, executorService);
handleSynthesizedClassMapping(builder);
+ synthesizeEnumUnboxingUtilityClass(builder, executorService);
printPhase("Lambda merging finalization");
// TODO(b/127694949): Adapt to PostOptimization.
@@ -1133,6 +1141,10 @@
timing.end();
}
+ if (enumUnboxer != null && methodProcessor.isPost()) {
+ enumUnboxer.rewriteCode(code);
+ }
+
if (method.isProcessed()) {
assert !appView.enableWholeProgramOptimizations()
|| !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
@@ -1431,10 +1443,6 @@
previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
- if (enumUnboxer != null && methodProcessor.isPost()) {
- enumUnboxer.unboxEnums(code);
- }
-
// This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
if (desugaredLibraryAPIConverter != null
&& (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
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 fc50632..1cfb92b 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,6 +29,8 @@
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
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;
@@ -170,6 +172,39 @@
if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
RewrittenPrototypeDescription prototypeChanges =
graphLense.lookupPrototypeChanges(actualTarget);
+
+ // TODO(b/149681096): Unify RewrittenPrototypeDescription and merge rewritten type and
+ // removedArgument rewriting.
+ // When converting types the default value may change (for example default value of
+ // a reference type is null while default value of int is 0).
+ List<Value> newInValues;
+ RewrittenTypeArgumentInfoCollection rewrittenTypeArgumentInfoCollection =
+ prototypeChanges.getRewrittenTypeArgumentInfoCollection();
+ if (rewrittenTypeArgumentInfoCollection.isEmpty()) {
+ newInValues = invoke.inValues();
+ } else {
+ newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
+ for (int i = 0; i < invoke.inValues().size(); i++) {
+ RewrittenTypeInfo argInfo =
+ rewrittenTypeArgumentInfoCollection.getArgumentRewrittenTypeInfo(i);
+ Value value = invoke.inValues().get(i);
+ if (argInfo != null
+ && argInfo.defaultValueHasChanged()
+ && value.isConstNumber()
+ && value.definition.asConstNumber().isZero()) {
+ iterator.previous();
+ // TODO(b/150188380): Add API to insert a const instruction with a type lattice.
+ Value rewrittenDefaultValue =
+ iterator.insertConstIntInstruction(code, appView.options(), 0);
+ iterator.next();
+ rewrittenDefaultValue.setTypeLattice(argInfo.defaultValueLatticeElement(appView));
+ newInValues.add(rewrittenDefaultValue);
+ } else {
+ newInValues.add(invoke.inValues().get(i));
+ }
+ }
+ }
+
RemovedArgumentInfoCollection removedArgumentsInfo =
prototypeChanges.getRemovedArgumentInfoCollection();
@@ -192,7 +227,6 @@
Value newOutValue =
prototypeChanges.hasBeenChangedToReturnVoid() ? null : makeOutValue(invoke, code);
- List<Value> newInValues;
if (removedArgumentsInfo.hasRemovedArguments()) {
if (Log.ENABLED) {
Log.info(
@@ -204,14 +238,13 @@
+ " arguments removed");
}
// Remove removed arguments from the invoke.
- newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
- for (int i = 0; i < invoke.inValues().size(); i++) {
+ List<Value> tempNewInValues = new ArrayList<>(actualTarget.proto.parameters.size());
+ for (int i = 0; i < newInValues.size(); i++) {
if (!removedArgumentsInfo.isArgumentRemoved(i)) {
- newInValues.add(invoke.inValues().get(i));
+ tempNewInValues.add(newInValues.get(i));
}
}
- } else {
- newInValues = invoke.inValues();
+ newInValues = tempNewInValues;
}
if (prototypeChanges.hasExtraNullParameter()) {
@@ -288,7 +321,8 @@
new InvokeStatic(replacementMethod, null, current.inValues()));
} else if (actualField != field) {
InstancePut newInstancePut =
- new InstancePut(actualField, instancePut.object(), instancePut.value());
+ InstancePut.createPotentiallyInvalid(
+ actualField, instancePut.object(), instancePut.value());
iterator.replaceCurrentInstruction(newInstancePut);
}
} else if (current.isStaticGet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 529bdc7..6052427 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -20,6 +20,7 @@
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
+import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
@@ -28,7 +29,7 @@
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
-class PostMethodProcessor implements MethodProcessor {
+public class PostMethodProcessor implements MethodProcessor {
private final AppView<AppInfoWithLiveness> appView;
private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
@@ -56,7 +57,8 @@
return !processed.contains(method);
}
- static class Builder {
+ public static class Builder {
+
private final Collection<CodeOptimization> defaultCodeOptimizations;
private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap =
Maps.newIdentityHashMap();
@@ -85,7 +87,7 @@
put(methodsToRevisit, defaultCodeOptimizations);
}
- void put(PostOptimization postOptimization) {
+ public void put(PostOptimization postOptimization) {
Collection<CodeOptimization> codeOptimizations =
postOptimization.codeOptimizationsForPostProcessing();
if (codeOptimizations == null) {
@@ -94,6 +96,20 @@
put(postOptimization.methodsToRevisit(), codeOptimizations);
}
+ // Some optimizations may change methods, creating new instances of the encoded methods with a
+ // new signature. The compiler needs to update the set of methods that must be reprocessed
+ // according to the graph lens.
+ public void mapDexEncodedMethods(AppView<?> appView) {
+ Map<DexEncodedMethod, Collection<CodeOptimization>> newMethodsMap = new IdentityHashMap<>();
+ methodsMap.forEach(
+ (dexEncodedMethod, optimizations) -> {
+ newMethodsMap.put(
+ appView.graphLense().mapDexEncodedMethod(dexEncodedMethod, appView), optimizations);
+ });
+ methodsMap.clear();
+ methodsMap.putAll(newMethodsMap);
+ }
+
PostMethodProcessor build(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index f6693eb..403f2f0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -1051,8 +1051,7 @@
private boolean shouldIgnoreFromReports(DexType missing) {
return appView.rewritePrefix.hasRewrittenType(missing, appView)
- || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(missing)
- || DesugaredLibraryAPIConverter.isVivifiedType(missing)
+ || missing.isD8R8SynthesizedClassType()
|| isCompanionClassType(missing)
|| emulatedInterfaces.containsValue(missing)
|| options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing);
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 b3a25d0..43c57fc 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
@@ -5,41 +5,68 @@
package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.FieldSetter;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeArgumentInfoCollection;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.CodeOptimization;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.conversion.PostOptimization;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
-public class EnumUnboxer {
+public class EnumUnboxer implements PostOptimization {
private final AppView<AppInfoWithLiveness> appView;
- private final Set<DexType> enumsToUnbox;
+ private final DexItemFactory factory;
+ // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
+ // enum if the optimization eventually decides to unbox it.
+ private final Map<DexType, Set<DexEncodedMethod>> enumsUnboxingCandidates;
+
+ private EnumUnboxingRewriter enumUnboxerRewriter;
private final boolean debugLogEnabled;
private final Map<DexType, Reason> debugLogs;
- private final DexItemFactory factory;
public EnumUnboxer(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
@@ -51,12 +78,7 @@
debugLogEnabled = false;
debugLogs = null;
}
- enumsToUnbox = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
- }
-
- public void unboxEnums(IRCode code) {
- // TODO(b/147860220): To implement.
- // Do not forget static get, which is implicitly valid (no inValue).
+ enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
}
public void analyzeEnums(IRCode code) {
@@ -72,7 +94,7 @@
private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
assert enumClass.isEnum();
reportFailure(enumClass.type, reason);
- enumsToUnbox.remove(enumClass.type);
+ enumsUnboxingCandidates.remove(enumClass.type);
}
private DexProgramClass getEnumUnboxingCandidateOrNull(TypeLatticeElement lattice) {
@@ -92,26 +114,96 @@
}
private DexProgramClass getEnumUnboxingCandidateOrNull(DexType anyType) {
- if (!enumsToUnbox.contains(anyType)) {
+ if (!enumsUnboxingCandidates.containsKey(anyType)) {
return null;
}
return appView.definitionForProgramType(anyType);
}
private void analyzeEnumsInMethod(IRCode code) {
+ Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
for (BasicBlock block : code.blocks) {
for (Instruction instruction : block.getInstructions()) {
Value outValue = instruction.outValue();
- DexProgramClass enumClass =
- outValue == null ? null : getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
- if (enumClass != null) {
- validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+ if (outValue != null) {
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
+ if (enumClass != null) {
+ Reason reason =
+ validateEnumUsages(
+ code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
+ if (reason == Reason.ELIGIBLE) {
+ eligibleEnums.add(enumClass.type);
+ }
+ }
+ if (outValue.getTypeLattice().isNullType()) {
+ addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
+ }
+ }
+ // If we have a ConstClass referencing directly an enum, it cannot be unboxed, except if
+ // the constClass is in an enum valueOf method (in this case the valueOf method will be
+ // removed or the enum will be marked as non unboxable).
+ if (instruction.isConstClass()) {
+ ConstClass constClass = instruction.asConstClass();
+ if (enumsUnboxingCandidates.containsKey(constClass.getValue())) {
+ DexMethod context = code.method.method;
+ DexClass dexClass = appView.definitionFor(context.holder);
+ if (dexClass != null
+ && dexClass.isEnum()
+ && factory.enumMethods.isValueOfMethod(context, dexClass)) {
+ continue;
+ }
+ markEnumAsUnboxable(
+ Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+ }
}
}
for (Phi phi : block.getPhis()) {
DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getTypeLattice());
if (enumClass != null) {
- validateEnumUsages(code, phi.uniqueUsers(), phi.uniquePhiUsers(), enumClass);
+ Reason reason =
+ validateEnumUsages(code, phi.uniqueUsers(), phi.uniquePhiUsers(), enumClass);
+ if (reason == Reason.ELIGIBLE) {
+ eligibleEnums.add(enumClass.type);
+ }
+ }
+ if (phi.getTypeLattice().isNullType()) {
+ addNullDependencies(phi.uniqueUsers(), eligibleEnums);
+ }
+ }
+ }
+ if (!eligibleEnums.isEmpty()) {
+ for (DexType eligibleEnum : eligibleEnums) {
+ Set<DexEncodedMethod> dependencies = enumsUnboxingCandidates.get(eligibleEnum);
+ // If dependencies is null, it means the enum is not eligible (It has been marked as
+ // unboxable by this thread or another one), so we do not need to record dependencies.
+ if (dependencies != null) {
+ dependencies.add(code.method);
+ }
+ }
+ }
+ }
+
+ private void addNullDependencies(Set<Instruction> uses, Set<DexType> eligibleEnums) {
+ for (Instruction use : uses) {
+ if (use.isInvokeMethod()) {
+ InvokeMethod invokeMethod = use.asInvokeMethod();
+ DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+ for (DexType paramType : invokedMethod.proto.parameters.values) {
+ if (enumsUnboxingCandidates.containsKey(paramType)) {
+ eligibleEnums.add(paramType);
+ }
+ }
+ if (invokeMethod.isInvokeMethodWithReceiver()) {
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+ if (enumClass != null) {
+ markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
+ }
+ }
+ }
+ if (use.isFieldPut()) {
+ DexType type = use.asFieldInstruction().getField().type;
+ if (enumsUnboxingCandidates.containsKey(type)) {
+ eligibleEnums.add(type);
}
}
}
@@ -137,8 +229,22 @@
return Reason.ELIGIBLE;
}
- public void finishEnumAnalysis() {
- for (DexType toUnbox : enumsToUnbox) {
+ public void unboxEnums(PostMethodProcessor.Builder postBuilder) {
+ // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
+ if (enumsUnboxingCandidates.isEmpty()) {
+ return;
+ }
+ ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
+ appView.setUnboxedEnums(enumsToUnbox);
+ GraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
+ appView.setGraphLense(enumUnboxingLens);
+ enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
+ postBuilder.put(this);
+ postBuilder.mapDexEncodedMethods(appView);
+ }
+
+ public void finishAnalysis() {
+ for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
assert enumClass != null;
@@ -235,27 +341,28 @@
return Reason.ELIGIBLE;
}
+ // TODO(b/147860220): Re-enable enum unboxing with fields of enum types.
// A field put is valid only if the field is not on an enum, and the field type and the valuePut
// have identical enum type.
- if (instruction.isFieldPut()) {
- FieldInstruction fieldInstruction = instruction.asFieldInstruction();
- DexEncodedField field = appView.appInfo().resolveField(fieldInstruction.getField());
- if (field == null) {
- return Reason.INVALID_FIELD_PUT;
- }
- DexProgramClass dexClass = appView.definitionForProgramType(field.field.holder);
- if (dexClass == null) {
- return Reason.INVALID_FIELD_PUT;
- }
- if (dexClass.isEnum()) {
- return Reason.FIELD_PUT_ON_ENUM;
- }
- // The put value has to be of the field type.
- if (field.field.type != enumClass.type) {
- return Reason.TYPE_MISSMATCH_FIELD_PUT;
- }
- return Reason.ELIGIBLE;
- }
+ // if (instruction.isFieldPut()) {
+ // FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+ // DexEncodedField field = appView.appInfo().resolveField(fieldInstruction.getField());
+ // if (field == null) {
+ // return Reason.INVALID_FIELD_PUT;
+ // }
+ // DexProgramClass dexClass = appView.definitionForProgramType(field.field.holder);
+ // if (dexClass == null) {
+ // return Reason.INVALID_FIELD_PUT;
+ // }
+ // if (dexClass.isEnum()) {
+ // return Reason.FIELD_PUT_ON_ENUM;
+ // }
+ // // The put value has to be of the field type.
+ // if (field.field.type != enumClass.type) {
+ // return Reason.TYPE_MISSMATCH_FIELD_PUT;
+ // }
+ // return Reason.ELIGIBLE;
+ // }
// An If using enum as inValue is valid if it matches e == null
// or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
@@ -301,9 +408,9 @@
reporter.info(
new StringDiagnostic(
"Unboxed enums (Unboxing succeeded "
- + enumsToUnbox.size()
+ + enumsUnboxingCandidates.size()
+ "): "
- + Arrays.toString(enumsToUnbox.toArray())));
+ + Arrays.toString(enumsUnboxingCandidates.keySet().toArray())));
StringBuilder sb = new StringBuilder();
sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n");
for (DexType enumType : debugLogs.keySet()) {
@@ -322,6 +429,36 @@
}
}
+ public void rewriteCode(IRCode code) {
+ if (enumUnboxerRewriter != null) {
+ enumUnboxerRewriter.rewriteCode(code);
+ }
+ }
+
+ public void synthesizeUtilityClass(
+ DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+ throws ExecutionException {
+ if (enumUnboxerRewriter != null) {
+ enumUnboxerRewriter.synthesizeEnumUnboxingUtilityClass(
+ appBuilder, converter, executorService);
+ }
+ }
+
+ @Override
+ public Set<DexEncodedMethod> methodsToRevisit() {
+ Set<DexEncodedMethod> toReprocess = Sets.newIdentityHashSet();
+ for (Set<DexEncodedMethod> methods : enumsUnboxingCandidates.values()) {
+ toReprocess.addAll(methods);
+ }
+ return toReprocess;
+ }
+
+ @Override
+ public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
+ // Answers null so default optimization setup is performed.
+ return null;
+ }
+
public enum Reason {
ELIGIBLE,
SUBTYPES,
@@ -330,6 +467,7 @@
UNEXPECTED_STATIC_FIELD,
VIRTUAL_METHOD,
UNEXPECTED_DIRECT_METHOD,
+ CONST_CLASS,
INVALID_PHI,
NO_INIT,
INVALID_INIT,
@@ -348,6 +486,196 @@
FIELD_PUT_ON_ENUM,
TYPE_MISSMATCH_FIELD_PUT,
INVALID_IF_TYPES,
+ ENUM_METHOD_CALLED_WITH_NULL_RECEIVER,
OTHER_UNSUPPORTED_INSTRUCTION;
}
+
+ private class TreeFixer {
+
+ private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
+ private final Set<DexType> enumsToUnbox;
+
+ private TreeFixer(Set<DexType> enumsToUnbox) {
+ this.enumsToUnbox = enumsToUnbox;
+ }
+
+ private GraphLense fixupTypeReferences() {
+ // Fix all methods and fields using enums to unbox.
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (enumsToUnbox.contains(clazz.type)) {
+ assert clazz.instanceFields().size() == 0;
+ clearEnumtoUnboxMethods(clazz);
+ } else {
+ fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+ fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
+ fixupFields(clazz.staticFields(), clazz::setStaticField);
+ fixupFields(clazz.instanceFields(), clazz::setInstanceField);
+ }
+ }
+ for (DexType toUnbox : enumsToUnbox) {
+ lensBuilder.map(toUnbox, factory.intType);
+ }
+ return lensBuilder.build(factory, appView.graphLense());
+ }
+
+ private void clearEnumtoUnboxMethods(DexProgramClass clazz) {
+ // The compiler may have references to the enum methods, but such methods will be removed
+ // and they cannot be reprocessed since their rewriting through the lensCodeRewriter/
+ // enumUnboxerRewriter will generate invalid code.
+ // To work around this problem we clear such methods, i.e., we replace the code object by
+ // an empty throwing code object, so reprocessing won't take time and will be valid.
+ for (DexEncodedMethod method : clazz.methods()) {
+ method.setCode(
+ appView.options().isGeneratingClassFiles()
+ ? method.buildEmptyThrowingCfCode()
+ : method.buildEmptyThrowingDexCode(),
+ appView);
+ }
+ }
+
+ private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
+ if (methods == null) {
+ return;
+ }
+ for (int i = 0; i < methods.size(); i++) {
+ DexEncodedMethod encodedMethod = methods.get(i);
+ DexMethod method = encodedMethod.method;
+ DexMethod newMethod = fixupMethod(method);
+ if (newMethod != method) {
+ lensBuilder.move(method, newMethod, encodedMethod.isStatic());
+ setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
+ }
+ }
+ }
+
+ private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
+ if (fields == null) {
+ return;
+ }
+ for (int i = 0; i < fields.size(); i++) {
+ DexEncodedField encodedField = fields.get(i);
+ DexField field = encodedField.field;
+ DexType newType = fixupType(field.type);
+ if (newType != field.type) {
+ DexField newField = factory.createField(field.holder, newType, field.name);
+ lensBuilder.move(field, newField);
+ setter.setField(i, encodedField.toTypeSubstitutedField(newField));
+ }
+ }
+ }
+
+ private DexMethod fixupMethod(DexMethod method) {
+ return factory.createMethod(method.holder, fixupProto(method.proto), method.name);
+ }
+
+ private DexProto fixupProto(DexProto proto) {
+ DexType returnType = fixupType(proto.returnType);
+ DexType[] arguments = fixupTypes(proto.parameters.values);
+ return factory.createProto(returnType, arguments);
+ }
+
+ private DexType fixupType(DexType type) {
+ if (type.isArrayType()) {
+ DexType base = type.toBaseType(factory);
+ DexType fixed = fixupType(base);
+ if (base == fixed) {
+ return type;
+ }
+ return type.replaceBaseType(fixed, factory);
+ }
+ if (type.isClassType() && enumsToUnbox.contains(type)) {
+ DexType intType = factory.intType;
+ lensBuilder.map(type, intType);
+ return intType;
+ }
+ return type;
+ }
+
+ private DexType[] fixupTypes(DexType[] types) {
+ DexType[] result = new DexType[types.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = fixupType(types[i]);
+ }
+ return result;
+ }
+ }
+
+ private static class EnumUnboxingLens extends NestedGraphLense {
+
+ private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges;
+
+ EnumUnboxingLens(
+ Map<DexType, DexType> typeMap,
+ Map<DexMethod, DexMethod> methodMap,
+ Map<DexField, DexField> fieldMap,
+ BiMap<DexField, DexField> originalFieldSignatures,
+ BiMap<DexMethod, DexMethod> originalMethodSignatures,
+ GraphLense previousLense,
+ DexItemFactory dexItemFactory,
+ Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges) {
+ super(
+ typeMap,
+ methodMap,
+ fieldMap,
+ originalFieldSignatures,
+ originalMethodSignatures,
+ previousLense,
+ dexItemFactory);
+ this.prototypeChanges = prototypeChanges;
+ }
+
+ @Override
+ public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+ // During the second IR processing enum unboxing is the only optimization rewriting
+ // prototype description, if this does not hold, remove the assertion and merge
+ // the two prototype changes.
+ assert previousLense.lookupPrototypeChanges(method).isEmpty();
+ return prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none());
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private static class Builder extends NestedGraphLense.Builder {
+
+ private Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges =
+ new IdentityHashMap<>();
+
+ public void move(DexMethod from, DexMethod to, boolean isStatic) {
+ super.move(from, to);
+ int offset = BooleanUtils.intValue(!isStatic);
+ RewrittenTypeArgumentInfoCollection.Builder builder =
+ RewrittenTypeArgumentInfoCollection.builder();
+ for (int i = 0; i < from.proto.parameters.size(); i++) {
+ DexType fromType = from.proto.parameters.values[i];
+ DexType toType = to.proto.parameters.values[i];
+ if (fromType != toType) {
+ builder.rewriteArgument(i + offset, fromType, toType);
+ }
+ }
+ RewrittenTypeInfo returnInfo =
+ from.proto.returnType == to.proto.returnType
+ ? null
+ : new RewrittenTypeInfo(from.proto.returnType, to.proto.returnType);
+ prototypeChanges.put(
+ to, RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build()));
+ }
+
+ public GraphLense build(DexItemFactory dexItemFactory, GraphLense previousLense) {
+ if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
+ return previousLense;
+ }
+ return new EnumUnboxingLens(
+ typeMap,
+ methodMap,
+ fieldMap,
+ originalFieldSignatures,
+ originalMethodSignatures,
+ previousLense,
+ dexItemFactory,
+ ImmutableMap.copyOf(prototypeChanges));
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 7b207c6..53cbcc0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -12,7 +12,9 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
import com.google.common.collect.Sets;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
class EnumUnboxingCandidateAnalysis {
@@ -26,11 +28,11 @@
factory = appView.dexItemFactory();
}
- Set<DexType> findCandidates() {
- Set<DexType> enums = Sets.newConcurrentHashSet();
+ Map<DexType, Set<DexEncodedMethod>> findCandidates() {
+ Map<DexType, Set<DexEncodedMethod>> enums = new ConcurrentHashMap<>();
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (isEnumUnboxingCandidate(clazz)) {
- enums.add(clazz.type);
+ enums.put(clazz.type, Sets.newConcurrentHashSet());
}
}
return enums;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
new file mode 100644
index 0000000..9888aec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -0,0 +1,218 @@
+// 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.enums;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class EnumUnboxingRewriter {
+
+ public static final String ENUM_UNBOXING_UTILITY_CLASS_NAME = "$r8$EnumUnboxingUtility";
+ private static final int REQUIRED_CLASS_FILE_VERSION = 52;
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory factory;
+ private final Set<DexType> enumsToUnbox;
+
+ private final DexType utilityClassType;
+ private final DexMethod ordinalUtilityMethod;
+
+ private boolean requiresOrdinalUtilityMethod = false;
+
+ EnumUnboxingRewriter(AppView<AppInfoWithLiveness> appView, Set<DexType> enumsToUnbox) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ this.enumsToUnbox = enumsToUnbox;
+
+ this.utilityClassType = factory.createType("L" + ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
+ this.ordinalUtilityMethod =
+ factory.createMethod(
+ utilityClassType,
+ factory.createProto(factory.intType, factory.intType),
+ "$enumboxing$ordinal");
+ }
+
+ void rewriteCode(IRCode code) {
+ // We should not process the enum methods, they will be removed and they may contain invalid
+ // rewriting rules.
+ if (enumsToUnbox.isEmpty()) {
+ return;
+ }
+ Set<Phi> affectedPhis = Sets.newIdentityHashSet();
+ InstructionListIterator iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
+ // counterpart.
+ if (instruction.isInvokeMethodWithReceiver()) {
+ InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
+ DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+ if (invokedMethod == factory.enumMethods.ordinal
+ && invokeMethod.getReceiver().getTypeLattice().isInt()) {
+ instruction =
+ new InvokeStatic(
+ ordinalUtilityMethod, invokeMethod.outValue(), invokeMethod.inValues());
+ iterator.replaceCurrentInstruction(instruction);
+ requiresOrdinalUtilityMethod = true;
+ }
+ // TODO(b/147860220): rewrite also other enum methods.
+ }
+ // Rewrites direct access to enum values into the corresponding int, $VALUES is not
+ // supported.
+ if (instruction.isStaticGet()) {
+ StaticGet staticGet = instruction.asStaticGet();
+ DexType holder = staticGet.getField().holder;
+ if (enumsToUnbox.contains(holder)) {
+ if (staticGet.outValue() == null) {
+ iterator.removeInstructionIgnoreOutValue();
+ continue;
+ }
+ Map<DexField, EnumValueInfo> enumValueInfoMapFor =
+ appView.appInfo().withLiveness().getEnumValueInfoMapFor(holder);
+ assert enumValueInfoMapFor != null;
+ // Replace by ordinal + 1 for null check (null is 0).
+ EnumValueInfo enumValueInfo = enumValueInfoMapFor.get(staticGet.getField());
+ assert enumValueInfo != null
+ : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
+ instruction = new ConstNumber(staticGet.outValue(), enumValueInfo.ordinal + 1);
+ staticGet
+ .outValue()
+ .setTypeLattice(PrimitiveTypeLatticeElement.fromNumericType(NumericType.INT));
+ iterator.replaceCurrentInstruction(instruction);
+ affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+ }
+ }
+ assert validateEnumToUnboxRemoved(instruction);
+ }
+ if (!affectedPhis.isEmpty()) {
+ new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
+ assert code.verifyTypes(appView);
+ }
+ assert code.isConsistentSSA();
+ }
+
+ private boolean validateEnumToUnboxRemoved(Instruction instruction) {
+ if (instruction.outValue() == null) {
+ return true;
+ }
+ TypeLatticeElement typeLattice = instruction.outValue().getTypeLattice();
+ assert !typeLattice.isClassType()
+ || !enumsToUnbox.contains(typeLattice.asClassTypeLatticeElement().getClassType());
+ if (typeLattice.isArrayType()) {
+ TypeLatticeElement arrayBaseTypeLattice =
+ typeLattice.asArrayTypeLatticeElement().getArrayBaseTypeLattice();
+ assert !arrayBaseTypeLattice.isClassType()
+ || !enumsToUnbox.contains(
+ arrayBaseTypeLattice.asClassTypeLatticeElement().getClassType());
+ }
+ return true;
+ }
+
+ // TODO(b/150172351): Synthesize the utility class upfront in the enqueuer.
+ void synthesizeEnumUnboxingUtilityClass(
+ DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+ throws ExecutionException {
+ // Synthesize a class which holds various utility methods that may be called from the IR
+ // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
+ List<DexEncodedMethod> requiredMethods = new ArrayList<>();
+ if (requiresOrdinalUtilityMethod) {
+ requiredMethods.add(synthesizeOrdinalMethod());
+ }
+ // TODO(b/147860220): synthesize also other enum methods.
+ if (requiredMethods.isEmpty()) {
+ return;
+ }
+ DexProgramClass utilityClass =
+ new DexProgramClass(
+ utilityClassType,
+ null,
+ new SynthesizedOrigin("EnumUnboxing ", getClass()),
+ ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+ factory.objectType,
+ DexTypeList.empty(),
+ factory.createString("enumunboxing"),
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ // All synthesized methods are static in this case.
+ requiredMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
+ DexEncodedMethod.EMPTY_ARRAY,
+ factory.getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ appBuilder.addSynthesizedClass(utilityClass, utilityClassInMainDexList());
+ appView.appInfo().addSynthesizedClass(utilityClass);
+ converter.optimizeSynthesizedClass(utilityClass, executorService);
+ }
+
+ // TODO(b/150178516): Add a test for this case.
+ private boolean utilityClassInMainDexList() {
+ for (DexType toUnbox : enumsToUnbox) {
+ if (appView.appInfo().isInMainDexList(toUnbox)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private DexEncodedMethod synthesizeOrdinalMethod() {
+ CfCode cfCode =
+ EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
+ return new DexEncodedMethod(
+ ordinalUtilityMethod,
+ synthesizedMethodAccessFlags(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ cfCode,
+ REQUIRED_CLASS_FILE_VERSION,
+ true);
+ }
+
+ private MethodAccessFlags synthesizedMethodAccessFlags() {
+ return MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, false);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 91aa098..33c7b2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -31,12 +31,20 @@
public void fixupClassTypeReferences(
Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
- if (dynamicLowerBoundType != null) {
- dynamicLowerBoundType = dynamicLowerBoundType.fixupClassTypeReferences(mapping, appView);
- }
if (dynamicUpperBoundType != null) {
dynamicUpperBoundType = dynamicUpperBoundType.fixupClassTypeReferences(mapping, appView);
}
+ if (dynamicLowerBoundType != null) {
+ TypeLatticeElement dynamicLowerBoundType =
+ this.dynamicLowerBoundType.fixupClassTypeReferences(mapping, appView);
+ if (dynamicLowerBoundType.isClassType()) {
+ this.dynamicLowerBoundType = dynamicLowerBoundType.asClassTypeLatticeElement();
+ } else {
+ assert dynamicLowerBoundType.isPrimitive();
+ this.dynamicLowerBoundType = null;
+ this.dynamicUpperBoundType = null;
+ }
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index d2e78f4..af494c8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -147,8 +147,16 @@
returnsObjectWithUpperBoundType.fixupClassTypeReferences(mapping, appView);
}
if (returnsObjectWithLowerBoundType != null) {
- returnsObjectWithLowerBoundType =
- returnsObjectWithLowerBoundType.fixupClassTypeReferences(mapping, appView);
+ TypeLatticeElement returnsObjectWithLowerBoundType =
+ this.returnsObjectWithLowerBoundType.fixupClassTypeReferences(mapping, appView);
+ if (returnsObjectWithLowerBoundType.isClassType()) {
+ this.returnsObjectWithLowerBoundType =
+ returnsObjectWithLowerBoundType.asClassTypeLatticeElement();
+ } else {
+ assert returnsObjectWithLowerBoundType.isPrimitive();
+ this.returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
+ this.returnsObjectWithLowerBoundType = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index ed47e54..c7d94d4 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -156,7 +156,7 @@
@Override
public final void buildPrelude(IRBuilder builder) {
- builder.buildArgumentsWithUnusedArgumentStubs(
+ builder.buildArgumentsWithRewrittenPrototypeChanges(
0, builder.getMethod(), DexSourceCode::doNothingWriteConsumer);
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index 129230a..f67cd54 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import java.util.List;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -41,6 +42,8 @@
@Test
public void testEnumUnboxing() throws Exception {
+ // TODO(b/147860220): Fix fields of type enums.
+ Assume.assumeTrue("Fix fields", false);
R8TestCompileResult compile =
testForR8(parameters.getBackend())
.addInnerClasses(FieldPutEnumUnboxingTest.class)