diff options
author | 2018-12-05 11:11:33 -0800 | |
---|---|---|
committer | 2019-01-28 09:37:57 -0800 | |
commit | 206348cea8b086a484b8bde37b2e281e5f7db638 (patch) | |
tree | 1e83b6484dbd9218ace7c94d266dc90ed7e66505 | |
parent | 58a268aa3569d8ad4f0e831de578620e1079ed59 (diff) |
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
20 files changed, 781 insertions, 33 deletions
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 8440e9aa4c..96d6d2a1ae 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -1789,6 +1789,14 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, 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 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, method_index, codegen_->GetCompilerOptions().GetInstructionSet(), invoke_type, + callee_dead_reference_safe, graph_->IsDebuggable(), /* osr= */ false, caller_instruction_counter); @@ -2023,6 +2032,13 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, 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 48fb611da2..c70674b0ad 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -317,6 +317,7 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { 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 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { 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 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { 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 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { // 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 42dbc77087..e8f8d32525 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -828,6 +828,29 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* allocator, } 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 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* allocator, 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 @@ CodeGenerator* OptimizingCompiler::TryCompileIntrinsic( 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 92d0b08301..c88390775c 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -1155,10 +1155,11 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { * * (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 @@ class SsaLivenessAnalysis : public ValueObject { // 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 83663c50e8..f36a2aa29e 100644 --- a/libdexfile/dex/dex_instruction.cc +++ b/libdexfile/dex/dex_instruction.cc @@ -402,9 +402,9 @@ std::string Instruction::DumpString(const DexFile* file) const { 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 07193b2546..44b80df325 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -547,11 +547,10 @@ bool ArtMethod::EqualParameters(Handle<mirror::ObjectArray<mirror::Class>> param 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 e75baf88fb..050be4ad1c 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,34 +148,38 @@ bool IsVisibilityCompatible(uint32_t actual, uint32_t expected) { 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(); - 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); + const AnnotationsDirectoryItem* annotations_dir = dex_file.GetAnnotationsDirectory(class_def); if (annotations_dir == nullptr) { return nullptr; } - const FieldAnnotationsItem* field_annotations = - dex_file->GetFieldAnnotations(annotations_dir); + 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 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; + } + return FindAnnotationSetForField(*field->GetDexFile(), *class_def, field->GetDexFieldIndex()); +} + const AnnotationItem* SearchAnnotationSet(const DexFile& dex_file, const AnnotationSetItem* annotation_set, const char* descriptor, @@ -276,9 +282,9 @@ const uint8_t* SearchEncodedAnnotation(const DexFile& dex_file, 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 @@ const ParameterAnnotationsItem* FindAnnotationsItemForMethod(ArtMethod* method) 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 @@ uint32_t GetNativeMethodAnnotationAccessFlags(const DexFile& dex_file, 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 3625cee3d4..018e87f344 100644 --- a/runtime/dex/dex_file_annotations.h +++ b/runtime/dex/dex_file_annotations.h @@ -78,6 +78,7 @@ bool IsMethodAnnotationPresent(ArtMethod* method, 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 @@ bool IsMethodAnnotationPresent(ArtMethod* method, 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 e433cbc2b1..d179b802e9 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -1836,6 +1836,16 @@ OatFile::OatClass OatDexFile::GetOatClass(uint16_t class_def_index) const { 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 3e9c01f9c2..1ba6e49ab8 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -502,6 +502,9 @@ class OatDexFile final { 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 0000000000..795cfacb42 --- /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 0000000000..abafce4476 --- /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 0000000000..b6ad21793c --- /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 0000000000..0c190846e5 --- /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 0000000000..84774da38a --- /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 0000000000..46b533a3f4 --- /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 0000000000..d4befde4be --- /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 0000000000..2c661460fc --- /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 0000000000..aff43b6f19 --- /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 9c01ba9c55..c4764e8c2d 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", |