blob: 0b04f9ba33e330f0e7a924e675158ec8f9e5bec4 [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.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().isProtoEnumShrinkingEnabled();
// 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.");
}
}
}
}