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",