| // 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.analysis.proto; |
| |
| import com.android.tools.r8.errors.CompilationError; |
| import com.android.tools.r8.graph.AppView; |
| 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.DexMethod; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.google.common.collect.Sets; |
| import java.util.Set; |
| |
| /** |
| * EnumLite proto enums include the following code: <code> |
| * private static final EnumLiteMap<ProtoEnum> internalValueMap = |
| * new EnumLiteMap<ProtoEnum>() { |
| * public ProtoEnum findValueByNumber(int number) { |
| * return ProtoEnum.forNumber(number); |
| * } |
| * }; |
| * </code> |
| * |
| * <p>If the field internalValueMap on the EnumLite is effectively unused (never read), the |
| * anonymous subclass of EnumLiteMap is effectively dead. R8 figures that out however too late, |
| * during the second round of tree shaking, and the bridge findValueByNumber in the anonymous |
| * subclass of EnumLiteMap prevents the EnumLite to be unboxed. |
| * |
| * <p>This class prematurely clears the virtual methods of the anonymous subclasses of EnumLiteMap, |
| * when it can prove that the internalValueMap field of the corresponding EnumLite is never read. |
| */ |
| public class EnumLiteProtoShrinker { |
| |
| private AppView<AppInfoWithLiveness> appView; |
| private ProtoReferences references; |
| private Set<DexType> deadEnumLiteMaps = Sets.newIdentityHashSet(); |
| |
| public EnumLiteProtoShrinker(AppView<AppInfoWithLiveness> appView, ProtoReferences references) { |
| this.appView = appView; |
| this.references = references; |
| } |
| |
| public Set<DexType> getDeadEnumLiteMaps() { |
| return deadEnumLiteMaps; |
| } |
| |
| private DexField createInternalValueMapField(DexType holder) { |
| return appView |
| .dexItemFactory() |
| .createField(holder, references.enumLiteMapType, references.internalValueMapFieldName); |
| } |
| |
| public void clearDeadEnumLiteMaps() { |
| assert appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled(); |
| // The optimization only enables further enums to be unboxed, no point to run it if enum |
| // unboxing is disabled. |
| if (!appView.options().enableEnumUnboxing) { |
| return; |
| } |
| // The optimization relies on shrinking and member value propagation to actually clear |
| // the anonymous subclasses of EnumLiteMap. |
| if (!appView.options().isShrinking() || !appView.options().enableValuePropagation) { |
| return; |
| } |
| internalClearDeadEnumLiteMaps(); |
| } |
| |
| private void internalClearDeadEnumLiteMaps() { |
| for (DexProgramClass clazz : appView.appInfo().classes()) { |
| if (isDeadEnumLiteMap(clazz)) { |
| deadEnumLiteMaps.add(clazz.getType()); |
| // Clears the EnumLiteMap methods to avoid them being IR processed. |
| clazz.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY); |
| } |
| } |
| } |
| |
| public boolean isDeadEnumLiteMap(DexProgramClass clazz) { |
| if (clazz.getInterfaces().contains(references.enumLiteMapType)) { |
| DexProgramClass enumLite = computeCorrespondingEnumLite(clazz); |
| if (enumLite != null) { |
| DexEncodedField field = |
| enumLite.lookupField(createInternalValueMapField(enumLite.getType())); |
| return field != null |
| && appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field) |
| && !appView.appInfo().isFieldRead(field); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Each EnumLiteMap subclass has only two virtual methods findValueByNumber: |
| * |
| * <ul> |
| * <li>EnumLite findValueByNumber(int) |
| * <li>ConcreteEnumLiteSubType findValueByNumber(int) |
| * </ul> |
| * |
| * <p>The method with the EnumLite return type is the bridge, we extract the concrete EnumLite |
| * subtype from the other method return type. |
| * |
| * <p>We bail out if other virtual methods than the two expected ones are found and return null. |
| */ |
| private DexProgramClass computeCorrespondingEnumLite(DexProgramClass enumLiteMap) { |
| if (enumLiteMap.getMethodCollection().numberOfVirtualMethods() != 2) { |
| return null; |
| } |
| DexType enumLiteCandidate = null; |
| for (DexEncodedMethod virtualMethod : enumLiteMap.virtualMethods()) { |
| if (!matchesFindValueByNumberMethod(virtualMethod.method)) { |
| return null; |
| } |
| if (virtualMethod.returnType() == references.enumLiteType) { |
| continue; |
| } |
| if (enumLiteCandidate != null) { |
| return null; |
| } |
| enumLiteCandidate = virtualMethod.returnType(); |
| } |
| if (enumLiteCandidate == null) { |
| return null; |
| } |
| DexProgramClass enumLite = appView.programDefinitionFor(enumLiteCandidate, enumLiteMap); |
| if (enumLite != null |
| && enumLite.isEnum() |
| && enumLite.interfaces.contains(references.enumLiteType)) { |
| return enumLite; |
| } |
| return null; |
| } |
| |
| private boolean matchesFindValueByNumberMethod(DexMethod method) { |
| return method.name == references.findValueByNumberName |
| && method.getArity() == 1 |
| && method.getParameters().values[0] == appView.dexItemFactory().intType; |
| } |
| |
| public void verifyDeadEnumLiteMapsAreDead() { |
| for (DexType deadEnumLiteMap : deadEnumLiteMaps) { |
| if (appView.appInfo().definitionForWithoutExistenceAssert(deadEnumLiteMap) != null) { |
| throw new CompilationError( |
| "EnumLite Proto Shrinker failure: Type " |
| + deadEnumLiteMap |
| + " was assumed to be dead during optimizations, but it is not."); |
| } |
| } |
| } |
| } |