blob: 79165d119d282bb8df6a65699e663543d6640f0a [file] [log] [blame]
// Copyright (c) 2022, 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;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
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.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.FieldGet;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InitClass;
import com.android.tools.r8.ir.code.InstanceFieldInstruction;
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.InstructionListIterator;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
public class EnqueuerDeferredTracingRewriter {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final CodeRewriter codeRewriter;
private final DeadCodeRemover deadCodeRemover;
EnqueuerDeferredTracingRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
this.codeRewriter = new CodeRewriter(appView);
this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
}
public CodeRewriter getCodeRewriter() {
return codeRewriter;
}
public DeadCodeRemover getDeadCodeRemover() {
return deadCodeRemover;
}
public void rewriteCode(
IRCode code,
Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
Map<DexField, ProgramField> prunedFields) {
// TODO(b/205810841): Consider inserting assume instructions to reduce number of null checks.
// TODO(b/205810841): Consider running constant canonicalizer.
ProgramMethod context = code.context();
// Rewrite field instructions that reference a pruned field.
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();
switch (instruction.opcode()) {
case INSTANCE_GET:
rewriteInstanceGet(
code,
instructionIterator,
instruction.asInstanceGet(),
affectedValues,
prunedFields);
break;
case INSTANCE_PUT:
rewriteInstancePut(instructionIterator, instruction.asInstancePut(), prunedFields);
break;
case STATIC_GET:
rewriteStaticGet(
code,
instructionIterator,
instruction.asStaticGet(),
affectedValues,
context,
initializedClassesWithContexts,
prunedFields);
break;
case STATIC_PUT:
rewriteStaticPut(
code,
instructionIterator,
instruction.asStaticPut(),
context,
initializedClassesWithContexts,
prunedFields);
break;
default:
break;
}
}
}
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
private void rewriteInstanceGet(
IRCode code,
InstructionListIterator instructionIterator,
InstanceGet instanceGet,
Set<Value> affectedValues,
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(instanceGet.getField());
if (prunedField == null) {
return;
}
insertDefaultValueForFieldGet(
code, instructionIterator, instanceGet, affectedValues, prunedField);
removeOrReplaceInstanceFieldInstructionWithNullCheck(instructionIterator, instanceGet);
}
private void rewriteInstancePut(
InstructionListIterator instructionIterator,
InstancePut instancePut,
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(instancePut.getField());
if (prunedField == null) {
return;
}
removeOrReplaceInstanceFieldInstructionWithNullCheck(instructionIterator, instancePut);
}
private void rewriteStaticGet(
IRCode code,
InstructionListIterator instructionIterator,
StaticGet staticGet,
Set<Value> affectedValues,
ProgramMethod context,
Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(staticGet.getField());
if (prunedField == null) {
return;
}
insertDefaultValueForFieldGet(
code, instructionIterator, staticGet, affectedValues, prunedField);
removeOrReplaceStaticFieldInstructionByInitClass(
code, instructionIterator, context, initializedClassesWithContexts, prunedField);
}
private void rewriteStaticPut(
IRCode code,
InstructionListIterator instructionIterator,
StaticPut staticPut,
ProgramMethod context,
Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
Map<DexField, ProgramField> prunedFields) {
ProgramField prunedField = prunedFields.get(staticPut.getField());
if (prunedField == null) {
return;
}
removeOrReplaceStaticFieldInstructionByInitClass(
code, instructionIterator, context, initializedClassesWithContexts, prunedField);
}
private void insertDefaultValueForFieldGet(
IRCode code,
InstructionListIterator instructionIterator,
FieldGet fieldGet,
Set<Value> affectedValues,
ProgramField prunedField) {
if (fieldGet.hasUsedOutValue()) {
instructionIterator.previous();
Value replacement =
prunedField.getType().isReferenceType()
? instructionIterator.insertConstNullInstruction(code, appView.options())
: instructionIterator.insertConstNumberInstruction(
code, appView.options(), 0, fieldGet.getOutType());
fieldGet.outValue().replaceUsers(replacement, affectedValues);
instructionIterator.next();
}
}
private void removeOrReplaceInstanceFieldInstructionWithNullCheck(
InstructionListIterator instructionIterator, InstanceFieldInstruction fieldInstruction) {
if (fieldInstruction.object().isMaybeNull()) {
instructionIterator.replaceCurrentInstructionWithNullCheck(
appView, fieldInstruction.object());
} else {
instructionIterator.removeOrReplaceByDebugLocalRead();
}
}
private void removeOrReplaceStaticFieldInstructionByInitClass(
IRCode code,
InstructionListIterator instructionIterator,
ProgramMethod context,
Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
ProgramField prunedField) {
if (prunedField.getHolder().classInitializationMayHaveSideEffectsInContext(appView, context)) {
instructionIterator.replaceCurrentInstruction(
InitClass.builder()
.setFreshOutValue(code, TypeElement.getInt())
.setType(prunedField.getHolderType())
.build());
initializedClassesWithContexts
.computeIfAbsent(prunedField.getHolder(), ignoreKey(ProgramMethodSet::createConcurrent))
.add(context);
} else {
instructionIterator.removeOrReplaceByDebugLocalRead();
}
}
}