Selectively allow dead reference elimination

Allow dead reference elimination in methods not containing
@ReachabilitySensitive accesses or calls, when the class is marked
@DeadReferenceSafe.

Add 1339-dead-reference-safe to aggressively check that everything
works as intended.

Bug: 111453875

Test: art/test/testrunner/testrunner.py --host --64 -t 1339-dead-reference-safe

Detect ReachabilitySensitive annotations.

Change-Id: I70c20431fdbcfcfd2692b2255d12ad59e37cb669
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 8440e9a..96d6d2a 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -1789,6 +1789,14 @@
     invoke_type = kVirtual;
   }
 
+  bool caller_dead_reference_safe = graph_->IsDeadReferenceSafe();
+  const dex::ClassDef& callee_class = resolved_method->GetClassDef();
+  // MethodContainsRSensitiveAccess is currently slow, but HasDeadReferenceSafeAnnotation()
+  // is currently rarely true.
+  bool callee_dead_reference_safe =
+      annotations::HasDeadReferenceSafeAnnotation(callee_dex_file, callee_class)
+      && !annotations::MethodContainsRSensitiveAccess(callee_dex_file, callee_class, method_index);
+
   const int32_t caller_instruction_counter = graph_->GetCurrentInstructionId();
   HGraph* callee_graph = new (graph_->GetAllocator()) HGraph(
       graph_->GetAllocator(),
@@ -1797,6 +1805,7 @@
       method_index,
       codegen_->GetCompilerOptions().GetInstructionSet(),
       invoke_type,
+      callee_dead_reference_safe,
       graph_->IsDebuggable(),
       /* osr= */ false,
       caller_instruction_counter);
@@ -2023,6 +2032,13 @@
     inline_stats_->AddTo(stats_);
   }
 
+  if (caller_dead_reference_safe && !callee_dead_reference_safe) {
+    // Caller was dead reference safe, but is not anymore, since we inlined dead
+    // reference unsafe code. Prior transformations remain valid, since they did not
+    // affect the inlined code.
+    graph_->MarkDeadReferenceUnsafe();
+  }
+
   return true;
 }
 
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 48fb611..c70674b 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -317,6 +317,7 @@
          uint32_t method_idx,
          InstructionSet instruction_set,
          InvokeType invoke_type = kInvalidInvokeType,
+         bool dead_reference_safe = false,
          bool debuggable = false,
          bool osr = false,
          int start_instruction_id = 0)
@@ -336,6 +337,7 @@
         has_simd_(false),
         has_loops_(false),
         has_irreducible_loops_(false),
+        dead_reference_safe_(dead_reference_safe),
         debuggable_(debuggable),
         current_instruction_id_(start_instruction_id),
         dex_file_(dex_file),
@@ -526,6 +528,12 @@
     has_bounds_checks_ = value;
   }
 
+  // Is the code known to be robust against eliminating dead references
+  // and the effects of early finalization?
+  bool IsDeadReferenceSafe() const { return dead_reference_safe_; }
+
+  void MarkDeadReferenceUnsafe() { dead_reference_safe_ = false; }
+
   bool IsDebuggable() const { return debuggable_; }
 
   // Returns a constant of the given type and value. If it does not exist
@@ -704,6 +712,14 @@
   // so there might be false positives.
   bool has_irreducible_loops_;
 
+  // Is the code known to be robust against eliminating dead references
+  // and the effects of early finalization? If false, dead reference variables
+  // are kept if they might be visible to the garbage collector.
+  // Currently this means that the class was declared to be dead-reference-safe,
+  // the method accesses no reachability-sensitive fields or data, and the same
+  // is true for any methods that were inlined into the current one.
+  bool dead_reference_safe_;
+
   // Indicates whether the graph should be compiled in a way that
   // ensures full debuggability. If false, we can apply more
   // aggressive optimizations that may limit the level of debugging.
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 42dbc77..e8f8d32 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -828,6 +828,29 @@
   }
 
   CodeItemDebugInfoAccessor code_item_accessor(dex_file, code_item, method_idx);
+
+  bool dead_reference_safe;
+  ArrayRef<const uint8_t> interpreter_metadata;
+  // For AOT compilation, we may not get a method, for example if its class is erroneous,
+  // possibly due to an unavailable superclass.  JIT should always have a method.
+  DCHECK(Runtime::Current()->IsAotCompiler() || method != nullptr);
+  if (method != nullptr) {
+    const dex::ClassDef* containing_class;
+    {
+      ScopedObjectAccess soa(Thread::Current());
+      containing_class = &method->GetClassDef();
+      interpreter_metadata = method->GetQuickenedInfo();
+    }
+    // MethodContainsRSensitiveAccess is currently slow, but HasDeadReferenceSafeAnnotation()
+    // is currently rarely true.
+    dead_reference_safe =
+        annotations::HasDeadReferenceSafeAnnotation(dex_file, *containing_class)
+        && !annotations::MethodContainsRSensitiveAccess(dex_file, *containing_class, method_idx);
+  } else {
+    // If we could not resolve the class, conservatively assume it's dead-reference unsafe.
+    dead_reference_safe = false;
+  }
+
   HGraph* graph = new (allocator) HGraph(
       allocator,
       arena_stack,
@@ -835,17 +858,12 @@
       method_idx,
       compiler_options.GetInstructionSet(),
       kInvalidInvokeType,
+      dead_reference_safe,
       compiler_driver->GetCompilerOptions().GetDebuggable(),
-      osr);
+      /* osr= */ osr);
 
-  ArrayRef<const uint8_t> interpreter_metadata;
-  // For AOT compilation, we may not get a method, for example if its class is erroneous.
-  // JIT should always have a method.
-  DCHECK(Runtime::Current()->IsAotCompiler() || method != nullptr);
   if (method != nullptr) {
     graph->SetArtMethod(method);
-    ScopedObjectAccess soa(Thread::Current());
-    interpreter_metadata = method->GetQuickenedInfo();
   }
 
   std::unique_ptr<CodeGenerator> codegen(
@@ -963,6 +981,7 @@
       method_idx,
       compiler_options.GetInstructionSet(),
       kInvalidInvokeType,
+      /* dead_reference_safe= */ true,  // Intrinsics don't affect dead reference safety.
       compiler_options.GetDebuggable(),
       /* osr= */ false);
 
diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h
index 92d0b08..c883907 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -1155,10 +1155,11 @@
  *
  * (a) Non-environment uses of an instruction always make
  *     the instruction live.
- * (b) Environment uses of an instruction whose type is
- *     object (that is, non-primitive), make the instruction live.
- *     This is due to having to keep alive objects that have
- *     finalizers deleting native objects.
+ * (b) Environment uses of an instruction whose type is object (that is, non-primitive), make the
+ *     instruction live, unless the class has an @DeadReferenceSafe annotation.
+ *     This avoids unexpected premature reference enqueuing or finalization, which could
+ *     result in premature deletion of native objects.  In the presence of @DeadReferenceSafe,
+ *     object references are treated like primitive types.
  * (c) When the graph has the debuggable property, environment uses
  *     of an instruction that has a primitive type make the instruction live.
  *     If the graph does not have the debuggable property, the environment
@@ -1287,6 +1288,7 @@
     // When compiling in OSR mode, all loops in the compiled method may be entered
     // from the interpreter via SuspendCheck; thus we need to preserve the environment.
     if (env_holder->IsSuspendCheck() && graph->IsCompilingOsr()) return true;
+    if (graph -> IsDeadReferenceSafe()) return false;
     return instruction->GetType() == DataType::Type::kReference;
   }
 
diff --git a/libdexfile/dex/dex_instruction.cc b/libdexfile/dex/dex_instruction.cc
index 83663c5..f36a2aa 100644
--- a/libdexfile/dex/dex_instruction.cc
+++ b/libdexfile/dex/dex_instruction.cc
@@ -402,9 +402,9 @@
         case INVOKE_VIRTUAL_QUICK:
           if (file != nullptr) {
             os << opcode << " {";
-            uint32_t method_idx = VRegB_35c();
+            uint32_t vtable_offset = VRegB_35c();
             DumpArgs(VRegA_35c());
-            os << "},  // vtable@" << method_idx;
+            os << "},  // vtable@" << vtable_offset;
             break;
           }
           FALLTHROUGH_INTENDED;
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 07193b2..44b80df 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -547,11 +547,10 @@
 ArrayRef<const uint8_t> ArtMethod::GetQuickenedInfo() {
   const DexFile& dex_file = *GetDexFile();
   const OatDexFile* oat_dex_file = dex_file.GetOatDexFile();
-  if (oat_dex_file == nullptr || (oat_dex_file->GetOatFile() == nullptr)) {
+  if (oat_dex_file == nullptr) {
     return ArrayRef<const uint8_t>();
   }
-  return oat_dex_file->GetOatFile()->GetVdexFile()->GetQuickenedInfoOf(dex_file,
-                                                                       GetDexMethodIndex());
+  return oat_dex_file->GetQuickenedInfoOf(dex_file, GetDexMethodIndex());
 }
 
 uint16_t ArtMethod::GetIndexFromQuickening(uint32_t dex_pc) {
diff --git a/runtime/dex/dex_file_annotations.cc b/runtime/dex/dex_file_annotations.cc
index e75baf8..050be4a 100644
--- a/runtime/dex/dex_file_annotations.cc
+++ b/runtime/dex/dex_file_annotations.cc
@@ -26,6 +26,7 @@
 #include "class_linker-inl.h"
 #include "class_root.h"
 #include "dex/dex_file-inl.h"
+#include "dex/dex_instruction-inl.h"
 #include "jni/jni_internal.h"
 #include "jvalue-inl.h"
 #include "mirror/array-alloc-inl.h"
@@ -36,6 +37,7 @@
 #include "mirror/object_array-inl.h"
 #include "oat_file.h"
 #include "obj_ptr-inl.h"
+#include "quicken_info.h"
 #include "reflection.h"
 #include "thread.h"
 #include "well_known_classes.h"
@@ -146,32 +148,36 @@
   return actual == expected;
 }
 
-const AnnotationSetItem* FindAnnotationSetForField(ArtField* field)
+static const AnnotationSetItem* FindAnnotationSetForField(const DexFile& dex_file,
+                                                          const dex::ClassDef& class_def,
+                                                          uint32_t field_index)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  const DexFile* dex_file = field->GetDexFile();
+  const AnnotationsDirectoryItem* annotations_dir = dex_file.GetAnnotationsDirectory(class_def);
+  if (annotations_dir == nullptr) {
+    return nullptr;
+  }
+  const FieldAnnotationsItem* field_annotations = dex_file.GetFieldAnnotations(annotations_dir);
+  if (field_annotations == nullptr) {
+    return nullptr;
+  }
+  uint32_t field_count = annotations_dir->fields_size_;
+  for (uint32_t i = 0; i < field_count; ++i) {
+    if (field_annotations[i].field_idx_ == field_index) {
+      return dex_file.GetFieldAnnotationSetItem(field_annotations[i]);
+    }
+  }
+  return nullptr;
+}
+
+static const AnnotationSetItem* FindAnnotationSetForField(ArtField* field)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
   ObjPtr<mirror::Class> klass = field->GetDeclaringClass();
   const dex::ClassDef* class_def = klass->GetClassDef();
   if (class_def == nullptr) {
     DCHECK(klass->IsProxyClass());
     return nullptr;
   }
-  const AnnotationsDirectoryItem* annotations_dir = dex_file->GetAnnotationsDirectory(*class_def);
-  if (annotations_dir == nullptr) {
-    return nullptr;
-  }
-  const FieldAnnotationsItem* field_annotations =
-      dex_file->GetFieldAnnotations(annotations_dir);
-  if (field_annotations == nullptr) {
-    return nullptr;
-  }
-  uint32_t field_index = field->GetDexFieldIndex();
-  uint32_t field_count = annotations_dir->fields_size_;
-  for (uint32_t i = 0; i < field_count; ++i) {
-    if (field_annotations[i].field_idx_ == field_index) {
-      return dex_file->GetFieldAnnotationSetItem(field_annotations[i]);
-    }
-  }
-  return nullptr;
+  return FindAnnotationSetForField(*field->GetDexFile(), *class_def, field->GetDexFieldIndex());
 }
 
 const AnnotationItem* SearchAnnotationSet(const DexFile& dex_file,
@@ -276,9 +282,9 @@
   return nullptr;
 }
 
-const AnnotationSetItem* FindAnnotationSetForMethod(const DexFile& dex_file,
-                                                    const dex::ClassDef& class_def,
-                                                    uint32_t method_index) {
+static const AnnotationSetItem* FindAnnotationSetForMethod(const DexFile& dex_file,
+                                                           const dex::ClassDef& class_def,
+                                                           uint32_t method_index) {
   const AnnotationsDirectoryItem* annotations_dir = dex_file.GetAnnotationsDirectory(class_def);
   if (annotations_dir == nullptr) {
     return nullptr;
@@ -329,7 +335,7 @@
   return nullptr;
 }
 
-const AnnotationSetItem* FindAnnotationSetForClass(const ClassData& klass)
+static const AnnotationSetItem* FindAnnotationSetForClass(const ClassData& klass)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   const DexFile& dex_file = klass.GetDexFile();
   const dex::ClassDef* class_def = klass.GetClassDef();
@@ -1310,6 +1316,191 @@
   return access_flags;
 }
 
+bool FieldIsReachabilitySensitive(const DexFile& dex_file,
+                                  const dex::ClassDef& class_def,
+                                  uint32_t field_index)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const AnnotationSetItem* annotation_set =
+      FindAnnotationSetForField(dex_file, class_def, field_index);
+  if (annotation_set == nullptr) {
+    return false;
+  }
+  const AnnotationItem* annotation_item = SearchAnnotationSet(dex_file, annotation_set,
+      "Ldalvik/annotation/optimization/ReachabilitySensitive;", DexFile::kDexVisibilityRuntime);
+  // TODO: We're missing the equivalent of DCheckNativeAnnotation (not a DCHECK). Does it matter?
+  return annotation_item != nullptr;
+}
+
+bool MethodIsReachabilitySensitive(const DexFile& dex_file,
+                                   const dex::ClassDef& class_def,
+                                   uint32_t method_index)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const AnnotationSetItem* annotation_set =
+      FindAnnotationSetForMethod(dex_file, class_def, method_index);
+  if (annotation_set == nullptr) {
+    return false;
+  }
+  const AnnotationItem* annotation_item = SearchAnnotationSet(dex_file, annotation_set,
+      "Ldalvik/annotation/optimization/ReachabilitySensitive;", DexFile::kDexVisibilityRuntime);
+  return annotation_item != nullptr;
+}
+
+static bool MethodIsReachabilitySensitive(const DexFile& dex_file,
+                                               uint32_t method_index)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  DCHECK(method_index < dex_file.NumMethodIds());
+  const dex::MethodId& method_id = dex_file.GetMethodId(method_index);
+  dex::TypeIndex class_index = method_id.class_idx_;
+  const dex::ClassDef * class_def = dex_file.FindClassDef(class_index);
+  return class_def != nullptr
+         && MethodIsReachabilitySensitive(dex_file, *class_def, method_index);
+}
+
+bool MethodContainsRSensitiveAccess(const DexFile& dex_file,
+                                    const dex::ClassDef& class_def,
+                                    uint32_t method_index)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // TODO: This is too slow to run very regularly. Currently this is only invoked in the
+  // presence of @DeadReferenceSafe, which will be rare. In the long run, we need to quickly
+  // check once whether a class has any @ReachabilitySensitive annotations. If not, we can
+  // immediately return false here for any method in that class.
+  uint32_t code_item_offset = dex_file.FindCodeItemOffset(class_def, method_index);
+  const dex::CodeItem* code_item = dex_file.GetCodeItem(code_item_offset);
+  CodeItemInstructionAccessor accessor(dex_file, code_item);
+  if (!accessor.HasCodeItem()) {
+    return false;
+  }
+  ArrayRef<const uint8_t> quicken_data;
+  const OatDexFile* oat_dex_file = dex_file.GetOatDexFile();
+  if (oat_dex_file != nullptr) {
+    quicken_data = oat_dex_file->GetQuickenedInfoOf(dex_file, method_index);
+  }
+  const QuickenInfoTable quicken_info(quicken_data);
+  uint32_t quicken_index = 0;
+  for (DexInstructionIterator iter = accessor.begin(); iter != accessor.end(); ++iter) {
+    switch (iter->Opcode()) {
+      case Instruction::IGET:
+      case Instruction::IGET_QUICK:
+      case Instruction::IGET_WIDE:
+      case Instruction::IGET_WIDE_QUICK:
+      case Instruction::IGET_OBJECT:
+      case Instruction::IGET_OBJECT_QUICK:
+      case Instruction::IGET_BOOLEAN:
+      case Instruction::IGET_BOOLEAN_QUICK:
+      case Instruction::IGET_BYTE:
+      case Instruction::IGET_BYTE_QUICK:
+      case Instruction::IGET_CHAR:
+      case Instruction::IGET_CHAR_QUICK:
+      case Instruction::IGET_SHORT:
+      case Instruction::IGET_SHORT_QUICK:
+      case Instruction::IPUT:
+      case Instruction::IPUT_QUICK:
+      case Instruction::IPUT_WIDE:
+      case Instruction::IPUT_WIDE_QUICK:
+      case Instruction::IPUT_OBJECT:
+      case Instruction::IPUT_OBJECT_QUICK:
+      case Instruction::IPUT_BOOLEAN:
+      case Instruction::IPUT_BOOLEAN_QUICK:
+      case Instruction::IPUT_BYTE:
+      case Instruction::IPUT_BYTE_QUICK:
+      case Instruction::IPUT_CHAR:
+      case Instruction::IPUT_CHAR_QUICK:
+      case Instruction::IPUT_SHORT:
+      case Instruction::IPUT_SHORT_QUICK:
+        {
+          uint32_t field_index;
+          if (iter->IsQuickened()) {
+            field_index = quicken_info.GetData(quicken_index);
+          } else {
+            field_index = iter->VRegC_22c();
+          }
+          DCHECK(field_index < dex_file.NumFieldIds());
+          // We only guarantee to pay attention to the annotation if it's in the same class,
+          // or a containing class, but it's OK to do so in other cases.
+          const dex::FieldId& field_id = dex_file.GetFieldId(field_index);
+          dex::TypeIndex class_index = field_id.class_idx_;
+          const dex::ClassDef * field_class_def = dex_file.FindClassDef(class_index);
+          // We do not handle the case in which the field is declared in a superclass, and
+          // don't claim to do so. The annotated field should normally be private.
+          if (field_class_def != nullptr
+              && FieldIsReachabilitySensitive(dex_file, *field_class_def, field_index)) {
+            return true;
+          }
+        }
+        break;
+      case Instruction::INVOKE_SUPER:
+        // Cannot call method in same class. TODO: Try an explicit superclass lookup for
+        // better "best effort"?
+        break;
+      case Instruction::INVOKE_INTERFACE:
+        // We handle an interface call just like a virtual call. We will find annotations
+        // on interface methods/fields visible to us, but not of the annotation is in a
+        // super-interface. Again, we could just ignore it.
+      case Instruction::INVOKE_VIRTUAL:
+      case Instruction::INVOKE_DIRECT:
+        {
+          uint32_t called_method_index = iter->VRegB_35c();
+          if (MethodIsReachabilitySensitive(dex_file, called_method_index)) {
+            return true;
+          }
+        }
+        break;
+      case Instruction::INVOKE_INTERFACE_RANGE:
+      case Instruction::INVOKE_VIRTUAL_RANGE:
+      case Instruction::INVOKE_DIRECT_RANGE:
+        {
+          uint32_t called_method_index = iter->VRegB_3rc();
+          if (MethodIsReachabilitySensitive(dex_file, called_method_index)) {
+            return true;
+          }
+        }
+        break;
+      case Instruction::INVOKE_VIRTUAL_QUICK:
+      case Instruction::INVOKE_VIRTUAL_RANGE_QUICK:
+        {
+          uint32_t called_method_index = quicken_info.GetData(quicken_index);
+          if (MethodIsReachabilitySensitive(dex_file, called_method_index)) {
+            return true;
+          }
+        }
+        break;
+        // We explicitly do not handle indirect ReachabilitySensitive accesses through VarHandles,
+        // etc. Thus we ignore INVOKE_CUSTOM / INVOKE_CUSTOM_RANGE / INVOKE_POLYMORPHIC /
+        // INVOKE_POLYMORPHIC_RANGE.
+      default:
+        // There is no way to add an annotation to array elements, and so far we've encountered no
+        // need for that, so we ignore AGET and APUT.
+        // It's impractical or impossible to garbage collect a class while one of its methods is
+        // on the call stack. We allow ReachabilitySensitive annotations on static methods and
+        // fields, but they can be safely ignored.
+        break;
+    }
+    if (QuickenInfoTable::NeedsIndexForInstruction(&iter.Inst())) {
+      ++quicken_index;
+    }
+  }
+  return false;
+}
+
+bool HasDeadReferenceSafeAnnotation(const DexFile& dex_file,
+                                    const dex::ClassDef& class_def)
+  // TODO: This should check outer classes as well.
+  // It's conservatively correct not to do so.
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const AnnotationsDirectoryItem* annotations_dir =
+      dex_file.GetAnnotationsDirectory(class_def);
+  if (annotations_dir == nullptr) {
+    return false;
+  }
+  const AnnotationSetItem* annotation_set = dex_file.GetClassAnnotationSet(annotations_dir);
+  if (annotation_set == nullptr) {
+    return false;
+  }
+  const AnnotationItem* annotation_item = SearchAnnotationSet(dex_file, annotation_set,
+      "Ldalvik/annotation/optimization/DeadReferenceSafe;", DexFile::kDexVisibilityRuntime);
+  return annotation_item != nullptr;
+}
+
 ObjPtr<mirror::Object> GetAnnotationForClass(Handle<mirror::Class> klass,
                                              Handle<mirror::Class> annotation_class) {
   ClassData data(klass);
diff --git a/runtime/dex/dex_file_annotations.h b/runtime/dex/dex_file_annotations.h
index 3625cee..018e87f 100644
--- a/runtime/dex/dex_file_annotations.h
+++ b/runtime/dex/dex_file_annotations.h
@@ -78,6 +78,7 @@
                                Handle<mirror::Class> annotation_class,
                                uint32_t visibility = DexFile::kDexVisibilityRuntime)
     REQUIRES_SHARED(Locks::mutator_lock_);
