Merge "Mark more lambdas as instantiated"
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 {}