blob: 4610f41f4ab4a10f5936b900019a048e8aa09d14 [file] [log] [blame]
// Copyright (c) 2017, 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.shaking.protolite;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
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.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeInterface;
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.MemberType;
import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.Switch;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.SwitchUtils;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.BiPredicate;
/**
* Implements the second phase of proto lite tree shaking.
* <p>
* In the pruner pass, we remove all references to the now dead fields from the methods that
* were treated specially during tree shaking using the {@link ProtoLiteExtension}.
* <p>
* For proto2 code, we aim to keep presence information for fields alive and move the values of
* dead fields to the unknown fields storage. For proto3, as it has no concept of passing on
* unknown fields, dead fields are completely removed. In particular, reading a proto and writing
* it again might loose data.
*/
public class ProtoLitePruner extends ProtoLiteBase {
private final AppInfoWithLiveness appInfo;
private final DexType visitorType;
private final DexType methodEnumType;
private final DexType codedOutputStreamType;
private final DexType protobufListType;
private final DexType listType;
private final DexString visitTag;
private final DexString mergeTag;
private final DexString isInitializedTag;
private final DexString makeImmutabkeTag;
private final DexString computeMethodPrefix;
private final DexString writeMethodPrefix;
private final DexString isInitializedMethodName;
private final DexString makeImmutableMethodName;
private final DexString sizeMethodName;
private final Set<DexField> seenFields;
private final DexMethod sizeMethod;
public ProtoLitePruner(AppInfoWithLiveness appInfo) {
super(appInfo);
this.appInfo = appInfo;
DexItemFactory factory = appInfo.dexItemFactory;
this.visitorType = factory.createType("Lcom/google/protobuf/GeneratedMessageLite$Visitor;");
this.methodEnumType = factory
.createType("Lcom/google/protobuf/GeneratedMessageLite$MethodToInvoke;");
this.codedOutputStreamType = factory.createType("Lcom/google/protobuf/CodedOutputStream;");
this.protobufListType = factory.createType("Lcom/google/protobuf/Internal$ProtobufList;");
this.listType = factory.createType("Ljava/util/List;");
this.visitTag = factory.createString("VISIT");
this.mergeTag = factory.createString("MERGE_FROM_STREAM");
this.isInitializedTag = factory.createString("IS_INITIALIZED");
this.makeImmutabkeTag = factory.createString("MAKE_IMMUTABLE");
this.computeMethodPrefix = factory.createString("compute");
this.writeMethodPrefix = factory.createString("write");
this.sizeMethodName = factory.createString("size");
this.isInitializedMethodName = factory.createString("isInitialized");
this.makeImmutableMethodName = factory.createString("makeImmutable");
this.sizeMethod = factory.createMethod(listType,
factory.createProto(factory.intType), sizeMethodName);
seenFields = appInfo.withLiveness()
.getExtension(ProtoLiteExtension.class, Collections.emptySet());
}
private boolean isPresenceField(DexField field, DexType instanceType) {
if (field.getHolder() != instanceType) {
return false;
}
// Proto2 uses fields named bitField<n>_ fields to store presence information.
String fieldName = field.name.toString();
return fieldName.endsWith("_")
&& fieldName.startsWith("bitField");
}
boolean isSetterThatNeedsProcessing(DexEncodedMethod method) {
// The pruner does not need to process setters, so this method always returns false.
return false;
}
private boolean isGetter(DexMethod method) {
return isGetterHelper(method, 0);
}
private boolean isCountGetter(DexMethod method) {
return isGetterHelper(method, COUNT_POSTFIX_LENGTH);
}
private boolean isGetterHelper(DexMethod method, int postFixLength) {
if (!method.name.beginsWith(getterNamePrefix)
|| !(method.proto.parameters.isEmpty() || hasSingleIntArgument(method))
|| (method.holder == messageType)
|| !method.holder.isSubtypeOf(messageType, appInfo)
|| method.name.size <= GETTER_NAME_PREFIX_LENGTH + postFixLength) {
return false;
}
DexField correspondingField = getterToField(method, postFixLength);
return seenFields.contains(correspondingField);
}
private boolean isDefinedAsNull(Value value) {
return value.definition != null && value.definition.isConstNumber()
&& value.definition.asConstNumber().isZero();
}
private boolean isComputeSizeMethod(DexMethod invokedMethod) {
return invokedMethod.holder == codedOutputStreamType
&& invokedMethod.name.beginsWith(computeMethodPrefix);
}
private boolean isWriteMethod(DexMethod invokedMethod) {
return invokedMethod.holder == codedOutputStreamType
&& invokedMethod.name.beginsWith(writeMethodPrefix);
}
private boolean isProtoField(DexField field) {
return seenFields.contains(field);
}
public void rewriteProtoLiteSpecialMethod(IRCode code, DexEncodedMethod method) {
DexString methodName = method.method.name;
if (methodName == dynamicMethodName) {
rewriteDynamicMethod(code, method);
} else if ((methodName == writeToMethodName) || (methodName == getSerializedSizeMethodName)) {
rewriteSizeOrWriteMethod(code);
} else if (methodName == constructorMethodName) {
rewriteConstructor(code);
} else {
throw new Unreachable();
}
}
/**
* For protos with repeated fields, the constructor may contain field initialization code like
*
* <pre>
* private Repeated() {
* repeated_ = com.google.protobuf.GeneratedMessageLite.emptyProtobufList();
* other_ = emptyBooleanList();
* sub_ = emptyProtobufList();
* }
* </pre>
*
* which this rewriting removes.
*/
private void rewriteConstructor(IRCode code) {
boolean wasRewritten;
do {
wasRewritten = false;
InstructionIterator it = code.instructionIterator();
while (it.hasNext()) {
Instruction insn = it.next();
if (insn.isInstancePut() && isDeadProtoField(insn.asInstancePut().getField())) {
// Remove initializations of dead fields.
it.remove();
wasRewritten = true;
} else if (insn.isInvokeStatic()) {
// Remove now unneeded constructor calls.
InvokeStatic invokeStatic = insn.asInvokeStatic();
DexMethod invokedMethod = invokeStatic.getInvokedMethod();
if ((!invokeStatic.outValue().isUsed())
&& invokedMethod.proto.returnType.isSubtypeOf(protobufListType, appInfo)) {
it.remove();
}
}
}
} while (wasRewritten);
}
/**
* The writeTo and getSerializedSize methods of a generated proto access all fields. We have to
* remove the accesses to dead fields. The actual code of these methods varies to quite some
* degree depending on the types of the fields. For example
*
* <pre>
* public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException {
* if (((bitField0_ & 0x00000001) == 0x00000001)) {
* output.writeInt32(1, id_);
* }
* if (((bitField0_ & 0x00000002) == 0x00000002)) {
* output.writeMessage(2, getInner());
* }
* for (int i = 0; i < repeated_.size(); i++) {
* output.writeString(3, repeated_.get(i));
* }
* for (int i = 0; i < other_.size(); i++) {
* output.writeBool(4, other_.getBoolean(i));
* }
* for (int i = 0; i < sub_.size(); i++) {
* output.writeMessage(5, sub_.get(i));
* }
* unknownFields.writeTo(output);
* }
* </pre>
*
* We look for direct field accesses (id_, repeated_, etc. above) and getters (getInner) and
* rewrite those to null/0. We also rewrite all uses of the results, like the size() and
* write methods above.
*/
private void rewriteSizeOrWriteMethod(IRCode code) {
boolean wasRewritten;
do {
wasRewritten = false;
InstructionIterator it = code.instructionIterator();
while (it.hasNext()) {
Instruction insn = it.next();
if (insn.isInstanceGet()) {
DexField field = insn.asInstanceGet().getField();
if (isDeadProtoField(field)) {
// Rewrite deads field access to corresponding 0.
it.replaceCurrentInstruction(code.createConstNull(insn.asInstanceGet()));
wasRewritten = true;
}
} else if (insn.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invokeMethod = insn.asInvokeMethodWithReceiver();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (isDeadProtoGetter(invokedMethod)) {
// Rewrite dead getters.
it.replaceCurrentInstruction(code.createConstNull(invokeMethod));
wasRewritten = true;
} else if (invokedMethod.name == sizeMethodName
&& invokedMethod.holder.isSubtypeOf(listType, appInfo)) {
Value receiver = invokeMethod.getReceiver();
if (isDefinedAsNull(receiver)) {
// Rewrite size() methods with null receiver.
it.replaceCurrentInstruction(code.createConstNull(invokeMethod));
}
} else if (invokedMethod.name == getterNamePrefix
&& invokedMethod.holder.isSubtypeOf(listType, appInfo)) {
Value receiver = invokeMethod.getReceiver();
if (isDefinedAsNull(receiver)) {
// Rewrite get(x) methods with null receiver.
it.replaceCurrentInstruction(code.createConstNull(invokeMethod));
wasRewritten = true;
}
} else if (isWriteMethod(invokedMethod)) {
Value lastArg = Iterables.getLast(invokeMethod.inValues());
if (isDefinedAsNull(lastArg)) {
it.remove();
}
}
} else if (insn.isInvokeMethod()) {
InvokeMethod invokeMethod = insn.asInvokeMethod();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (isComputeSizeMethod(invokedMethod)) {
Value lastArg = Iterables.getLast(invokeMethod.inValues());
if (isDefinedAsNull(lastArg)) {
// This is a computeSize method on a constant null. The field was dead.
assert (invokeMethod.outValue() != null);
it.replaceCurrentInstruction(code.createConstNull(invokeMethod));
wasRewritten = true;
}
}
}
}
} while (wasRewritten);
}
/**
* The dyanmicMethod code is actually a collection of various methods contained in one.
* It uses a switch statement over an enum to identify which actual operation is performed.
* We need to rewrite all cases that might access dead fields. These are
* <dl>
* <dt>IS_INITIALIZED</dt>
* <dd>See {@link #rewriteIsInitializedCase}.</dd>
* <dt>MAKE_IMMUTABLE</dt>
* <dd>See {@link #rewriteMakeImmutableCase}.</dd>
* <dt>VISIT</dt>
* <dd>See {@link #rewriteVisitCase}.</dd>
* <dt>MERGE_FROM_STREAM</dt>
* <dd>See {@link #rewriteMergeCase}.</dd>
* </dl>
*/
private void rewriteDynamicMethod(IRCode code, DexEncodedMethod method) {
// This method contains a switch and we are interested in some of the cases only.
InstructionIterator iterator = code.instructionIterator();
Instruction matchingInstr = iterator.nextUntil(Instruction::isSwitch);
if (matchingInstr == null) {
throw new CompilationError("dynamicMethod in protoLite without switch.");
}
Switch switchInstr = matchingInstr.asSwitch();
EnumSwitchInfo info = SwitchUtils.analyzeSwitchOverEnum(switchInstr, appInfo);
if (info == null || info.enumClass != methodEnumType) {
throw new CompilationError("Malformed switch in dynamicMethod of proto lite.");
}
BasicBlock initializedCase = null;
BasicBlock visitCase = null;
BasicBlock mergeCase = null;
BasicBlock makeImmutableCase = null;
for (int keyIdx = 0; keyIdx < switchInstr.numberOfKeys(); keyIdx++) {
int key = switchInstr.getKey(keyIdx);
DexField label = info.indexMap.get(key);
assert label != null;
if (label.name == visitTag) {
assert visitCase == null;
visitCase = switchInstr.targetBlock(keyIdx);
} else if (label.name == mergeTag) {
assert mergeCase == null;
mergeCase = switchInstr.targetBlock(keyIdx);
} else if (label.name == isInitializedTag) {
assert initializedCase == null;
initializedCase = switchInstr.targetBlock(keyIdx);
} else if (label.name == makeImmutabkeTag) {
assert makeImmutableCase == null;
makeImmutableCase = switchInstr.targetBlock(keyIdx);
}
}
DexType instanceType = method.method.getHolder();
rewriteIsInitializedCase(initializedCase, instanceType, code);
assert code.isConsistentSSA();
rewriteMakeImmutableCase(makeImmutableCase, code);
assert code.isConsistentSSA();
rewriteVisitCase(visitCase, code);
assert code.isConsistentSSA();
rewriteMergeCase(mergeCase, instanceType, code);
assert code.isConsistentSSA();
}
/**
* In the presence of repeated fields, the MAKE_IMMUTABLE case will contain code of the form
*
* <pre>
* case MAKE_IMMUTABLE: {
* repeated_.makeImmutable();
* other_.makeImmutable();
* sub_.makeImmutable();
* return null;
* }
* </pre>
*
* For dead fields, we remove the field access and the call to makeImmutable.
*/
private void rewriteMakeImmutableCase(BasicBlock switchCase, IRCode code) {
DominatorTree dom = new DominatorTree(code);
boolean wasRewritten;
do {
wasRewritten = false;
for (BasicBlock current : dom.dominatedBlocks(switchCase)) {
InstructionIterator it = current.iterator();
while (it.hasNext()) {
Instruction insn = it.next();
if (insn.isInstanceGet() && isDeadProtoField(insn.asInstanceGet().getField())) {
it.replaceCurrentInstruction(code.createConstNull(insn));
wasRewritten = true;
} else if (insn.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invokeMethod = insn.asInvokeMethodWithReceiver();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (isDeadProtoGetter(invokedMethod)) {
it.replaceCurrentInstruction(code.createConstNull(invokeMethod));
wasRewritten = true;
} else if (invokedMethod.name == makeImmutableMethodName
&& invokedMethod.getHolder().isSubtypeOf(protobufListType, appInfo)) {
Value receiver = invokeMethod.getReceiver();
if (isDefinedAsNull(receiver)) {
it.remove();
}
}
}
}
}
} while (wasRewritten);
}
/**
* The IS_INITIALIZED case also has a high degree of variability depending on the type of fields.
* Common code looks as follows
*
* <pre>
* case IS_INITIALIZED: {
* byte isInitialized = memoizedIsInitialized;
* if (isInitialized == 1) return DEFAULT_INSTANCE;
* if (isInitialized == 0) return null;
*
* boolean shouldMemoize = ((Boolean) arg0).booleanValue();
* if (!hasId()) {
* if (shouldMemoize) {
* memoizedIsInitialized = 0;
* }
* return null;
* }
* for (int i = 0; i < getSubCount(); i++) {
* if (!getSub(i).isInitialized()) {
* if (shouldMemoize) {
* memoizedIsInitialized = 0;
* }
* return null;
* }
* }
* if (shouldMemoize) memoizedIsInitialized = 1;
* return DEFAULT_INSTANCE;
* }
* </pre>
*
* We remove all the accesses of dead fields and getters. We also replace secondary invokes
* (like getSubCount or isInitialized) with conservative default values (e.g. 0, true).
* <p>
* We also rewrite getXXXCount methods on live fields, as accesses to those methods was filtered
* during tree shaking. In place of those methods, we inline their definition.
*/
private void rewriteIsInitializedCase(BasicBlock switchCase, DexType instanceType,
IRCode code) {
DominatorTree dom = new DominatorTree(code);
boolean wasRewritten;
do {
wasRewritten = false;
for (BasicBlock current : dom.dominatedBlocks(switchCase)) {
InstructionIterator it = current.iterator();
while (it.hasNext()) {
Instruction insn = it.next();
if (insn.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invokeMethod = insn.asInvokeMethodWithReceiver();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (isDeadProtoGetter(invokedMethod)) {
it.replaceCurrentInstruction(code.createConstNull(invokeMethod));
wasRewritten = true;
} else if (invokedMethod.name == isInitializedMethodName
&& invokedMethod.getHolder().isSubtypeOf(messageType, appInfo)) {
Value receiver = invokeMethod.getReceiver();
if (isDefinedAsNull(receiver)) {
// We cannot compute initialization state for nested messages and repeated
// messages that have been removed or moved to unknown fields. Just return
// true.
it.replaceCurrentInstruction(code.createTrue());
wasRewritten = true;
}
} else if (isCountGetter(invokedMethod)) {
// We have to rewrite these as a precaution, as they might be dead due to
// tree shaking ignoring them.
DexField field = getterToField(invokedMethod, 5);
if (appInfo.liveFields.contains(field)) {
// Effectively inline the code that is normally inside these methods.
Value thisReference = invokeMethod.getReceiver();
Value newResult = code.createValue(MoveType.SINGLE);
invokeMethod.outValue().replaceUsers(newResult);
Value theList = code.createValue(MoveType.OBJECT);
it.replaceCurrentInstruction(
new InstanceGet(MemberType.OBJECT, theList, thisReference, field));
it.add(new InvokeInterface(sizeMethod, newResult, Collections.emptyList()));
} else {
// The field is dead, so its count is always 0.
it.replaceCurrentInstruction(code.createConstNull(invokeMethod));
}
}
}
}
}
} while (wasRewritten);
}
private InstancePut findProtoFieldWrite(BasicBlock block, DexType instanceType,
BiPredicate<DexField, DexType> filter, DominatorTree dom) {
for (BasicBlock current : dom.dominatedBlocks(block)) {
InstructionIterator insns = current.iterator();
InstancePut instancePut = (InstancePut) insns.nextUntil(Instruction::isInstancePut);
if (instancePut != null && filter.test(instancePut.getField(), instanceType)) {
return instancePut;
}
}
return null;
}
/**
* For the merge case, we typically see code that contains a switch over the field tags. The inner
* switch has the form
*
* <pre>
* switch (tag) {
* case 0:
* done = true;
* break;
* default: {
* if (!parseUnknownField(tag, input)) {
* done = true;
* }
* break;
* }
* case 8: {
* bitField0_ |= 0x00000001;
* id_ = input.readInt32();
* break;
* }
* case 18: {
* nestedproto2.GeneratedNestedProto.NestedOne.Builder subBuilder = null;
* if (((bitField0_ & 0x00000002) == 0x00000002)) {
* subBuilder = inner_.toBuilder();
* }
* inner_ = input.readMessage(nestedproto2.GeneratedNestedProto.NestedOne.parser(),
* extensionRegistry);
* if (subBuilder != null) {
* subBuilder.mergeFrom(inner_);
* inner_ = subBuilder.buildPartial();
* }
* bitField0_ |= 0x00000002;
* break;
* }
* case 24: {
* if (!other_.isModifiable()) {
* other_ =
* com.google.protobuf.GeneratedMessageLite.mutableCopy(other_);
* }
* other_.addBoolean(input.readBool());
* break;
* }
* case 26: {
* int length = input.readRawVarint32();
* int limit = input.pushLimit(length);
* if (!other_.isModifiable() && input.getBytesUntilLimit() > 0) {
* final int currentSize = other_.size();
* other_ = other_.mutableCopyWithCapacity(
* currentSize + (length/1));
* }
* while (input.getBytesUntilLimit() > 0) {
* other_.addBoolean(input.readBool());
* }
* input.popLimit(limit);
* break;
* }
* }
* </pre>
*
* The general approach here is to identify the field that is processed by a case and, if
* the field is dead, remove the entire case.
* <p>
* We slightly complicate the rewriting by also checking whether the block computes a
* presence bitfield (bitField0_ above). If so, we move that computation to a new block that
* continues to the default case. This ensures that presence is recorded correctly, yet the
* field is moved to the unknownFields collection, if such exists.
*/
private void rewriteMergeCase(BasicBlock caseBlock, DexType instanceType,
IRCode code) {
// We are looking for a switch statement over the input tag. Just traverse all blocks until
// we find it.
List<BasicBlock> deadBlocks = new ArrayList<>();
DominatorTree dom = new DominatorTree(code);
for (BasicBlock current : dom.dominatedBlocks(caseBlock)) {
InstructionIterator it = current.iterator();
Switch switchInstr;
if ((switchInstr = (Switch) it.nextUntil(Instruction::isSwitch)) != null) {
int nextBlock = code.getHighestBlockNumber() + 1;
IntList liveKeys = new IntArrayList(switchInstr.numberOfKeys());
List<BasicBlock> liveBlocks = new ArrayList<>(switchInstr.numberOfKeys());
boolean needsCleanup = false;
// Filter out all the cases that contain writes to dead fields.
for (int keyIdx = 0; keyIdx < switchInstr.numberOfKeys(); keyIdx++) {
BasicBlock targetBlock = switchInstr.targetBlock(keyIdx);
InstancePut instancePut =
findProtoFieldWrite(targetBlock, instanceType, (field, holder) -> isProtoField(field),
dom);
if (instancePut == null
|| appInfo.withLiveness().liveFields.contains(instancePut.getField())) {
// This is a live case. Keep it.
liveKeys.add(switchInstr.getKey(keyIdx));
liveBlocks.add(targetBlock);
} else {
// We cannot just remove this entire switch case if there is some computation here
// for whether the field is present. We check this by searching for a write to
// the bitField<xxx>_ fields. If such write exists, we move the corresponding
// instructions to the first block in the switch.
//TODO(herhut): Only do this if the written field has a live hasMethod.
InstancePut bitFieldUpdate = findProtoFieldWrite(targetBlock, instanceType,
this::isPresenceField, dom);
if (bitFieldUpdate != null) {
BasicBlock newBlock = BasicBlock.createGotoBlock(nextBlock++);
newBlock.link(switchInstr.fallthroughBlock());
// Copy over the computation of the field;
moveInstructionTo(newBlock.listIterator(), bitFieldUpdate, dom, targetBlock);
switchInstr.getBlock().link(newBlock);
liveKeys.add(switchInstr.getKey(keyIdx));
liveBlocks.add(newBlock);
code.blocks.add(newBlock);
}
needsCleanup = true;
}
}
if (needsCleanup) {
DominatorTree updatedTree = new DominatorTree(code);
BasicBlock fallThrough = switchInstr.fallthroughBlock();
List<BasicBlock> successors = ImmutableList.copyOf(current.getNormalSucessors());
for (BasicBlock successor : successors) {
if (successor != fallThrough && !liveBlocks.contains(successor)) {
deadBlocks.addAll(current.unlink(successor, updatedTree));
}
}
int[] blockIndices = new int[liveBlocks.size()];
for (int i = 0; i < liveBlocks.size(); i++) {
blockIndices[i] = current.getSuccessors().indexOf(liveBlocks.get(i));
}
Switch newSwitch = new Switch(switchInstr.inValues().get(0), liveKeys.toIntArray(),
blockIndices, current.getSuccessors().indexOf(fallThrough));
it.replaceCurrentInstruction(newSwitch);
}
break;
}
}
code.removeBlocks(deadBlocks);
}
//TODO(herhut): This should really be a copy with a value substitution map.
private void moveInstructionTo(InstructionListIterator iterator, Instruction insn,
DominatorTree dom,
BasicBlock dominator) {
for (Value value : insn.inValues()) {
Instruction input = value.definition;
// We do not support phis.
assert input != null;
if (dom.dominatedBy(input.getBlock(), dominator)) {
// And no shared instructions.
assert input.outValue().numberOfUsers() == 1;
moveInstructionTo(iterator, input, dom, dominator);
}
}
insn.getBlock().removeInstruction(insn);
iterator.add(insn);
}
private boolean isDeadProtoField(DexField field) {
return isProtoField(field) && !appInfo.liveFields.contains(field);
}
private boolean isDeadProtoGetter(DexMethod method) {
return isGetter(method) && isDeadProtoField(getterToField(method));
}
private boolean isVisitOfDeadField(Instruction instruction) {
if (!instruction.isInvokeMethod()) {
return false;
}
InvokeMethod invokeMethod = instruction.asInvokeMethod();
if (invokeMethod.getInvokedMethod().getHolder() == visitorType
&& invokeMethod.getInvokedMethod().getArity() >= 2) {
Instruction secondArg = invokeMethod.inValues().get(2).definition;
return secondArg.isConstNumber();
}
return false;
}
/**
* The visit case has typically the form
*
* <pre>
* case VISIT: {
* Visitor visitor = (Visitor) arg0;
* repeatedproto.GeneratedRepeatedProto.Repeated other =
* (repeatedproto.GeneratedRepeatedProto.Repeated) arg1;
* id_ = visitor.visitInt(
* hasId(), id_,
* other.hasId(), other.id_);
* repeated_= visitor.visitList(repeated_, other.repeated_);
* inner_ = visitor.visitMessage(inner_, other.inner_);
* if (visitor == com.google.protobuf.GeneratedMessageLite.MergeFromVisitor.INSTANCE) {
* bitField0_ |= other.bitField0_;
* }
* return this;
* }
* </pre>
*
* We remove all writes and reads to dead fields and correspondign secondary instructions, like
* the visitXXX methods.
* <p>
* Note that the invoked hasMethods are benign, as the only access the bitFieldXXX_ fields, which
* we currently do not remove. Inlining will likely remove the methods.
*/
private void rewriteVisitCase(BasicBlock switchCase, IRCode code) {
DominatorTree dom = new DominatorTree(code);
boolean wasRewritten;
do {
wasRewritten = false;
for (BasicBlock target : dom.dominatedBlocks(switchCase)) {
InstructionIterator it = target.iterator();
while (it.hasNext()) {
Instruction insn = it.next();
if (insn.isInstanceGet()) {
InstanceGet instanceGet = insn.asInstanceGet();
if (isDeadProtoField(instanceGet.getField())) {
it.replaceCurrentInstruction(code.createConstNull(instanceGet));
wasRewritten = true;
}
} else if (insn.isInstancePut()) {
if (isDeadProtoField(insn.asInstancePut().getField())) {
it.remove();
}
} else if (isVisitOfDeadField(insn)) {
it.replaceCurrentInstruction(code.createConstNull(insn));
} else if (insn.isCheckCast()) {
// The call to visitXXX is a generic method invoke, so it will be followed by a check
// cast to fix up the type. As the result is no longer needed once we are done, we can
// remove the cast. This removes a potential last reference to an inner message class.
// TODO(herhut): We should have a generic dead cast removal.
Value inValue = insn.inValues().get(0);
if (isDefinedAsNull(inValue)) {
insn.outValue().replaceUsers(inValue);
it.remove();
}
}
}
}
} while (wasRewritten);
}
}