diff options
author | 2017-01-27 10:37:34 +0000 | |
---|---|---|
committer | 2017-01-27 10:37:36 +0000 | |
commit | ca21dc47adeed92a15a9d3fd090bdd0e6654679c (patch) | |
tree | 59aca3792abfa47c7424e4dac13248651e4662b2 | |
parent | 67c60656639acc92dca2ae8713add7d22683c7b9 (diff) | |
parent | e8fcd013493b800227bd7ea5f38f6cc27e9b90d1 (diff) |
Merge "Revert "Revert "CHA for abstract methods."""
-rw-r--r-- | compiler/image_writer.cc | 10 | ||||
-rw-r--r-- | compiler/optimizing/inliner.cc | 3 | ||||
-rw-r--r-- | runtime/art_method.cc | 6 | ||||
-rw-r--r-- | runtime/art_method.h | 5 | ||||
-rw-r--r-- | runtime/cha.cc | 196 | ||||
-rw-r--r-- | runtime/cha.h | 16 | ||||
-rw-r--r-- | test/616-cha-abstract/expected.txt | 1 | ||||
-rw-r--r-- | test/616-cha-abstract/info.txt | 1 | ||||
-rw-r--r-- | test/616-cha-abstract/run | 18 | ||||
-rw-r--r-- | test/616-cha-abstract/src/Main.java | 159 |
10 files changed, 377 insertions, 38 deletions
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 15e4cd8e9c..c72edb18a3 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -2348,6 +2348,16 @@ const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy, const ImageInfo& image_info) { + if (orig->IsAbstract()) { + // Ignore the single-implementation info for abstract method. + // Do this on orig instead of copy, otherwise there is a crash due to methods + // are copied before classes. + // TODO: handle fixup of single-implementation method for abstract method. + orig->SetHasSingleImplementation(false); + orig->SetSingleImplementation( + nullptr, Runtime::Current()->GetClassLinker()->GetImagePointerSize()); + } + memcpy(copy, orig, ArtMethod::Size(target_ptr_size_)); copy->SetDeclaringClass(GetImageAddress(orig->GetDeclaringClassUnchecked())); diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 5d40f75618..7772e8f973 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -304,7 +304,8 @@ ArtMethod* HInliner::TryCHADevirtualization(ArtMethod* resolved_method) { // We do not support HDeoptimize in OSR methods. return nullptr; } - return resolved_method->GetSingleImplementation(); + PointerSize pointer_size = caller_compilation_unit_.GetClassLinker()->GetImagePointerSize(); + return resolved_method->GetSingleImplementation(pointer_size); } bool HInliner::TryInline(HInvoke* invoke_instruction) { diff --git a/runtime/art_method.cc b/runtime/art_method.cc index a3d9ba61f3..ec789f51ef 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -55,15 +55,13 @@ extern "C" void art_quick_invoke_stub(ArtMethod*, uint32_t*, uint32_t, Thread*, extern "C" void art_quick_invoke_static_stub(ArtMethod*, uint32_t*, uint32_t, Thread*, JValue*, const char*); -ArtMethod* ArtMethod::GetSingleImplementation() { +ArtMethod* ArtMethod::GetSingleImplementation(PointerSize pointer_size) { DCHECK(!IsNative()); if (!IsAbstract()) { // A non-abstract's single implementation is itself. return this; } - // TODO: add single-implementation logic for abstract method by storing it - // in ptr_sized_fields_. - return nullptr; + return reinterpret_cast<ArtMethod*>(GetDataPtrSize(pointer_size)); } ArtMethod* ArtMethod::FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa, diff --git a/runtime/art_method.h b/runtime/art_method.h index 17f343d442..a33111a343 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -456,7 +456,7 @@ class ArtMethod FINAL { } } - ArtMethod* GetSingleImplementation() + ArtMethod* GetSingleImplementation(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); ALWAYS_INLINE void SetSingleImplementation(ArtMethod* method, PointerSize pointer_size) { @@ -684,7 +684,8 @@ class ArtMethod FINAL { ArtMethod** dex_cache_resolved_methods_; // Pointer to JNI function registered to this method, or a function to resolve the JNI function, - // or the profiling data for non-native methods, or an ImtConflictTable. + // or the profiling data for non-native methods, or an ImtConflictTable, or the + // single-implementation of an abstract method. void* data_; // Method dispatch from quick compiled code invokes this pointer which may cause bridging into diff --git a/runtime/cha.cc b/runtime/cha.cc index d94b091ebd..e726bdbcb8 100644 --- a/runtime/cha.cc +++ b/runtime/cha.cc @@ -185,7 +185,8 @@ class CHACheckpoint FINAL : public Closure { }; void ClassHierarchyAnalysis::VerifyNonSingleImplementation(mirror::Class* verify_class, - uint16_t verify_index) { + uint16_t verify_index, + ArtMethod* excluded_method) { // Grab cha_lock_ to make sure all single-implementation updates are seen. PointerSize image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); @@ -195,9 +196,14 @@ void ClassHierarchyAnalysis::VerifyNonSingleImplementation(mirror::Class* verify return; } ArtMethod* verify_method = verify_class->GetVTableEntry(verify_index, image_pointer_size); - DCHECK(!verify_method->HasSingleImplementation()) - << "class: " << verify_class->PrettyClass() - << " verify_method: " << verify_method->PrettyMethod(true); + if (verify_method != excluded_method) { + DCHECK(!verify_method->HasSingleImplementation()) + << "class: " << verify_class->PrettyClass() + << " verify_method: " << verify_method->PrettyMethod(true); + if (verify_method->IsAbstract()) { + DCHECK(verify_method->GetSingleImplementation(image_pointer_size) == nullptr); + } + } verify_class = verify_class->GetSuperClass(); } } @@ -206,41 +212,160 @@ void ClassHierarchyAnalysis::CheckSingleImplementationInfo( Handle<mirror::Class> klass, ArtMethod* virtual_method, ArtMethod* method_in_super, - std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) { + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods, + PointerSize pointer_size) { // TODO: if klass is not instantiable, virtual_method isn't invocable yet so // even if it overrides, it doesn't invalidate single-implementation // assumption. - DCHECK_NE(virtual_method, method_in_super); + DCHECK((virtual_method != method_in_super) || virtual_method->IsAbstract()); DCHECK(method_in_super->GetDeclaringClass()->IsResolved()) << "class isn't resolved"; // If virtual_method doesn't come from a default interface method, it should // be supplied by klass. - DCHECK(virtual_method->IsCopied() || + DCHECK(virtual_method == method_in_super || + virtual_method->IsCopied() || virtual_method->GetDeclaringClass() == klass.Get()); - // A new virtual_method should set method_in_super to - // non-single-implementation (if not set already). - // We don't grab cha_lock_. Single-implementation flag won't be set to true - // again once it's set to false. + // To make updating single-implementation flags simple, we always maintain the following + // invariant: + // Say all virtual methods in the same vtable slot, starting from the bottom child class + // to super classes, is a sequence of unique methods m3, m2, m1, ... (after removing duplicate + // methods for inherited methods). + // For example for the following class hierarchy, + // class A { void m() { ... } } + // class B extends A { void m() { ... } } + // class C extends B {} + // class D extends C { void m() { ... } } + // the sequence is D.m(), B.m(), A.m(). + // The single-implementation status for that sequence of methods begin with one or two true's, + // then become all falses. The only case where two true's are possible is for one abstract + // method m and one non-abstract method mImpl that overrides method m. + // With the invariant, when linking in a new class, we only need to at most update one or + // two methods in the sequence for their single-implementation status, in order to maintain + // the invariant. + if (!method_in_super->HasSingleImplementation()) { // method_in_super already has multiple implementations. All methods in the // same vtable slots in its super classes should have // non-single-implementation already. if (kIsDebugBuild) { VerifyNonSingleImplementation(klass->GetSuperClass()->GetSuperClass(), - method_in_super->GetMethodIndex()); + method_in_super->GetMethodIndex(), + nullptr /* excluded_method */); } return; } // Native methods don't have single-implementation flag set. DCHECK(!method_in_super->IsNative()); - // Invalidate method_in_super's single-implementation status. - invalidated_single_impl_methods.insert(method_in_super); + + uint16_t method_index = method_in_super->GetMethodIndex(); + if (method_in_super->IsAbstract()) { + if (kIsDebugBuild) { + // An abstract method should have made all methods in the same vtable + // slot above it in the class hierarchy having non-single-implementation. + mirror::Class* super_super = klass->GetSuperClass()->GetSuperClass(); + VerifyNonSingleImplementation(super_super, + method_index, + method_in_super); + } + + if (virtual_method->IsAbstract()) { + // SUPER: abstract, VIRTUAL: abstract. + if (method_in_super == virtual_method) { + DCHECK(klass->IsInstantiable()); + // An instantiable subclass hasn't provided a concrete implementation of + // the abstract method. Invoking method_in_super may throw AbstractMethodError. + // This is an uncommon case, so we simply treat method_in_super as not + // having single-implementation. + invalidated_single_impl_methods.insert(method_in_super); + return; + } else { + // One abstract method overrides another abstract method. This is an uncommon + // case. We simply treat method_in_super as not having single-implementation. + invalidated_single_impl_methods.insert(method_in_super); + return; + } + } else { + // SUPER: abstract, VIRTUAL: non-abstract. + // A non-abstract method overrides an abstract method. + if (method_in_super->GetSingleImplementation(pointer_size) == nullptr) { + // Abstract method_in_super has no implementation yet. + // We need to grab cha_lock_ for further checking/updating due to possible + // races. + MutexLock cha_mu(Thread::Current(), *Locks::cha_lock_); + if (!method_in_super->HasSingleImplementation()) { + return; + } + if (method_in_super->GetSingleImplementation(pointer_size) == nullptr) { + // virtual_method becomes the first implementation for method_in_super. + method_in_super->SetSingleImplementation(virtual_method, pointer_size); + // Keep method_in_super's single-implementation status. + return; + } + // Fall through to invalidate method_in_super's single-implementation status. + } + // Abstract method_in_super already got one implementation. + // Invalidate method_in_super's single-implementation status. + invalidated_single_impl_methods.insert(method_in_super); + return; + } + } else { + if (virtual_method->IsAbstract()) { + // SUPER: non-abstract, VIRTUAL: abstract. + // An abstract method overrides a non-abstract method. This is an uncommon + // case, we simply treat both methods as not having single-implementation. + invalidated_single_impl_methods.insert(virtual_method); + // Fall-through to handle invalidating method_in_super of its + // single-implementation status. + } + + // SUPER: non-abstract, VIRTUAL: non-abstract/abstract(fall-through from previous if). + // Invalidate method_in_super's single-implementation status. + invalidated_single_impl_methods.insert(method_in_super); + + // method_in_super might be the single-implementation of another abstract method, + // which should be also invalidated of its single-implementation status. + mirror::Class* super_super = klass->GetSuperClass()->GetSuperClass(); + while (super_super != nullptr && + method_index < super_super->GetVTableLength()) { + ArtMethod* method_in_super_super = super_super->GetVTableEntry(method_index, pointer_size); + if (method_in_super_super != method_in_super) { + if (method_in_super_super->IsAbstract()) { + if (method_in_super_super->HasSingleImplementation()) { + // Invalidate method_in_super's single-implementation status. + invalidated_single_impl_methods.insert(method_in_super_super); + // No need to further traverse up the class hierarchy since if there + // are cases that one abstract method overrides another method, we + // should have made that method having non-single-implementation already. + } else { + // method_in_super_super is already non-single-implementation. + // No need to further traverse up the class hierarchy. + } + } else { + DCHECK(!method_in_super_super->HasSingleImplementation()); + // No need to further traverse up the class hierarchy since two non-abstract + // methods (method_in_super and method_in_super_super) should have set all + // other methods (abstract or not) in the vtable slot to be non-single-implementation. + } + + if (kIsDebugBuild) { + VerifyNonSingleImplementation(super_super->GetSuperClass(), + method_index, + method_in_super_super); + } + // No need to go any further. + return; + } else { + super_super = super_super->GetSuperClass(); + } + } + } } void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> klass, - ArtMethod* method) { + ArtMethod* method, + PointerSize pointer_size) { DCHECK(method->IsCopied() || method->GetDeclaringClass() == klass.Get()); if (klass->IsFinal() || method->IsFinal()) { // Final classes or methods do not need CHA for devirtualization. @@ -253,16 +378,21 @@ void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> // cannot be inlined. It's not worthwhile to devirtualize the // call which can add a deoptimization point. DCHECK(!method->HasSingleImplementation()); - } else { - method->SetHasSingleImplementation(true); - if (method->IsAbstract()) { - // There is no real implementation yet. - // TODO: implement single-implementation logic for abstract methods. - DCHECK(method->GetSingleImplementation() == nullptr); + } else if (method->IsAbstract()) { + if (method->GetDeclaringClass()->IsInstantiable()) { + // Rare case, but we do accept it (such as 800-smali/smali/b_26143249.smali). + // Do not attempt to devirtualize it. + method->SetHasSingleImplementation(false); } else { - // Single implementation of non-abstract method is itself. - DCHECK_EQ(method->GetSingleImplementation(), method); + // Abstract method starts with single-implementation flag set and null + // implementation method. + method->SetHasSingleImplementation(true); + DCHECK(method->GetSingleImplementation(pointer_size) == nullptr); } + } else { + method->SetHasSingleImplementation(true); + // Single implementation of non-abstract method is itself. + DCHECK_EQ(method->GetSingleImplementation(pointer_size), method); } } @@ -286,19 +416,29 @@ void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) { ArtMethod* method_in_super = super_class->GetVTableEntry(i, image_pointer_size); if (method == method_in_super) { // vtable slot entry is inherited from super class. + if (method->IsAbstract() && klass->IsInstantiable()) { + // An instantiable class that inherits an abstract method is treated as + // supplying an implementation that throws AbstractMethodError. + CheckSingleImplementationInfo(klass, + method, + method_in_super, + invalidated_single_impl_methods, + image_pointer_size); + } continue; } - InitSingleImplementationFlag(klass, method); + InitSingleImplementationFlag(klass, method, image_pointer_size); CheckSingleImplementationInfo(klass, method, method_in_super, - invalidated_single_impl_methods); + invalidated_single_impl_methods, + image_pointer_size); } // For new virtual methods that don't override. for (int32_t i = super_class->GetVTableLength(); i < klass->GetVTableLength(); ++i) { ArtMethod* method = klass->GetVTableEntry(i, image_pointer_size); - InitSingleImplementationFlag(klass, method); + InitSingleImplementationFlag(klass, method, image_pointer_size); } Runtime* const runtime = Runtime::Current(); @@ -321,6 +461,10 @@ void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) { continue; } invalidated->SetHasSingleImplementation(false); + if (invalidated->IsAbstract()) { + // Clear the single implementation method. + invalidated->SetSingleImplementation(nullptr, image_pointer_size); + } if (runtime->IsAotCompiler()) { // No need to invalidate any compiled code as the AotCompiler doesn't diff --git a/runtime/cha.h b/runtime/cha.h index ada5c89981..a56a752d8c 100644 --- a/runtime/cha.h +++ b/runtime/cha.h @@ -112,7 +112,9 @@ class ClassHierarchyAnalysis { void UpdateAfterLoadingOf(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); private: - void InitSingleImplementationFlag(Handle<mirror::Class> klass, ArtMethod* method) + void InitSingleImplementationFlag(Handle<mirror::Class> klass, + ArtMethod* method, + PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); // `virtual_method` in `klass` overrides `method_in_super`. @@ -123,12 +125,16 @@ class ClassHierarchyAnalysis { Handle<mirror::Class> klass, ArtMethod* virtual_method, ArtMethod* method_in_super, - std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods, + PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); - // Verify all methods in the same vtable slot from verify_class and its supers - // don't have single-implementation. - void VerifyNonSingleImplementation(mirror::Class* verify_class, uint16_t verify_index) + // For all methods in vtable slot at `verify_index` of `verify_class` and its + // superclasses, single-implementation status should be false, except if the + // method is `excluded_method`. + void VerifyNonSingleImplementation(mirror::Class* verify_class, + uint16_t verify_index, + ArtMethod* excluded_method) REQUIRES_SHARED(Locks::mutator_lock_); // A map that maps a method to a set of compiled code that assumes that method has a diff --git a/test/616-cha-abstract/expected.txt b/test/616-cha-abstract/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/616-cha-abstract/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/616-cha-abstract/info.txt b/test/616-cha-abstract/info.txt new file mode 100644 index 0000000000..4f7e01391d --- /dev/null +++ b/test/616-cha-abstract/info.txt @@ -0,0 +1 @@ +Test for Class Hierarchy Analysis (CHA) on abstract method. diff --git a/test/616-cha-abstract/run b/test/616-cha-abstract/run new file mode 100644 index 0000000000..d8b4f0d26c --- /dev/null +++ b/test/616-cha-abstract/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +# Run without an app image to prevent the classes to be loaded at startup. +exec ${RUN} "${@}" --no-app-image diff --git a/test/616-cha-abstract/src/Main.java b/test/616-cha-abstract/src/Main.java new file mode 100644 index 0000000000..e1d7db170d --- /dev/null +++ b/test/616-cha-abstract/src/Main.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2017 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. + */ + +abstract class Base { + abstract void foo(int i); + + void printError(String msg) { + System.out.println(msg); + } +} + +class Main1 extends Base { + void foo(int i) { + if (i != 1) { + printError("error1"); + } + } +} + +class Main2 extends Main1 { + void foo(int i) { + if (i != 2) { + printError("error2"); + } + } +} + +public class Main { + static Main1 sMain1; + static Main1 sMain2; + + static boolean sIsOptimizing = true; + static boolean sHasJIT = true; + static volatile boolean sOtherThreadStarted; + + private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { + if (hasSingleImplementation(clazz, method_name) != b) { + System.out.println(clazz + "." + method_name + + " doesn't have single implementation value of " + b); + } + } + + // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked. + // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined. + // After Dummy.createMain2() which links in Main2, live testOverride() on stack + // should be deoptimized. + static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) { + if (setHasJIT) { + if (isInterpreted()) { + sHasJIT = false; + } + return; + } + + if (createMain2 && (sIsOptimizing || sHasJIT)) { + assertIsManaged(); + } + + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + + if (createMain2) { + // Wait for the other thread to start. + while (!sOtherThreadStarted); + // Create an Main2 instance and assign it to sMain2. + // sMain1 is kept the same. + sMain2 = Dummy.createMain2(); + // Wake up the other thread. + synchronized(Main.class) { + Main.class.notify(); + } + } else if (wait) { + // This is the other thread. + synchronized(Main.class) { + sOtherThreadStarted = true; + // Wait for Main2 to be linked and deoptimization is triggered. + try { + Main.class.wait(); + } catch (Exception e) { + } + } + } + + // There should be a deoptimization here right after Main2 is linked by + // calling Dummy.createMain2(), even though sMain1 didn't change. + // The behavior here would be different if inline-cache is used, which + // doesn't deoptimize since sMain1 still hits the type cache. + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) { + // This method should be deoptimized right after Main2 is created. + assertIsInterpreted(); + } + + if (sMain2 != null) { + sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2); + } + } + + // Test scenarios under which CHA-based devirtualization happens, + // and class loading that overrides a method can invalidate compiled code. + public static void main(String[] args) { + System.loadLibrary(args[0]); + + if (isInterpreted()) { + sIsOptimizing = false; + } + + // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet. + sMain1 = new Main1(); + + ensureJitCompiled(Main.class, "testOverride"); + testOverride(false, false, true); + + if (sHasJIT && !sIsOptimizing) { + assertSingleImplementation(Base.class, "foo", true); + assertSingleImplementation(Main1.class, "foo", true); + } else { + // Main2 is verified ahead-of-time so it's linked in already. + } + + // Create another thread that also calls sMain1.foo(). + // Try to test suspend and deopt another thread. + new Thread() { + public void run() { + testOverride(false, true, false); + } + }.start(); + + // This will create Main2 instance in the middle of testOverride(). + testOverride(true, false, false); + assertSingleImplementation(Base.class, "foo", false); + assertSingleImplementation(Main1.class, "foo", false); + } + + private static native void ensureJitCompiled(Class<?> itf, String method_name); + private static native void assertIsInterpreted(); + private static native void assertIsManaged(); + private static native boolean isInterpreted(); + private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); +} + +// Put createMain2() in another class to avoid class loading due to verifier. +class Dummy { + static Main1 createMain2() { + return new Main2(); + } +} |