+
 // Check whether a method from the `dex_file` with the given `method_index`
 // is annotated with @dalvik.annotation.optimization.FastNative or
 // @dalvik.annotation.optimization.CriticalNative with build visibility.
@@ -85,6 +86,28 @@
 uint32_t GetNativeMethodAnnotationAccessFlags(const DexFile& dex_file,
                                               const dex::ClassDef& class_def,
                                               uint32_t method_index);
+// Is the field from the `dex_file` with the given `field_index`
+// annotated with @dalvik.annotation.optimization.ReachabilitySensitive?
+bool FieldIsReachabilitySensitive(const DexFile& dex_file,
+                                  const dex::ClassDef& class_def,
+                                  uint32_t field_index);
+// Is the method from the `dex_file` with the given `method_index`
+// annotated with @dalvik.annotation.optimization.ReachabilitySensitive?
+bool MethodIsReachabilitySensitive(const DexFile& dex_file,
+                                   const dex::ClassDef& class_def,
+                                   uint32_t method_index);
+// Does the method from the `dex_file` with the given `method_index` contain an access to a field
+// annotated with @dalvik.annotation.optimization.ReachabilitySensitive, or a call to a method
+// with that annotation?
+// Class_def is the class defining the method. We consider only accessses to classes or methods
+// declared in the static type of the corresponding object. We may overlook accesses to annotated
+// fields or methods that are in neither class_def nor a containing (outer) class.
+bool MethodContainsRSensitiveAccess(const DexFile& dex_file,
+                                    const dex::ClassDef& class_def,
+                                    uint32_t method_index);
+// Is the given class annotated with @dalvik.annotation.optimization.DeadReferenceSafe?
+bool HasDeadReferenceSafeAnnotation(const DexFile& dex_file,
+                                    const dex::ClassDef& class_def);
 
 // Class annotations.
 ObjPtr<mirror::Object> GetAnnotationForClass(Handle<mirror::Class> klass,
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index e433cbc..d179b80 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -1836,6 +1836,16 @@
                            reinterpret_cast<const OatMethodOffsets*>(methods_pointer));
 }
 
