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 {}