blob: 3bbd63616374ca5a07403e076e432cd20f6f7b17 [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.ir.optimize.enums;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMember;
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.DexMember;
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.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.ArrayAccess;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
public class EnumUnboxingRewriter {
public static final String ENUM_UNBOXING_UTILITY_METHOD_PREFIX = "$enumboxing$";
private static final CfVersion REQUIRED_CLASS_FILE_VERSION = CfVersion.V1_8;
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
private final InternalOptions options;
private final EnumDataMap unboxedEnumsData;
private EnumUnboxingLens enumUnboxingLens;
private final EnumUnboxingUtilityClasses utilityClasses;
private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
private final DexMethod ordinalUtilityMethod;
private final DexMethod equalsUtilityMethod;
private final DexMethod compareToUtilityMethod;
private final DexMethod zeroCheckMethod;
private final DexMethod zeroCheckMessageMethod;
EnumUnboxingRewriter(
AppView<AppInfoWithLiveness> appView,
EnumDataMap unboxedEnumsInstanceFieldData,
EnumUnboxingUtilityClasses utilityClasses) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.options = appView.options();
this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
this.utilityClasses = utilityClasses;
// Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
DexType sharedEnumUnboxingUtilityType = utilityClasses.getSharedUtilityClass().getType();
this.ordinalUtilityMethod =
factory.createMethod(
sharedEnumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
this.equalsUtilityMethod =
factory.createMethod(
sharedEnumUnboxingUtilityType,
factory.createProto(factory.booleanType, factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
this.compareToUtilityMethod =
factory.createMethod(
sharedEnumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
// Custom methods for Object#getClass without outValue and Objects.requireNonNull.
this.zeroCheckMethod =
factory.createMethod(
sharedEnumUnboxingUtilityType,
factory.createProto(factory.voidType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
this.zeroCheckMessageMethod =
factory.createMethod(
sharedEnumUnboxingUtilityType,
factory.createProto(factory.voidType, factory.intType, factory.stringType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
}
private LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
return utilityClasses.getLocalUtilityClass(enumType);
}
private SharedEnumUnboxingUtilityClass getSharedUtilityClass() {
return utilityClasses.getSharedUtilityClass();
}
public void setEnumUnboxingLens(EnumUnboxingLens enumUnboxingLens) {
this.enumUnboxingLens = enumUnboxingLens;
}
Set<Phi> rewriteCode(IRCode code) {
// We should not process the enum methods, they will be removed and they may contain invalid
// rewriting rules.
if (unboxedEnumsData.isEmpty()) {
return Sets.newIdentityHashSet();
}
assert code.isConsistentSSABeforeTypesAreCorrect();
ProgramMethod context = code.context();
Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
Set<BasicBlock> seenBlocks = Sets.newIdentityHashSet();
Set<Instruction> instructionsToRemove = Sets.newIdentityHashSet();
Value zeroConstValue = null;
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
seenBlocks.add(block);
zeroConstValue = fixNullsInBlockPhis(code, block, zeroConstValue);
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
if (instructionsToRemove.contains(instruction)) {
iterator.removeOrReplaceByDebugLocalRead();
continue;
}
// Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
// counterpart. The rewriting (== or match) is based on the following:
// - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
// - equals, hashCode are final and implemented in java.lang.Enum and java.lang.Object,
// - getClass is final and implemented only in java.lang.Object,
// - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
// also in the unboxed enum class.
if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (enumType != null) {
if (invokedMethod == factory.enumMembers.ordinalMethod
|| invokedMethod.match(factory.enumMembers.hashCode)) {
replaceEnumInvoke(
iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
continue;
} else if (invokedMethod.match(factory.enumMembers.equals)) {
replaceEnumInvoke(
iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
continue;
} else if (invokedMethod == factory.enumMembers.compareTo) {
replaceEnumInvoke(
iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
continue;
} else if (invokedMethod == factory.enumMembers.nameMethod) {
rewriteNameMethod(iterator, invokeMethod, enumType);
continue;
} else if (invokedMethod.match(factory.enumMembers.toString)) {
DexMethod lookupMethod = enumUnboxingLens.lookupMethod(invokedMethod);
// If the lookupMethod is different, then a toString method was on the enumType
// class, which was moved, and the lens code rewriter will rewrite the invoke to
// that method.
if (invokeMethod.isInvokeSuper() || lookupMethod == invokedMethod) {
rewriteNameMethod(iterator, invokeMethod, enumType);
continue;
}
} else if (invokedMethod == factory.objectMembers.getClass) {
assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
replaceEnumInvoke(
iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
continue;
}
} else if (invokedMethod == factory.stringBuilderMethods.appendObject
|| invokedMethod == factory.stringBufferMethods.appendObject) {
// Rewrites stringBuilder.append(enumInstance) as if it was
// stringBuilder.append(String.valueOf(unboxedEnumInstance));
Value enumArg = invokeMethod.getArgument(1);
DexType enumArgType = getEnumTypeOrNull(enumArg, convertedEnums);
if (enumArgType != null) {
DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumArgType);
InvokeStatic toStringInvoke =
InvokeStatic.builder()
.setMethod(stringValueOfMethod)
.setSingleArgument(enumArg)
.setFreshOutValue(appView, code)
.setPosition(invokeMethod)
.build();
DexMethod newAppendMethod =
invokedMethod == factory.stringBuilderMethods.appendObject
? factory.stringBuilderMethods.appendString
: factory.stringBufferMethods.appendString;
List<Value> arguments =
ImmutableList.of(invokeMethod.getReceiver(), toStringInvoke.outValue());
InvokeVirtual invokeAppendString =
new InvokeVirtual(newAppendMethod, invokeMethod.clearOutValue(), arguments);
invokeAppendString.setPosition(invokeMethod.getPosition());
iterator.replaceCurrentInstruction(toStringInvoke);
if (block.hasCatchHandlers()) {
iterator
.splitCopyCatchHandlers(code, blocks, appView.options())
.listIterator(code)
.add(invokeAppendString);
} else {
iterator.add(invokeAppendString);
}
continue;
}
}
} else if (instruction.isInvokeStatic()) {
InvokeStatic invokeStatic = instruction.asInvokeStatic();
DexClassAndMethod singleTarget = invokeStatic.lookupSingleTarget(appView, context);
if (singleTarget == null) {
continue;
}
DexMethod invokedMethod = singleTarget.getReference();
if (invokedMethod == factory.enumMembers.valueOf
&& invokeStatic.getArgument(0).isConstClass()) {
DexType enumType =
invokeStatic.getArgument(0).getConstInstruction().asConstClass().getValue();
if (unboxedEnumsData.isUnboxedEnum(enumType)) {
DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
Value outValue = invokeStatic.outValue();
Value rewrittenOutValue = null;
if (outValue != null) {
rewrittenOutValue = code.createValue(TypeElement.getInt());
affectedPhis.addAll(outValue.uniquePhiUsers());
}
InvokeStatic invoke =
new InvokeStatic(
valueOfMethod,
rewrittenOutValue,
Collections.singletonList(invokeStatic.inValues().get(1)));
iterator.replaceCurrentInstruction(invoke);
convertedEnums.put(invoke, enumType);
continue;
}
} else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
assert invokeStatic.arguments().size() == 1;
Value argument = invokeStatic.getArgument(0);
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
invokeStatic.outValue().replaceUsers(argument);
iterator.removeOrReplaceByDebugLocalRead();
}
} else if (invokedMethod == factory.stringMembers.valueOf) {
assert invokeStatic.arguments().size() == 1;
Value argument = invokeStatic.getArgument(0);
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumType);
iterator.replaceCurrentInstruction(
new InvokeStatic(
stringValueOfMethod, invokeStatic.outValue(), invokeStatic.arguments()));
continue;
}
} else if (invokedMethod == factory.objectsMethods.requireNonNull) {
assert invokeStatic.arguments().size() == 1;
Value argument = invokeStatic.getArgument(0);
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
replaceEnumInvoke(
iterator, invokeStatic, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
}
} else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
assert invokeStatic.arguments().size() == 2;
Value argument = invokeStatic.getArgument(0);
DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
if (enumType != null) {
replaceEnumInvoke(
iterator,
invokeStatic,
zeroCheckMessageMethod,
m -> synthesizeZeroCheckMessageMethod());
}
}
}
if (instruction.isStaticGet()) {
StaticGet staticGet = instruction.asStaticGet();
DexField field = staticGet.getField();
DexType holder = field.holder;
if (!unboxedEnumsData.isUnboxedEnum(holder)) {
continue;
}
if (staticGet.hasUnusedOutValue()) {
iterator.removeOrReplaceByDebugLocalRead();
continue;
}
affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
if (unboxedEnumsData.matchesValuesField(field)) {
// Load the size of this enum's $VALUES array before the current instruction.
iterator.previous();
Value sizeValue =
iterator.insertConstIntInstruction(
code, options, unboxedEnumsData.getValuesSize(holder));
iterator.next();
// Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
InvokeStatic invoke =
InvokeStatic.builder()
.setMethod(getSharedUtilityClass().getValuesMethod())
.setFreshOutValue(appView, code)
.setSingleArgument(sizeValue)
.build();
iterator.replaceCurrentInstruction(invoke);
convertedEnums.put(invoke, holder);
// Check if the call to SharedUtilityClass.values(size) is followed by a call to
// clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
// array. This is needed because the javac generated implementation of MyEnum.values()
// is implemented as `return $VALUES.clone()`.
removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
} else {
// Replace by ordinal + 1 for null check (null is 0).
assert unboxedEnumsData.hasUnboxedValueFor(field)
: "Invalid read to " + field.name + ", error during enum analysis";
ConstNumber intConstant =
code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
iterator.replaceCurrentInstruction(intConstant);
convertedEnums.put(intConstant, holder);
}
}
if (instruction.isInstanceGet()) {
InstanceGet instanceGet = instruction.asInstanceGet();
DexType holder = instanceGet.getField().holder;
if (unboxedEnumsData.isUnboxedEnum(holder)) {
DexMethod fieldMethod = computeInstanceFieldMethod(instanceGet.getField());
Value rewrittenOutValue =
code.createValue(
TypeElement.fromDexType(
fieldMethod.proto.returnType, Nullability.maybeNull(), appView));
InvokeStatic invoke =
new InvokeStatic(
fieldMethod, rewrittenOutValue, ImmutableList.of(instanceGet.object()));
iterator.replaceCurrentInstruction(invoke);
if (unboxedEnumsData.isUnboxedEnum(instanceGet.getField().type)) {
convertedEnums.put(invoke, instanceGet.getField().type);
}
}
}
// Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
if (instruction.isArrayAccess()) {
ArrayAccess arrayAccess = instruction.asArrayAccess();
DexType enumType = getEnumTypeOrNull(arrayAccess);
if (enumType != null) {
if (arrayAccess.hasOutValue()) {
affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
}
instruction = arrayAccess.withMemberType(MemberType.INT);
iterator.replaceCurrentInstruction(instruction);
convertedEnums.put(instruction, enumType);
}
assert validateArrayAccess(arrayAccess);
}
}
}
assert code.isConsistentSSABeforeTypesAreCorrect();
return affectedPhis;
}
private void removeRedundantValuesArrayCloning(
InvokeStatic invoke, Set<Instruction> instructionsToRemove, Set<BasicBlock> seenBlocks) {
for (Instruction user : invoke.outValue().aliasedUsers()) {
if (user.isInvokeVirtual()) {
InvokeVirtual cloneCandidate = user.asInvokeVirtual();
if (cloneCandidate.getInvokedMethod().match(appView.dexItemFactory().objectMembers.clone)) {
if (cloneCandidate.hasOutValue()) {
cloneCandidate.outValue().replaceUsers(invoke.outValue());
}
BasicBlock cloneBlock = cloneCandidate.getBlock();
if (cloneBlock == invoke.getBlock() || !seenBlocks.contains(cloneBlock)) {
instructionsToRemove.add(cloneCandidate);
} else {
cloneBlock.removeInstruction(cloneCandidate);
}
}
}
}
}
private void rewriteNameMethod(
InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
DexMethod toStringMethod =
computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
iterator.replaceCurrentInstruction(
new InvokeStatic(toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
}
private Value fixNullsInBlockPhis(IRCode code, BasicBlock block, Value zeroConstValue) {
for (Phi phi : block.getPhis()) {
if (getEnumTypeOrNull(phi.getType()) != null) {
for (int i = 0; i < phi.getOperands().size(); i++) {
Value operand = phi.getOperand(i);
if (operand.getType().isNullType()) {
if (zeroConstValue == null) {
zeroConstValue = insertConstZero(code);
}
phi.replaceOperandAt(i, zeroConstValue);
}
}
}
}
return zeroConstValue;
}
private Value insertConstZero(IRCode code) {
InstructionListIterator iterator = code.entryBlock().listIterator(code);
while (iterator.hasNext() && iterator.peekNext().isArgument()) {
iterator.next();
}
return iterator.insertConstIntInstruction(code, options, 0);
}
private DexMethod computeInstanceFieldMethod(DexField field) {
EnumInstanceFieldKnownData enumFieldKnownData =
unboxedEnumsData.getInstanceFieldData(field.holder, field);
if (enumFieldKnownData.isOrdinal()) {
utilityMethods.computeIfAbsent(ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
return ordinalUtilityMethod;
}
return computeInstanceFieldUtilityMethod(field.holder, field);
}
private void replaceEnumInvoke(
InstructionListIterator iterator,
InvokeMethod invoke,
DexMethod method,
Function<DexMethod, DexEncodedMethod> synthesizor) {
utilityMethods.computeIfAbsent(method, synthesizor);
InvokeStatic replacement =
new InvokeStatic(
method, invoke.hasUnusedOutValue() ? null : invoke.outValue(), invoke.arguments());
assert !replacement.hasOutValue()
|| !replacement.getInvokedMethod().getReturnType().isVoidType();
iterator.replaceCurrentInstruction(replacement);
}
private boolean validateArrayAccess(ArrayAccess arrayAccess) {
ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
if (arrayType == null) {
assert arrayAccess.array().getType().isNullType();
return true;
}
assert arrayAccess.getMemberType() != MemberType.OBJECT
|| arrayType.getNesting() > 1
|| arrayType.getBaseType().isReferenceType();
return true;
}
private DexType getEnumTypeOrNull(Value receiver, Map<Instruction, DexType> convertedEnums) {
TypeElement type = receiver.getType();
if (type.isInt()) {
return convertedEnums.get(receiver.definition);
}
return getEnumTypeOrNull(type);
}
private DexType getEnumTypeOrNull(TypeElement type) {
if (!type.isClassType()) {
return null;
}
DexType enumType = type.asClassType().getClassType();
return unboxedEnumsData.isUnboxedEnum(enumType) ? enumType : null;
}
public static String compatibleName(DexType type) {
return type.toSourceString().replace('.', '$');
}
private DexMethod computeInstanceFieldUtilityMethod(DexType enumType, DexField field) {
assert unboxedEnumsData.isUnboxedEnum(enumType);
assert field.holder == enumType || field.holder == factory.enumType;
String methodName =
"get"
+ (enumType == field.holder ? "" : "Enum$")
+ field.name
+ "$$"
+ compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(field.type, factory.intType),
methodName);
utilityMethods.computeIfAbsent(
fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field, null));
return fieldMethod;
}
private DexMethod computeStringValueOfUtilityMethod(DexType enumType) {
// TODO(b/167994636): remove duplication between instance field name read and this method.
assert unboxedEnumsData.isUnboxedEnum(enumType);
String methodName = "string$valueOf$" + compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(factory.stringType, factory.intType),
methodName);
AbstractValue nullString =
appView.abstractValueFactory().createSingleStringValue(factory.createString("null"));
utilityMethods.computeIfAbsent(
fieldMethod,
m -> synthesizeInstanceFieldMethod(m, enumType, factory.enumMembers.nameField, nullString));
return fieldMethod;
}
private DexMethod computeValueOfUtilityMethod(DexType enumType) {
assert unboxedEnumsData.isUnboxedEnum(enumType);
DexMethod valueOf =
factory.createMethod(
utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(factory.intType, factory.stringType),
"valueOf" + compatibleName(enumType));
utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
return valueOf;
}
private DexType getEnumTypeOrNull(ArrayAccess arrayAccess) {
ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
if (arrayType == null) {
assert arrayAccess.array().getType().isNullType();
return null;
}
if (arrayType.getNesting() != 1) {
return null;
}
TypeElement baseType = arrayType.getBaseType();
if (!baseType.isClassType()) {
return null;
}
DexType classType = baseType.asClassType().getClassType();
return unboxedEnumsData.isUnboxedEnum(classType) ? classType : null;
}
void synthesizeEnumUnboxingUtilityMethods(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
// Append to the various utility classes, in deterministic order, the utility methods and
// fields required.
Map<DexType, List<DexEncodedMethod>> methodMap = triageEncodedMembers(utilityMethods.values());
if (methodMap.isEmpty()) {
return;
}
SortedProgramMethodSet wave = SortedProgramMethodSet.create();
methodMap.forEach(
(type, methodsSorted) -> {
DexProgramClass utilityClass = appView.definitionFor(type).asProgramClass();
assert utilityClass != null;
utilityClass.addDirectMethods(methodsSorted);
for (DexEncodedMethod dexEncodedMethod : methodsSorted) {
wave.add(new ProgramMethod(utilityClass, dexEncodedMethod));
}
});
converter.processMethodsConcurrently(wave, executorService);
}
<R extends DexMember<T, R>, T extends DexEncodedMember<T, R>>
Map<DexType, List<T>> triageEncodedMembers(Collection<T> encodedMembers) {
if (encodedMembers.isEmpty()) {
return Collections.emptyMap();
}
Map<DexType, List<T>> encodedMembersMap = new IdentityHashMap<>();
// We compute encodedMembers by types.
for (T encodedMember : encodedMembers) {
List<T> members =
encodedMembersMap.computeIfAbsent(
encodedMember.getHolderType(), ignored -> new ArrayList<>());
members.add(encodedMember);
}
// We make the order deterministic.
for (List<T> value : encodedMembersMap.values()) {
value.sort(Comparator.comparing(DexEncodedMember::getReference));
}
return encodedMembersMap;
}
private DexEncodedMethod synthesizeInstanceFieldMethod(
DexMethod method, DexType enumType, DexField field, AbstractValue nullValue) {
assert method.proto.returnType == field.type;
assert unboxedEnumsData.getInstanceFieldData(enumType, field).isMapping();
CfCode cfCode =
new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
appView,
method.holder,
field.type,
unboxedEnumsData.getInstanceFieldData(enumType, field).asEnumFieldMappingData(),
nullValue)
.generateCfCode();
return synthesizeUtilityMethod(cfCode, method);
}
private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
assert method.proto.returnType == factory.intType;
assert unboxedEnumsData
.getInstanceFieldData(enumType, factory.enumMembers.nameField)
.isMapping();
CfCode cfCode =
new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
appView,
method.holder,
enumType,
unboxedEnumsData
.getInstanceFieldData(enumType, factory.enumMembers.nameField)
.asEnumFieldMappingData())
.generateCfCode();
return synthesizeUtilityMethod(cfCode, method);
}
private DexEncodedMethod synthesizeZeroCheckMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
return synthesizeUtilityMethod(cfCode, zeroCheckMethod);
}
private DexEncodedMethod synthesizeZeroCheckMessageMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(
appView.options(), zeroCheckMessageMethod);
return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod);
}
private DexEncodedMethod synthesizeOrdinalMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod);
}
private DexEncodedMethod synthesizeEqualsMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
return synthesizeUtilityMethod(cfCode, equalsUtilityMethod);
}
private DexEncodedMethod synthesizeCompareToMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
appView.options(), compareToUtilityMethod);
return synthesizeUtilityMethod(cfCode, compareToUtilityMethod);
}
private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method) {
return new DexEncodedMethod(
method,
MethodAccessFlags.createPublicStaticSynthetic(),
MethodTypeSignature.noSignature(),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
cfCode,
true,
REQUIRED_CLASS_FILE_VERSION);
}
}