+ArrayRef<const uint8_t> OatDexFile::GetQuickenedInfoOf(const DexFile& dex_file,
+                                                       uint32_t dex_method_idx) const {
+  const OatFile* oat_file = GetOatFile();
+  if (oat_file == nullptr) {
+    return ArrayRef<const uint8_t>();
+  } else  {
+    return oat_file->GetVdexFile()->GetQuickenedInfoOf(dex_file, dex_method_idx);
+  }
+}
+
 const dex::ClassDef* OatDexFile::FindClassDef(const DexFile& dex_file,
                                               const char* descriptor,
                                               size_t hash) {
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 3e9c01f..1ba6e49 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -502,6 +502,9 @@
     return dex_file_pointer_;
   }
 
+  ArrayRef<const uint8_t> GetQuickenedInfoOf(const DexFile& dex_file,
+                                             uint32_t dex_method_idx) const;
+
   // Looks up a class definition by its class descriptor. Hash must be
   // ComputeModifiedUtf8Hash(descriptor).
   static const dex::ClassDef* FindClassDef(const DexFile& dex_file,
diff --git a/test/1339-dead-reference-safe/check b/test/1339-dead-reference-safe/check
new file mode 100644
index 0000000..795cfac
--- /dev/null
+++ b/test/1339-dead-reference-safe/check
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# DeadReferenceSafe result differs for interpreted mode. A real failure
+# will produce an extra line anyway.
+
+diff --ignore-matching-lines="DeadReferenceSafe count:" -q $1 $2
diff --git a/test/1339-dead-reference-safe/expected.txt b/test/1339-dead-reference-safe/expected.txt
new file mode 100644
index 0000000..abafce4
--- /dev/null
+++ b/test/1339-dead-reference-safe/expected.txt
@@ -0,0 +1,6 @@
+JNI_OnLoad called
+DeadReferenceUnsafe count: 5
+DeadReferenceSafe count: N
+ReachabilitySensitive count: 5
+ReachabilitySensitiveFun count: 5
+ReachabilityFence count: 5
diff --git a/test/1339-dead-reference-safe/info.txt b/test/1339-dead-reference-safe/info.txt
new file mode 100644
index 0000000..b6ad217
--- /dev/null
+++ b/test/1339-dead-reference-safe/info.txt
@@ -0,0 +1 @@
+Test that @DeadReferenceSafe and @ReachabilitySensitive have the intended effect.
diff --git a/test/1339-dead-reference-safe/src/DeadReferenceSafeTest.java b/test/1339-dead-reference-safe/src/DeadReferenceSafeTest.java
new file mode 100644
index 0000000..0c19084
--- /dev/null
+++ b/test/1339-dead-reference-safe/src/DeadReferenceSafeTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import dalvik.annotation.optimization.DeadReferenceSafe;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@DeadReferenceSafe
+public final class DeadReferenceSafeTest {
+  static AtomicInteger nFinalized = new AtomicInteger(0);
+  private static final int INNER_ITERS = 10;
+  static int count;
+  static boolean interpreted;
+  int n = 1;
+
+  private static void $noinline$loop() {
+    DeadReferenceSafeTest x;
+    // The loop allocates INNER_ITERS DeadReferenceSafeTest objects.
+    for (int i = 0; i < INNER_ITERS; ++i) {
+      // We've allocated i objects so far.
+      x = new DeadReferenceSafeTest();
+      count += x.n;
+      // x is dead here.
+      if (i == 5) {
+        // With dead reference elimination, all 6 objects should have been finalized here.
+        // However the interpreter doesn't (yet?) play by the proper rules.
+        Main.$noinline$gcAndCheck(nFinalized, (interpreted ? 5 : 6), "DeadReferenceSafe",
+            "Failed to reclaim dead reference in @DeadReferenceSafe code!");
+      }
+    }
+  }
+
+  private static void reset(int expected_count) {
+    Runtime.getRuntime().gc();
+    System.runFinalization();
+    if (nFinalized.get() != expected_count) {
+      System.out.println("DeadReferenceSafeTest: Wrong number of finalized objects:"
+                         + nFinalized.get());
+    }
+    nFinalized.set(0);
+  }
+
+  protected void finalize() {
+    nFinalized.incrementAndGet();
+  }
+
+  public static void runTest() {
+    try {
+      interpreted = !Main.ensureCompiled(DeadReferenceSafeTest.class, "$noinline$loop");
+    } catch (NoSuchMethodException e) {
+      System.out.println("Unexpectedly threw " + e);
+    }
+
+    $noinline$loop();
+
+    if (count != INNER_ITERS) {
+      System.out.println("DeadReferenceSafeTest: Final count wrong: " + count);
+    }
+    reset(INNER_ITERS);
+  }
+}
diff --git a/test/1339-dead-reference-safe/src/DeadReferenceUnsafeTest.java b/test/1339-dead-reference-safe/src/DeadReferenceUnsafeTest.java
new file mode 100644
index 0000000..84774da
--- /dev/null
+++ b/test/1339-dead-reference-safe/src/DeadReferenceUnsafeTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class DeadReferenceUnsafeTest {
+  static AtomicInteger nFinalized = new AtomicInteger(0);
+  private static final int INNER_ITERS = 10;
+  static int count;
+  int n = 1;
+
+  private static void $noinline$loop() {
+    DeadReferenceUnsafeTest x;
+    // The loop allocates INNER_ITERS DeadReferenceUnsafeTest objects.
+    for (int i = 0; i < INNER_ITERS; ++i) {
+      // We've allocated i objects so far.
+      x = new DeadReferenceUnsafeTest();
+      count += x.n;
+      // x is dead here.
+      if (i == 5) {
+        // Without dead reference elimination, the last object should be kept around,
+        // and only 5 objects should be relcaimed here.
+        Main.$noinline$gcAndCheck(nFinalized, 5, "DeadReferenceUnsafe",
+            "Failed to keep dead reference live in unannotated code!");
+      }
+    }
+  }
+
+  private static void reset(int expected_count) {
+    Runtime.getRuntime().gc();
+    System.runFinalization();
+    if (nFinalized.get() != expected_count) {
+      System.out.println("DeadReferenceUnsafeTest: Wrong number of finalized objects:"
+                         + nFinalized.get());
+    }
+    nFinalized.set(0);
+  }
+
+  protected void finalize() {
+    nFinalized.incrementAndGet();
+  }
+
+  public static void runTest() {
+    try {
+      Main.ensureCompiled(DeadReferenceUnsafeTest.class, "$noinline$loop");
+    } catch (NoSuchMethodException e) {
+      System.out.println("Unexpectedly threw " + e);
+    }
+
+    $noinline$loop();
+
+    if (count != INNER_ITERS) {
+      System.out.println("DeadReferenceUnsafeTest: Final count wrong: " + count);
+    }
+    reset(INNER_ITERS);
+  }
+}
diff --git a/test/1339-dead-reference-safe/src/Main.java b/test/1339-dead-reference-safe/src/Main.java
new file mode 100644
index 0000000..46b533a
--- /dev/null
+++ b/test/1339-dead-reference-safe/src/Main.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Main {
+
+  // Ensure that the "loop" method is compiled. Otherwise we currently have no real way to get rid
+  // of dead references. Return true if it looks like we succeeded.
+  public static boolean ensureCompiled(Class cls, String methodName) throws NoSuchMethodException {
+    Method m = cls.getDeclaredMethod(methodName);
+    if (isAotCompiled(cls, methodName)) {
+      return true;
+    } else {
+      ensureMethodJitCompiled(m);
+      if (hasJitCompiledEntrypoint(cls, methodName)) {
+        return true;
+      }
+      return false;
+    }
+  }
+
+  // Garbage collect and check that the atomic counter has the expected value.
+  // Exped value of -1 means don't care.
+  // Noinline because we don't want the inlining here to interfere with the ReachabilitySensitive
+  // analysis.
+  public static void $noinline$gcAndCheck(AtomicInteger counter, int expected, String label,
+                                          String msg) {
+    Runtime.getRuntime().gc();
+    System.runFinalization();
+    int count = counter.get();
+    System.out.println(label + " count: " + count);
+    if (counter.get() != expected && expected != -1) {
+      System.out.println(msg);
+    }
+  }
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[0]);
+    // Run several variations of the same test with different reachability annotations, etc.
+    // Only the DeadReferenceSafeTest should finalize every previously allocated object.
+    DeadReferenceUnsafeTest.runTest();
+    DeadReferenceSafeTest.runTest();
+    ReachabilitySensitiveTest.runTest();
+    ReachabilitySensitiveFunTest.runTest();
+    ReachabilityFenceTest.runTest();
+  }
+  public static native void ensureMethodJitCompiled(Method meth);
+  public static native boolean hasJitCompiledEntrypoint(Class<?> cls, String methodName);
+  public static native boolean isAotCompiled(Class<?> cls, String methodName);
+}
diff --git a/test/1339-dead-reference-safe/src/ReachabilityFenceTest.java b/test/1339-dead-reference-safe/src/ReachabilityFenceTest.java
new file mode 100644
index 0000000..d4befde
--- /dev/null
+++ b/test/1339-dead-reference-safe/src/ReachabilityFenceTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// DeadReferenceSafeTest, but with a reachabilityFence.
+
+import dalvik.annotation.optimization.DeadReferenceSafe;
+import java.lang.ref.Reference;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@DeadReferenceSafe
+public final class ReachabilityFenceTest {
+  static AtomicInteger nFinalized = new AtomicInteger(0);
+  private static final int INNER_ITERS = 10;
+  static int count;
+  int n = 1;
+
+  private static void $noinline$loop() {
+    ReachabilityFenceTest x;
+    // Each loop allocates INNER_ITERS ReachabilitySenstiveTest objects.
+    for (int i = 0; i < INNER_ITERS; ++i) {
+      // We've allocated i objects so far.
+      x = new ReachabilityFenceTest();
+      count += x.n;
+      // x is dead here.
+      if (i == 5) {
+        // The rechabilityFence should keep the last allocated object reachable.
+        // Thus the last instance should not be finalized.
+        Main.$noinline$gcAndCheck(nFinalized, 5, "ReachabilityFence",
+            "reachabilityFence failed to keep object live.");
+      }
+      Reference.reachabilityFence(x);
+    }
+  }
+
+  private static void reset(int expected_count) {
+    Runtime.getRuntime().gc();
+    System.runFinalization();
+    if (nFinalized.get() != expected_count) {
+      System.out.println("ReachabilityFenceTest: Wrong number of finalized objects:"
+                         + nFinalized.get());
+    }
+    nFinalized.set(0);
+  }
+
+  protected void finalize() {
+    nFinalized.incrementAndGet();
+  }
+
+  public static void runTest() {
+    try {
+      Main.ensureCompiled(ReachabilityFenceTest.class, "$noinline$loop");
+    } catch (NoSuchMethodException e) {
+      System.out.println("Unexpectedly threw " + e);
+    }
+
+    $noinline$loop();
+
+    if (count != INNER_ITERS) {
+      System.out.println("ReachabilityFenceTest: Final count wrong: " + count);
+    }
+    reset(INNER_ITERS);
+  }
+}
diff --git a/test/1339-dead-reference-safe/src/ReachabilitySensitiveFunTest.java b/test/1339-dead-reference-safe/src/ReachabilitySensitiveFunTest.java
new file mode 100644
index 0000000..2c66146
--- /dev/null
+++ b/test/1339-dead-reference-safe/src/ReachabilitySensitiveFunTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// DeadReferenceSafeTest, but with a ReachabilitySensitive annotation.
+
+import dalvik.annotation.optimization.DeadReferenceSafe;
+import dalvik.annotation.optimization.ReachabilitySensitive;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@DeadReferenceSafe
+public final class ReachabilitySensitiveFunTest {
+  static AtomicInteger nFinalized = new AtomicInteger(0);
+  private static final int INNER_ITERS = 10;
+  static int count;
+  int n = 1;
+  @ReachabilitySensitive
+  int getN() {
+    return n;
+  }
+
+  private static void $noinline$loop() {
+    ReachabilitySensitiveFunTest x;
+    // The loop allocates INNER_ITERS ReachabilitySensitiveTest objects.
+    for (int i = 0; i < INNER_ITERS; ++i) {
+      // We've allocated i objects so far.
+      x = new ReachabilitySensitiveFunTest();
+      // ReachabilitySensitive reference.
+      count += x.getN();
+      // x is dead here.
+      if (i == 5) {
+        // Since there is a ReachabilitySensitive call, x should be kept live
+        // until it is reassigned. Thus the last instance should not be finalized.
+        Main.$noinline$gcAndCheck(nFinalized, 5, "ReachabilitySensitiveFun",
+            "@ReachabilitySensitive call failed to keep object live.");
+      }
+    }
+  }
+
+  private static void reset(int expected_count) {
+    Runtime.getRuntime().gc();
+    System.runFinalization();
+    if (nFinalized.get() != expected_count) {
+      System.out.println("ReachabilitySensitiveFunTest: Wrong number of finalized objects:"
+                         + nFinalized.get());
+    }
+    nFinalized.set(0);
+  }
+
+  protected void finalize() {
+    nFinalized.incrementAndGet();
+  }
+
+  public static void runTest() {
+    try {
+      Main.ensureCompiled(ReachabilitySensitiveFunTest.class, "$noinline$loop");
+    } catch (NoSuchMethodException e) {
+      System.out.println("Unexpectedly threw " + e);
+    }
+
+    $noinline$loop();
+
+    if (count != INNER_ITERS) {
+      System.out.println("ReachabilitySensitiveFunTest: Final count wrong: " + count);
+    }
+    reset(INNER_ITERS);
+  }
+}
diff --git a/test/1339-dead-reference-safe/src/ReachabilitySensitiveTest.java b/test/1339-dead-reference-safe/src/ReachabilitySensitiveTest.java
new file mode 100644
index 0000000..aff43b6
--- /dev/null
+++ b/test/1339-dead-reference-safe/src/ReachabilitySensitiveTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// DeadReferenceSafeTest, but with a ReachabilitySensitive annotation.
+
+import dalvik.annotation.optimization.DeadReferenceSafe;
+import dalvik.annotation.optimization.ReachabilitySensitive;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@DeadReferenceSafe
+public final class ReachabilitySensitiveTest {
+  static AtomicInteger nFinalized = new AtomicInteger(0);
+  private static final int INNER_ITERS = 10;
+  static int count;
+  @ReachabilitySensitive
+  int n = 1;
+
+  private static void $noinline$loop() {
+    ReachabilitySensitiveTest x;
+    // The loop allocates INNER_ITERS ReachabilitySensitiveTest objects.
+    for (int i = 0; i < INNER_ITERS; ++i) {
+      // We've allocated i objects so far.
+      x = new ReachabilitySensitiveTest();
+      // ReachabilitySensitive reference.
+      count += x.n;
+      // x is dead here.
+      if (i == 5) {
+        // Since there is a ReachabilitySensitive reference to x.n, x should be kept live
+        // until it is reassigned. Thus the last instance should not be finalized.
+        Main.$noinline$gcAndCheck(nFinalized, 5, "ReachabilitySensitive",
+            "@ReachabilitySensitive failed to keep object live.");
+      }
+    }
+  }
+
+  private static void reset(int expected_count) {
+    Runtime.getRuntime().gc();
+    System.runFinalization();
+    if (nFinalized.get() != expected_count) {
+      System.out.println("ReachabilitySensitiveTest: Wrong number of finalized objects:"
+                         + nFinalized.get());
+    }
+    nFinalized.set(0);
+  }
+
+  protected void finalize() {
+    nFinalized.incrementAndGet();
+  }
+
+  public static void runTest() {
+    try {
+      Main.ensureCompiled(ReachabilitySensitiveTest.class, "$noinline$loop");
+    } catch (NoSuchMethodException e) {
+      System.out.println("Unexpectedly threw " + e);
+    }
+
+    $noinline$loop();
+
+    if (count != INNER_ITERS) {
+      System.out.println("ReachabilitySensitiveTest: Final count wrong: " + count);
+    }
+    reset(INNER_ITERS);
+  }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 9c01ba9..c4764e8 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1088,6 +1088,7 @@
                   "999-redefine-hiddenapi",
                   "1000-non-moving-space-stress",
                   "1001-app-image-regions",
+                  "1339-dead-reference-safe",
                   "1951-monitor-enter-no-suspend",
                   "1957-error-ext"],
         "variant": "jvm",