blob: 104b854dadf4ee7d7c32b1872449e6e5dd6fa4aa [file] [log] [blame]
// Copyright (c) 2019, 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 static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getObjectsValueFromMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.setObjectsValueForMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
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.ProgramMethod;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRCodeUtils;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.DependentMinimumKeepInfoCollection;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
public class GeneratedMessageLiteShrinker {
private final AppView<?> appView;
private final RawMessageInfoDecoder decoder;
private final RawMessageInfoEncoder encoder;
private final ProtoReferences references;
private final TypeElement objectArrayType;
private final TypeElement stringType;
public GeneratedMessageLiteShrinker(
AppView<?> appView, RawMessageInfoDecoder decoder, ProtoReferences references) {
this.appView = appView;
this.decoder = decoder;
this.encoder = new RawMessageInfoEncoder(appView.dexItemFactory());
this.references = references;
// Types.
this.objectArrayType =
TypeElement.fromDexType(
appView.dexItemFactory().objectArrayType, definitelyNotNull(), appView);
this.stringType = TypeElement.stringClassType(appView, definitelyNotNull());
}
public void extendRootSet(DependentMinimumKeepInfoCollection dependentMinimumKeepInfo) {
// Disable optimizations for various methods that are modeled, to ensure that we can still
// recognize the uses of these methods even after optimizations have been run.
DexProgramClass generatedMessageLiteClass =
asProgramClassOrNull(
appView
.appInfo()
.definitionForWithoutExistenceAssert(references.generatedMessageLiteType));
if (generatedMessageLiteClass != null) {
ProgramMethod dynamicMethod =
generatedMessageLiteClass.lookupProgramMethod(references.dynamicMethod);
if (dynamicMethod != null) {
disallowSignatureOptimizations(
dependentMinimumKeepInfo
.getOrCreateUnconditionalMinimumKeepInfoFor(dynamicMethod.getReference())
.asMethodJoiner());
}
references.forEachMethodReference(
reference -> {
DexProgramClass holder =
asProgramClassOrNull(appView.definitionFor(reference.getHolderType()));
ProgramMethod method = reference.lookupOnProgramClass(holder);
if (method != null) {
disallowSignatureOptimizations(
dependentMinimumKeepInfo
.getOrCreateUnconditionalMinimumKeepInfoFor(method.getReference())
.asMethodJoiner());
}
});
}
}
private void disallowSignatureOptimizations(KeepMethodInfo.Joiner methodJoiner) {
methodJoiner
.disallowConstantArgumentOptimization()
.disallowMethodStaticizing()
.disallowParameterRemoval()
.disallowParameterReordering()
.disallowParameterTypeStrengthening()
.disallowReturnTypeStrengthening()
.disallowUnusedArgumentOptimization()
.disallowUnusedReturnValueOptimization();
}
public void run(IRCode code) {
ProgramMethod method = code.context();
if (references.isDynamicMethod(method.getReference())) {
rewriteDynamicMethod(method, code);
} else if (appView.hasLiveness()) {
optimizeNewMutableInstance(appView.withLiveness(), code);
}
}
private void optimizeNewMutableInstance(AppView<AppInfoWithLiveness> appView, IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
BasicBlockIterator blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
InstructionListIterator instructionIterator = block.listIterator(code);
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
DexType newMutableInstanceType = getNewMutableInstanceType(appView, instruction);
if (newMutableInstanceType == null) {
continue;
}
DexMethod instanceInitializerReference =
appView.dexItemFactory().createInstanceInitializer(newMutableInstanceType);
ProgramMethod instanceInitializer =
asProgramMethodOrNull(appView.definitionFor(instanceInitializerReference));
if (instanceInitializer == null
|| AccessControl.isMemberAccessible(
instanceInitializer, instanceInitializer.getHolder(), code.context(), appView)
.isPossiblyFalse()) {
continue;
}
NewInstance newInstance =
NewInstance.builder()
.setType(newMutableInstanceType)
.setFreshOutValue(
code, newMutableInstanceType.toTypeElement(appView, definitelyNotNull()))
.setPosition(instruction)
.build();
instructionIterator.replaceCurrentInstruction(newInstance, affectedValues);
InvokeDirect constructorInvoke =
InvokeDirect.builder()
.setMethod(instanceInitializerReference)
.setSingleArgument(newInstance.outValue())
.setPosition(instruction)
.build();
if (block.hasCatchHandlers()) {
// Split the block after the new-instance instruction and insert the constructor call in
// the split block.
BasicBlock splitBlock =
instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options());
instructionIterator = splitBlock.listIterator(code);
instructionIterator.add(constructorInvoke);
BasicBlock previousBlock =
blockIterator.previousUntil(previous -> previous == splitBlock);
assert previousBlock != null;
blockIterator.next();
} else {
instructionIterator.add(constructorInvoke);
}
}
}
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
private DexType getNewMutableInstanceType(
AppView<AppInfoWithLiveness> appView, Instruction instruction) {
if (!instruction.isInvokeMethodWithReceiver()) {
return null;
}
InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
DexMethod invokedMethod = invoke.getInvokedMethod();
if (!references.isDynamicMethod(invokedMethod)
&& !references.isDynamicMethodBridge(invokedMethod)) {
return null;
}
assert invokedMethod.getParameter(0) == references.methodToInvokeType;
if (!references.methodToInvokeMembers.isNewMutableInstanceEnum(
invoke.getFirstNonReceiverArgument())) {
return null;
}
TypeElement receiverType = invoke.getReceiver().getDynamicUpperBoundType(appView);
if (!receiverType.isClassType()) {
return null;
}
DexType rawReceiverType = receiverType.asClassType().getClassType();
return appView.appInfo().isStrictSubtypeOf(rawReceiverType, references.generatedMessageLiteType)
? rawReceiverType
: null;
}
public void postOptimizeDynamicMethods(
IRConverter converter, ExecutorService executorService, Timing timing)
throws ExecutionException {
timing.begin("[Proto] Post optimize dynamic methods");
ProgramMethodSet wave = ProgramMethodSet.create(this::forEachDynamicMethod);
OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
methodProcessor.forEachWaveWithExtension(
(method, methodProcessingContext) ->
converter.processDesugaredMethod(
method,
OptimizationFeedbackIgnore.getInstance(),
methodProcessor,
methodProcessingContext),
executorService);
timing.end();
}
private void forEachDynamicMethod(Consumer<ProgramMethod> consumer) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
appView
.appInfoWithLiveness()
.forEachInstantiatedSubType(
references.generatedMessageLiteType,
clazz -> {
DexMethod dynamicMethodReference =
dexItemFactory.createMethod(
clazz.type, references.dynamicMethodProto, references.dynamicMethodName);
ProgramMethod dynamicMethod = clazz.lookupProgramMethod(dynamicMethodReference);
if (dynamicMethod != null) {
consumer.accept(dynamicMethod);
}
},
lambda -> {
assert false;
});
}
/**
* Finds all const-string instructions in the code that flows into GeneratedMessageLite.
* newMessageInfo(), and rewrites them into a dex-item-based-const-string if the string value
* corresponds to the name of an instance field of the enclosing class.
*
* <p>NOTE: This is work in progress. Understanding the full semantics of the arguments passed to
* newMessageInfo is still pending.
*/
private void rewriteDynamicMethod(ProgramMethod method, IRCode code) {
InvokeMethod newMessageInfoInvoke = getNewMessageInfoInvoke(code, references);
if (newMessageInfoInvoke != null) {
Value infoValue =
getInfoValueFromMessageInfoConstructionInvoke(newMessageInfoInvoke, references);
Value objectsValue =
getObjectsValueFromMessageInfoConstructionInvoke(newMessageInfoInvoke, references);
// Decode the arguments passed to newMessageInfo().
ProtoMessageInfo protoMessageInfo = decoder.run(method, infoValue, objectsValue);
if (protoMessageInfo != null) {
// Rewrite the arguments to newMessageInfo().
rewriteArgumentsToNewMessageInfo(code, newMessageInfoInvoke, infoValue, protoMessageInfo);
// Ensure that the definition of the original `objects` value is removed.
IRCodeUtils.removeArrayAndTransitiveInputsIfNotUsed(code, objectsValue.definition);
} else {
// We should generally be able to decode the arguments passed to newMessageInfo().
assert false;
}
}
}
private void rewriteArgumentsToNewMessageInfo(
IRCode code,
InvokeMethod newMessageInfoInvoke,
Value infoValue,
ProtoMessageInfo protoMessageInfo) {
rewriteInfoArgumentToNewMessageInfo(code, infoValue, protoMessageInfo);
rewriteObjectsArgumentToNewMessageInfo(code, newMessageInfoInvoke, protoMessageInfo);
}
private void rewriteInfoArgumentToNewMessageInfo(
IRCode code, Value infoValue, ProtoMessageInfo protoMessageInfo) {
infoValue.definition.replace(
new ConstString(code.createValue(stringType), encoder.encodeInfo(protoMessageInfo)), code);
}
private void rewriteObjectsArgumentToNewMessageInfo(
IRCode code,
InvokeMethod newMessageInfoInvoke,
ProtoMessageInfo protoMessageInfo) {
// Position iterator immediately before the call to newMessageInfo().
BasicBlock block = newMessageInfoInvoke.getBlock();
InstructionListIterator instructionIterator = block.listIterator(code, newMessageInfoInvoke);
Instruction previous = instructionIterator.previous();
instructionIterator.setInsertionPosition(newMessageInfoInvoke.getPosition());
assert previous == newMessageInfoInvoke;
// Create the `objects` array.
List<ProtoObject> objects = encoder.encodeObjects(protoMessageInfo);
Value sizeValue =
instructionIterator.insertConstIntInstruction(code, appView.options(), objects.size());
Value newObjectsValue = code.createValue(objectArrayType);
// Populate the `objects` array.
var rewriteOptions = appView.options().rewriteArrayOptions();
if (rewriteOptions.canUseFilledNewArrayOfNonStringObjects()
&& objects.size() < rewriteOptions.maxSizeForFilledNewArrayOfReferences) {
List<Value> arrayValues = new ArrayList<>(objects.size());
for (int i = 0; i < objects.size(); i++) {
Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
instructionIterator.add(materializingInstruction);
arrayValues.add(materializingInstruction.outValue());
}
instructionIterator.add(
new InvokeNewArray(
appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues));
} else {
instructionIterator.add(
new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
for (int i = 0; i < objects.size(); i++) {
Value indexValue =
instructionIterator.insertConstIntInstruction(code, appView.options(), i);
Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
instructionIterator.add(materializingInstruction);
instructionIterator.add(
new ArrayPut(
MemberType.OBJECT,
newObjectsValue,
indexValue,
materializingInstruction.outValue()));
}
}
// Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
// GeneratedMessageLite.newMessageInfo().
setObjectsValueForMessageInfoConstructionInvoke(
newMessageInfoInvoke, newObjectsValue, references);
}
public static InvokeMethod getNewMessageInfoInvoke(IRCode code, ProtoReferences references) {
for (Instruction instruction : code.instructions()) {
if (instruction.isInvokeMethod()) {
InvokeMethod invoke = instruction.asInvokeMethod();
if (references.isMessageInfoConstructionMethod(invoke.getInvokedMethod())) {
return invoke;
}
}
}
return null;
}
}