Add support for ReachabilitySensitive annotations.
When one of these annotations are on a method or a field in a class,
all methods of that class are compiled in debug mode to make sure
that locals are kept alive throughout their scope. However, in
relase mode builds we still do not output any locals information.
R=sgjesse@google.com, zerny@google.com
Change-Id: Ic76aed45d37cf46c29756087be1503447ac7a409
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index d5221b2..6914bc1 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -145,15 +145,14 @@
}
@Override
- public void allocateRegisters(boolean debug) {
- assert options.debug == debug;
- allocateRegisters();
- }
-
public void allocateRegisters() {
computeNeedsRegister();
ImmutableList<BasicBlock> blocks = computeLivenessInformation();
performLinearScan();
+ // Even if the method is reachability sensitive, we do not compute debug information after
+ // register allocation. We just treat the method as being in debug mode in order to keep
+ // locals alive for their entire live range. In release mode the liveness is all that matters
+ // and we do not actually want locals information in the output.
if (options.debug) {
LinearScanRegisterAllocator.computeDebugInfo(blocks, liveIntervals, this, liveAtEntrySets);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index ec9b186..0474696 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -847,6 +847,11 @@
}
@Override
+ public boolean isReachabilitySensitive() {
+ return false;
+ }
+
+ @Override
public boolean returnsArgument() {
return false;
}
@@ -956,6 +961,7 @@
// TODO(b/71500340): We call this *hint* because it does not 100% guarantee that a parameter is
// not null when the method returns normally. Maybe nonNullParamOnNormalExit in the future.
private BitSet nonNullParamHints = null;
+ private boolean reachabilitySensitive = false;
private OptimizationInfoImpl() {
// Intentionally left empty, just use the default values.
@@ -977,6 +983,7 @@
initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
parametersUsages = template.parametersUsages;
nonNullParamHints = template.nonNullParamHints;
+ reachabilitySensitive = template.reachabilitySensitive;
}
@Override
@@ -995,6 +1002,11 @@
}
@Override
+ public boolean isReachabilitySensitive() {
+ return reachabilitySensitive;
+ }
+
+ @Override
public boolean returnsArgument() {
return returnedArgument != -1;
}
@@ -1077,6 +1089,11 @@
}
@Override
+ public void setReachabilitySensitive(boolean reachabilitySensitive) {
+ this.reachabilitySensitive = reachabilitySensitive;
+ }
+
+ @Override
public void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
this.classInlinerEligibility = eligibility;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 6503f11..0450937 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -272,6 +272,8 @@
createType("Ldalvik/annotation/codegen/CovariantReturnType;");
public final DexType annotationCovariantReturnTypes =
createType("Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;");
+ public final DexType annotationReachabilitySensitive =
+ createType("Ldalvik/annotation/optimization/ReachabilitySensitive;");
private static final String METAFACTORY_METHOD_NAME = "metafactory";
private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 1516398..a895aa7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -382,4 +382,44 @@
assert classFileVersion != -1;
return classFileVersion;
}
+
+ /**
+ * Is the class reachability sensitive.
+ *
+ * <p>A class is reachability sensitive if the
+ * dalvik.annotation.optimization.ReachabilitySensitive annotation is on any field or method. When
+ * that is the case, dead reference elimination is disabled and locals are kept alive for their
+ * entire scope.
+ */
+ public boolean hasReachabilitySensitiveAnnotation(DexItemFactory factory) {
+ for (DexEncodedMethod directMethod : directMethods) {
+ for (DexAnnotation annotation : directMethod.annotations.annotations) {
+ if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+ return true;
+ }
+ }
+ }
+ for (DexEncodedMethod virtualMethod : virtualMethods) {
+ for (DexAnnotation annotation : virtualMethod.annotations.annotations) {
+ if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+ return true;
+ }
+ }
+ }
+ for (DexEncodedField staticField : staticFields) {
+ for (DexAnnotation annotation : staticField.annotations.annotations) {
+ if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+ return true;
+ }
+ }
+ }
+ for (DexEncodedField instanceField : instanceFields) {
+ for (DexAnnotation annotation : instanceField.annotations.annotations) {
+ if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 99e544a..c2f4943 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -122,11 +122,10 @@
Origin origin) {
assert getOwner() == encodedMethod;
triggerDelayedParsingIfNeccessary();
- return options.debug
+ return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
? internalBuildWithLocals(
encodedMethod, encodedMethod, appInfo, graphLense, options, null, null)
- : internalBuild(
- encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
+ : internalBuild(encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
}
@Override
@@ -142,7 +141,7 @@
assert getOwner() == encodedMethod;
assert generator != null;
triggerDelayedParsingIfNeccessary();
- return options.debug
+ return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
? internalBuildWithLocals(
context, encodedMethod, appInfo, graphLense, options, generator, callerPosition)
: internalBuild(
@@ -176,7 +175,9 @@
InternalOptions options,
ValueNumberGenerator generator,
Position callerPosition) {
- if (!options.debug || !options.proguardConfiguration.getKeepAttributes().localVariableTable) {
+ if (!(encodedMethod.getOptimizationInfo().isReachabilitySensitive()
+ || (options.debug
+ && options.proguardConfiguration.getKeepAttributes().localVariableTable))) {
node.localVariables.clear();
}
diff --git a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
index c693ea4..4fc81d0 100644
--- a/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/OptimizationInfo.java
@@ -21,6 +21,8 @@
BitSet getNonNullParamHints();
+ boolean isReachabilitySensitive();
+
boolean returnsArgument();
int getReturnedArgument();
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
index d7ae7ae..cb93ee0 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
@@ -31,6 +31,8 @@
void setNonNullParamHints(BitSet hints);
+ void setReachabilitySensitive(boolean reachabilitySensitive);
+
void markUseIdentifierNameString();
void markForceInline();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index e348d10..e11f1bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -635,7 +635,9 @@
private boolean consistentBlockInstructions() {
boolean argumentsAllowed = true;
for (BasicBlock block : blocks) {
- block.consistentBlockInstructions(argumentsAllowed, options.debug);
+ block.consistentBlockInstructions(
+ argumentsAllowed,
+ options.debug || method.getOptimizationInfo().isReachabilitySensitive());
argumentsAllowed = false;
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 190ab3a..7cdcd76 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -63,7 +63,8 @@
@Override
public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
- return !code.options.debug && code.options.isGeneratingDex();
+ return !(code.options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+ && code.options.isGeneratingDex();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index eda63f0..953a7f2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -458,7 +458,7 @@
}
public boolean isDebugMode() {
- return options.debug;
+ return options.debug || method.getOptimizationInfo().isReachabilitySensitive();
}
public Int2ReferenceSortedMap<BlockInfo> getCFG() {
@@ -630,7 +630,7 @@
private boolean insertDebugPositions() {
boolean hasDebugPositions = false;
- if (!options.debug) {
+ if (!isDebugMode()) {
return hasDebugPositions;
}
for (BasicBlock block : blocks) {
@@ -847,7 +847,7 @@
public void addDebugLocalStart(int register, DebugLocalInfo local) {
assert local != null;
- if (!options.debug) {
+ if (!isDebugMode()) {
return;
}
// If the local was not introduced by the previous instruction, start it here.
@@ -867,7 +867,7 @@
public void addDebugLocalEnd(int register, DebugLocalInfo local) {
assert local != null;
- if (!options.debug) {
+ if (!isDebugMode()) {
return;
}
Value value = readRegisterForDebugLocal(register, local);
@@ -877,7 +877,7 @@
}
public void addDebugPosition(Position position) {
- if (options.debug) {
+ if (isDebugMode()) {
assert previousLocalValue == null;
assert source.getCurrentPosition().equals(position);
if (!debugLocalEnds.isEmpty()) {
@@ -1096,7 +1096,7 @@
public void addMove(ValueTypeConstraint constraint, int dest, int src) {
Value in = readRegister(src, constraint);
- if (options.debug) {
+ if (isDebugMode()) {
// If the move is writing to a different local we must construct a new value.
DebugLocalInfo destLocal = getOutgoingLocal(dest);
if (destLocal != null && destLocal != in.getLocalInfo()) {
@@ -1876,7 +1876,7 @@
}
private Value readRegisterForDebugLocal(int register, DebugLocalInfo local) {
- assert options.debug;
+ assert isDebugMode();
ValueTypeConstraint type = ValueTypeConstraint.fromDexType(local.type);
return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, RegisterReadType.DEBUG);
}
@@ -1959,7 +1959,7 @@
}
private DebugLocalInfo getIncomingLocalAtBlock(int register, BasicBlock block) {
- if (options.debug) {
+ if (isDebugMode()) {
int blockOffset = offsets.getInt(block);
return source.getIncomingLocalAtBlock(register, blockOffset);
}
@@ -2055,11 +2055,11 @@
}
private DebugLocalInfo getIncomingLocal(int register) {
- return options.debug ? source.getIncomingLocal(register) : null;
+ return isDebugMode() ? source.getIncomingLocal(register) : null;
}
private DebugLocalInfo getOutgoingLocal(int register) {
- return options.debug ? source.getOutgoingLocal(register) : null;
+ return isDebugMode() ? source.getOutgoingLocal(register) : null;
}
private void checkRegister(int register) {
@@ -2161,7 +2161,7 @@
}
private void attachLocalValues(Instruction ir) {
- if (!options.debug) {
+ if (!isDebugMode()) {
assert previousLocalValue == null;
assert debugLocalEnds.isEmpty();
return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 61c69f8..05fd603 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -438,18 +438,22 @@
}
private void convertMethodsToDex(DexProgramClass clazz) {
+ boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
// When converting all methods on a class always convert <clinit> first.
for (DexEncodedMethod method : clazz.directMethods()) {
if (method.isClassInitializer()) {
+ method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
convertMethodToDex(method);
break;
}
}
- clazz.forEachMethod(method -> {
- if (!method.isClassInitializer()) {
- convertMethodToDex(method);
- }
- });
+ clazz.forEachMethod(
+ method -> {
+ if (!method.isClassInitializer()) {
+ method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
+ convertMethodToDex(method);
+ }
+ });
}
private void convertMethodToDex(DexEncodedMethod method) {
@@ -478,6 +482,7 @@
public DexApplication optimize(DexApplication application, ExecutorService executorService)
throws ExecutionException {
+ computeReachabilitySensitivity(application);
removeLambdaDeserializationMethods();
collectLambdaMergingCandidates(application);
collectStaticizerCandidates(application);
@@ -566,6 +571,14 @@
return builder.build();
}
+ private void computeReachabilitySensitivity(DexApplication application) {
+ application.classes().forEach(c -> {
+ if (c.hasReachabilitySensitiveAnnotation(options.itemFactory)) {
+ c.methods().forEach(m -> m.getMutableOptimizationInfo().setReachabilitySensitive(true));
+ }
+ });
+ }
+
private void forEachSelectedOutliningMethod(
ExecutorService executorService, BiConsumer<IRCode, DexEncodedMethod> consumer)
throws ExecutionException {
@@ -812,7 +825,9 @@
CodeRewriter.ensureDirectStringNewToInit(code);
}
- if (options.debug) {
+ boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+
+ if (isDebugMode) {
codeRewriter.simplifyDebugLocals(code);
}
@@ -867,9 +882,8 @@
nonNullTracker.addNonNull(code);
assert code.isConsistentSSA();
}
- if (options.enableInlining && inliner != null) {
+ if (!isDebugMode && options.enableInlining && inliner != null) {
// TODO(zerny): Should we support inlining in debug mode? b/62937285
- assert !options.debug;
inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
}
@@ -884,7 +898,7 @@
codeRewriter.rewriteGetClass(code);
}
- if (!options.debug) {
+ if (!isDebugMode) {
// TODO(jsjeon): Consider merging these into one single optimize().
stringOptimizer.computeTrivialOperationsOnConstString(code, appInfo.dexItemFactory);
// Reflection optimization 2. get*Name() with const-class -> const-string
@@ -926,7 +940,7 @@
assert code.isConsistentSSA();
}
- if (!options.debug) {
+ if (!isDebugMode) {
codeRewriter.collectClassInitializerDefaults(method, code);
}
if (Log.ENABLED) {
@@ -1175,7 +1189,7 @@
workaroundForwardingInitializerBug(code);
LinearScanRegisterAllocator registerAllocator =
new LinearScanRegisterAllocator(appInfo, code, options);
- registerAllocator.allocateRegisters(options.debug);
+ registerAllocator.allocateRegisters();
if (options.canHaveExceptionTargetingLoopHeaderBug()) {
codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 880a674..c971a14 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -199,7 +199,7 @@
* Perform register allocation for the IRCode.
*/
@Override
- public void allocateRegisters(boolean debug) {
+ public void allocateRegisters() {
// There are no linked values prior to register allocation.
assert noLinkedValues();
assert code.isConsistentSSA();
@@ -216,7 +216,11 @@
Log.debug(this.getClass(), toString());
}
assert registersUsed() == 0 || unusedRegisters != null;
- if (debug) {
+ // Even if the method is reachability sensitive, we do not compute debug information after
+ // register allocation. We just treat the method as being in debug mode in order to keep
+ // locals alive for their entire live range. In release mode the liveness is all that matters
+ // and we do not actually want locals information in the output.
+ if (options.debug) {
computeDebugInfo(blocks);
}
clearUserInfo();
@@ -1612,9 +1616,11 @@
// Set all free positions for possible registers to max integer.
RegisterPositions freePositions = new RegisterPositions(registerConstraint + 1);
- if (options.debug && !code.method.accessFlags.isStatic()) {
- // If we are generating debug information, we pin the this value register since the
- // debugger expects to always be able to find it in the input register.
+ if ((options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+ && !code.method.accessFlags.isStatic()) {
+ // If we are generating debug information or if the method is reachability sensitive,
+ // we pin the this value register. The debugger expects to always be able to find it in
+ // the input register.
assert numberOfArgumentRegisters > 0;
assert firstArgumentValue != null && firstArgumentValue.requiredRegisters() == 1;
freePositions.set(0, 0);
@@ -2620,7 +2626,9 @@
}
}
}
- if (options.debug) {
+ if (options.debug || code.method.getOptimizationInfo().isReachabilitySensitive()) {
+ // In debug mode, or if the method is reachability sensitive, extend the live range
+ // to cover the full scope of a local variable (encoded as debug values).
int number = instruction.getNumber();
for (Value use : instruction.getDebugValues()) {
assert use.needsRegister();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index ba83787..1314b3f 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -9,7 +9,7 @@
import java.util.List;
public interface RegisterAllocator {
- void allocateRegisters(boolean debug);
+ void allocateRegisters();
int registersUsed();
int getRegisterForValue(Value value, int instructionNumber);
int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 0572c36..55c64ee 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -128,6 +128,7 @@
blocks.add(block);
InternalOptions options = new InternalOptions();
+ options.debug = true;
AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
IRCode code =
new IRCode(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 49a9457..eedfc2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -69,9 +69,11 @@
// Check that the goto in block0 remains. There was a bug in the trivial goto elimination
// that ended up removing that goto changing the code to start with the unreachable
// throw.
+ InternalOptions options = new InternalOptions();
+ options.debug = true;
IRCode code =
new IRCode(
- new InternalOptions(),
+ options,
null,
blocks,
new ValueNumberGenerator(),
@@ -150,9 +152,11 @@
// Check that the goto in block0 remains. There was a bug in the trivial goto elimination
// that ended up removing that goto changing the code to start with the unreachable
// throw.
+ InternalOptions options = new InternalOptions();
+ options.debug = true;
IRCode code =
new IRCode(
- new InternalOptions(),
+ options,
null,
blocks,
new ValueNumberGenerator(),
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 7e4b76a..5ed3cc5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -21,7 +21,7 @@
private static class MockRegisterAllocator implements RegisterAllocator {
@Override
- public void allocateRegisters(boolean debug) {
+ public void allocateRegisters() {
}
@Override
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
new file mode 100644
index 0000000..edd6fd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -0,0 +1,181 @@
+// Copyright (c) 2018 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.reachabilitysensitive;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.AddIntLit8;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import dalvik.annotation.optimization.ReachabilitySensitive;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+class TestClass {
+ public void method() {
+ int i = 2;
+ int j = i + 1;
+ int k = j + 2;
+ System.out.println(k);
+ }
+}
+
+class TestClassWithAnnotatedField {
+ @ReachabilitySensitive private final long field = 0;
+
+ public void method() {
+ int i = 2;
+ int j = i + 1;
+ int k = j + 2;
+ System.out.println(k);
+ }
+}
+
+class TestClassWithAnnotatedMethod {
+
+ @ReachabilitySensitive
+ public void unrelatedAnnotatedMethod() {}
+
+ public void method() {
+ int i = 2;
+ int j = i + 1;
+ int k = j + 2;
+ System.out.println(k);
+ }
+}
+
+@RunWith(Parameterized.class)
+public class ReachabilitySensitiveTest extends TestBase {
+
+ private final Tool tool;
+
+ @Parameters(name = "{0}")
+ public static List<Object> data() {
+ return ImmutableList.of(Tool.D8, Tool. R8);
+ }
+
+ public ReachabilitySensitiveTest(Tool tool) {
+ this.tool = tool;
+ }
+
+ @Test
+ public void testNoAnnotation()
+ throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+ CodeInspector inspector = tool == Tool.R8
+ ? compileR8(TestClass.class)
+ : compile(TestClass.class);
+ DexCode code =
+ inspector.method(TestClass.class.getMethod("method")).getMethod().getCode().asDexCode();
+ // Computation of k is constant folded and the value takes up one register. System.out takes
+ // up another register and the receiver is the last.
+ assertEquals(3, code.registerSize);
+ checkNoLocals(code);
+ }
+
+ @Test
+ public void testFieldAnnotation()
+ throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+ CodeInspector inspector = tool == Tool.R8
+ ? compileR8(TestClassWithAnnotatedField.class)
+ : compile(TestClassWithAnnotatedField.class);
+ checkAnnotatedCode(
+ inspector
+ .method(TestClassWithAnnotatedField.class.getMethod("method"))
+ .getMethod()
+ .getCode()
+ .asDexCode());
+ }
+
+ @Test
+ public void testMethodAnnotation()
+ throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+ CodeInspector inspector = tool == Tool.R8
+ ? compileR8(TestClassWithAnnotatedMethod.class)
+ : compile(TestClassWithAnnotatedMethod.class);
+ checkAnnotatedCode(
+ inspector
+ .method(TestClassWithAnnotatedMethod.class.getMethod("method"))
+ .getMethod()
+ .getCode()
+ .asDexCode());
+ }
+
+ private void checkNoLocals(DexCode code) {
+ // Even if we preserve live range of locals, we do not output locals information
+ // as this is a release build.
+ assertTrue(Arrays.stream(code.getDebugInfo().events)
+ .allMatch(event -> !(event instanceof StartLocal)));
+ }
+
+ private void checkAnnotatedCode(DexCode code) {
+ // All live at the same time: receiver, i, j, k, System.out.
+ assertEquals(5, code.registerSize);
+ Instruction first = code.instructions[0];
+ Instruction second = code.instructions[1];
+ Instruction third = code.instructions[2];
+ // None of the local declarations overwrite other locals.
+ assertTrue(first instanceof Const4);
+ assertTrue(second instanceof AddIntLit8);
+ assertTrue(third instanceof AddIntLit8);
+ int firstRegister = ((Const4) first).A;
+ int secondRegister = ((AddIntLit8) second).AA;
+ int thirdRegister = ((AddIntLit8) third).AA;
+ assertFalse(firstRegister == secondRegister);
+ assertFalse(firstRegister == thirdRegister);
+ assertFalse(secondRegister == thirdRegister);
+ checkNoLocals(code);
+ }
+
+ private CodeInspector compile(Class... classes)
+ throws CompilationFailedException, IOException, ExecutionException {
+ return testForD8()
+ .addProgramClasses(classes)
+ .setMode(CompilationMode.RELEASE)
+ .compile()
+ .inspector();
+ }
+
+ private CodeInspector compileR8(Class... classes)
+ throws CompilationFailedException, IOException, ExecutionException {
+ List<String> keepRules =
+ Arrays.stream(classes)
+ .map(c -> "-keep class " + c.getCanonicalName() + " { <methods>; }")
+ .collect(Collectors.toList());
+ return testForR8(Backend.DEX)
+ .addProgramClasses(classes)
+ // TODO(ager): This will be in android.jar over time. For now, make it part of the app.
+ .addProgramClasses(ReachabilitySensitive.class)
+ .setMode(CompilationMode.RELEASE)
+ // Keep the input class and its methods.
+ .addKeepRules(keepRules)
+ // Keep the annotation class.
+ .addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
+ // Keep the annotation and debug information which is needed for debug mode to actually
+ // keep things alive.
+ .addKeepRules("-keepattributes *Annotations*,LineNumberTable," +
+ "LocalVariableTable,LocalVariableTypeTable")
+ .compile()
+ .inspector();
+ }
+}
diff --git a/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java b/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java
new file mode 100644
index 0000000..04fc705
--- /dev/null
+++ b/src/test/java/dalvik/annotation/optimization/ReachabilitySensitive.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2018 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 dalvik.annotation.optimization;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// This is a copy of libcore/dalvik/annotation/optimiztion/ReachabilitySensitive.java.
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface ReachabilitySensitive {}