void ClassHierarchyAnalysis::RemoveDependentsWithMethodHeaders( const std::unordered_set<OatQuickMethodHeader*>& method_headers) { // Iterate through all entries in the dependency map and remove any entry that // contains one of those in method_headers. for (auto map_it = cha_dependency_map_.begin(); map_it != cha_dependency_map_.end(); ) {
ListOfDependentPairs& dependents = map_it->second;
dependents.erase(
std::remove_if(
dependents.begin(),
dependents.end(),
[&method_headers](MethodAndMethodHeaderPair& dependent) { return method_headers.find(dependent.second) != method_headers.end();
}),
dependents.end());
// Remove the map entry if there are no more dependents. if (dependents.empty()) {
map_it = cha_dependency_map_.erase(map_it);
} else {
map_it++;
}
}
}
void ClassHierarchyAnalysis::ResetSingleImplementationInHierarchy(ObjPtr<mirror::Class> klass, const LinearAlloc* alloc, const PointerSize pointer_size) const { // Presumably called from some sort of class visitor, no null pointers expected.
DCHECK(klass != nullptr);
DCHECK(alloc != nullptr);
// Skip interfaces since they cannot provide SingleImplementations to work with. if (klass->IsInterface()) { return;
}
// This method is called while visiting classes in the class table of a class loader. // That means, some 'klass'es can belong to other classloaders. Argument 'alloc' // allows to explicitly indicate a classloader, which is going to be deleted. // Filter out classes, that do not belong to it. if (!alloc->ContainsUnsafe(klass->GetMethodsPtr())) { return;
}
// CHA analysis is only applied to resolved classes. if (!klass->IsResolved()) { return;
}
ObjPtr<mirror::Class> super = klass->GetSuperClass<kDefaultVerifyFlags, kWithoutReadBarrier>();
// Skip Object class and primitive classes. if (super == nullptr) { return;
}
// The class is going to be deleted. Iterate over the virtual methods of its superclasses to see // if they have SingleImplementations methods defined by 'klass'. // Skip all virtual methods that do not override methods from super class since they cannot be // SingleImplementations for anything.
int32_t vtbl_size = super->GetVTableLength<kDefaultVerifyFlags>();
ObjPtr<mirror::ClassLoader> loader =
klass->GetClassLoader<kDefaultVerifyFlags, kWithoutReadBarrier>(); for (int vtbl_index = 0; vtbl_index < vtbl_size; ++vtbl_index) {
ArtMethod* method =
klass->GetVTableEntry<kDefaultVerifyFlags, kWithoutReadBarrier>(vtbl_index, pointer_size); if (!alloc->ContainsUnsafe(method)) { continue;
}
// Find all occurrences of virtual methods in parents' SingleImplementations fields // and reset them. // No need to reset SingleImplementations for the method itself (it will be cleared anyways), // so start with a superclass and move up looking into a corresponding vtbl slot. for (ObjPtr<mirror::Class> super_it = super;
super_it != nullptr &&
super_it->GetVTableLength<kDefaultVerifyFlags>() > vtbl_index;
super_it = super_it->GetSuperClass<kDefaultVerifyFlags, kWithoutReadBarrier>()) { // Skip superclasses that are also going to be unloaded.
ObjPtr<mirror::ClassLoader> super_loader = super_it->
GetClassLoader<kDefaultVerifyFlags, kWithoutReadBarrier>(); if (super_loader == loader) { continue;
}
ArtMethod* super_method = super_it->
GetVTableEntry<kDefaultVerifyFlags, kWithoutReadBarrier>(vtbl_index, pointer_size); if (super_method->IsAbstract() &&
super_method->HasSingleImplementation() &&
super_method->GetSingleImplementation(pointer_size) == method) { // Do like there was no single implementation defined previously // for this method of the superclass.
super_method->SetSingleImplementation(nullptr, pointer_size);
} else { // No related SingleImplementations could possibly be found any further.
DCHECK(!super_method->HasSingleImplementation()); break;
}
}
}
// Check all possible interface methods too.
ObjPtr<mirror::IfTable> iftable = klass->GetIfTable<kDefaultVerifyFlags, kWithoutReadBarrier>(); const size_t ifcount = klass->GetIfTableCount<kDefaultVerifyFlags>(); for (size_t i = 0; i < ifcount; ++i) {
ObjPtr<mirror::Class> interface =
iftable->GetInterface<kDefaultVerifyFlags, kWithoutReadBarrier>(i); for (ArtMethod& interface_method : interface->GetDeclaredMethods(pointer_size)) { if (interface_method.IsVirtual() &&
interface_method.HasSingleImplementation() &&
alloc->ContainsUnsafe(interface_method.GetSingleImplementation(pointer_size)) &&
!interface_method.IsDefault()) { // Do like there was no single implementation defined previously for this method.
interface_method.SetSingleImplementation(nullptr, pointer_size);
}
}
}
}
// This stack visitor walks the stack and for compiled code with certain method // headers, sets the should_deoptimize flag on stack to 1. // TODO: also set the register value to 1 when should_deoptimize is allocated in // a register. class CHAStackVisitor final : public StackVisitor { public:
CHAStackVisitor(Thread* thread_in,
Context* context, const std::unordered_set<OatQuickMethodHeader*>& method_headers)
: StackVisitor(thread_in, context, StackVisitor::StackWalkKind::kSkipInlinedFrames),
method_headers_(method_headers) {
}
bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
ArtMethod* method = GetMethod(); // Avoid types of methods that do not have an oat quick method header. if (method == nullptr ||
method->IsRuntimeMethod() ||
method->IsNative() ||
method->IsProxyMethod()) { returntrue;
} if (GetCurrentQuickFrame() == nullptr) { // Not compiled code. returntrue;
} // Method may have multiple versions of compiled code. Check // the method header to see if it has should_deoptimize flag. const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
DCHECK(method_header != nullptr); if (!method_header->HasShouldDeoptimizeFlag()) { // This compiled version doesn't have should_deoptimize flag. Skip. returntrue;
} auto it = std::find(method_headers_.begin(), method_headers_.end(), method_header); if (it == method_headers_.end()) { // Not in the list of method headers that should be deoptimized. returntrue;
}
// The compiled code on stack is not valid anymore. Need to deoptimize.
SetShouldDeoptimizeFlag(DeoptimizeFlagValue::kCHA);
returntrue;
}
private: // Set of method headers for compiled code that should be deoptimized. const std::unordered_set<OatQuickMethodHeader*>& method_headers_;
DISALLOW_COPY_AND_ASSIGN(CHAStackVisitor);
};
class CHACheckpoint final : public Closure { public: explicit CHACheckpoint(const std::unordered_set<OatQuickMethodHeader*>& method_headers)
: barrier_(0),
method_headers_(method_headers) {}
void Run(Thread* thread) override REQUIRES_SHARED(Locks::mutator_lock_) { // Note thread and self may not be equal if thread was already suspended at // the point of the request.
Thread* self = Thread::Current();
CHAStackVisitor visitor(thread, nullptr, method_headers_);
visitor.WalkStack();
barrier_.Pass(self);
}
private: // The barrier to be passed through and for the requestor to wait upon.
Barrier barrier_; // List of method headers for invalidated compiled code. const std::unordered_set<OatQuickMethodHeader*>& method_headers_;
void ClassHierarchyAnalysis::CheckVirtualMethodSingleImplementationInfo(
Handle<mirror::Class> klass,
ArtMethod* virtual_method,
ArtMethod* method_in_super,
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_IMPLIES(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 == method_in_super ||
virtual_method->IsCopied() ||
virtual_method->GetDeclaringClass() == klass.Get());
// 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.
VerifyNonSingleImplementation(klass->GetSuperClass()->GetSuperClass(),
method_in_super->GetMethodIndex(), /* excluded_method= */ nullptr); return;
}
uint16_t method_index = method_in_super->GetMethodIndex(); if (method_in_super->IsAbstract()) { // An abstract method should have made all methods in the same vtable // slot above it in the class hierarchy having non-single-implementation.
VerifyNonSingleImplementation(klass->GetSuperClass()->GetSuperClass(),
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 (!virtual_method->IsDefaultConflicting() &&
method_in_super->GetSingleImplementation(pointer_size) == nullptr) { // Abstract method_in_super has no implementation yet. // We need to grab cha_lock_ since there may be multiple class linking // going on that can check/modify the single-implementation flag/method // of method_in_super.
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.
}
// method_in_super might be the single-implementation of another abstract method, // which should be also invalidated of its single-implementation status.
ObjPtr<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.
}
VerifyNonSingleImplementation(super_super->GetSuperClass(),
method_index,
method_in_super_super); // No need to go any further. return;
} else {
super_super = super_super->GetSuperClass();
}
}
}
}
if (!interface_method->HasSingleImplementation()) { return;
}
if (!implementation_method->IsInvokable()) {
DCHECK(implementation_method->IsAbstract() || implementation_method->IsDefaultConflicting()); // An instantiable class doesn't supply an implementation for interface_method, // or has conflicting default method implementations. Invoking the interface method // on the class will throw AbstractMethodError or IncompatibleClassChangeError. // (Note: The RI throws AME instead of ICCE for default conflict.) This is an uncommon // case, so we simply treat interface_method as not having single-implementation.
invalidated_single_impl_methods.insert(interface_method); return;
}
// We need to grab cha_lock_ since there may be multiple class linking going // on that can check/modify the single-implementation flag/method of // interface_method.
MutexLock cha_mu(Thread::Current(), *Locks::cha_lock_); // Do this check again after we grab cha_lock_. if (!interface_method->HasSingleImplementation()) { return;
}
ArtMethod* single_impl = interface_method->GetSingleImplementation(pointer_size); if (single_impl == nullptr) { // implementation_method becomes the first implementation for // interface_method.
interface_method->SetSingleImplementation(implementation_method, pointer_size); // Keep interface_method's single-implementation status. return;
}
DCHECK(single_impl->IsInvokable()); if ((single_impl->GetDeclaringClass() == implementation_method->GetDeclaringClass())) { // Same implementation. Since implementation_method may be a copy of a default // method, we need to check the declaring class for equality. return;
} // Another implementation for interface_method.
invalidated_single_impl_methods.insert(interface_method);
}
void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> klass,
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. // This frees up modifier bits for intrinsics which currently are only // used for static methods or methods of final classes. return;
} if (method->IsAbstract()) { // single-implementation of abstract method shares the same field // that's used for JNI function of native method. It's fine since a method // cannot be both abstract and native.
DCHECK(!method->IsNative()) << "Abstract method cannot be native";
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);
DCHECK(method->GetSingleImplementation(pointer_size) == nullptr);
} else { // Abstract method starts with single-implementation flag set and null // implementation method.
method->SetHasSingleImplementation(true);
DCHECK(!method->HasCodeItem()) << method->PrettyMethod();
DCHECK(method->GetSingleImplementation(pointer_size) == nullptr) << method->PrettyMethod();
} // Default conflicting methods cannot be treated with single implementations, // as we need to call them (and not inline them) in case of ICCE. // See class_linker.cc:EnsureThrowsInvocationError.
} elseif (!method->IsDefaultConflicting()) {
method->SetHasSingleImplementation(true); // Single implementation of non-abstract method is itself.
DCHECK_EQ(method->GetSingleImplementation(pointer_size), method);
}
}
void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) {
PointerSize image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); if (klass->IsInterface()) { for (ArtMethod& method : klass->GetDeclaredMethods(image_pointer_size)) { if (method.IsVirtual()) {
DCHECK(method.IsAbstract() || method.IsDefault());
InitSingleImplementationFlag(klass, &method, image_pointer_size);
}
} return;
}
// Keeps track of all methods whose single-implementation assumption // is invalidated by linking `klass`.
std::unordered_set<ArtMethod*> invalidated_single_impl_methods;
// Do an entry-by-entry comparison of vtable contents with super's vtable. for (int32_t i = 0; i < super_class->GetVTableLength(); ++i) {
ArtMethod* method = klass->GetVTableEntry(i, image_pointer_size);
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.
CheckVirtualMethodSingleImplementationInfo(klass,
method,
method_in_super,
invalidated_single_impl_methods,
image_pointer_size);
} continue;
}
InitSingleImplementationFlag(klass, method, image_pointer_size);
CheckVirtualMethodSingleImplementationInfo(klass,
method,
method_in_super,
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, image_pointer_size);
}
if (klass->IsInstantiable()) {
ObjPtr<mirror::IfTable> iftable = klass->GetIfTable(); const size_t ifcount = klass->GetIfTableCount(); for (size_t i = 0; i < ifcount; ++i) {
ObjPtr<mirror::Class> interface = iftable->GetInterface(i); for (ArtMethod& interface_method : interface->GetDeclaredMethods(image_pointer_size)) { if (!interface_method.IsVirtual()) { continue;
}
ObjPtr<mirror::PointerArray> method_array = iftable->GetMethodArray(i);
ArtMethod* implementation_method = method_array->GetElementPtrSize<ArtMethod*>(
interface_method.GetMethodIndex(), image_pointer_size);
DCHECK(implementation_method != nullptr) << klass->PrettyClass();
CheckInterfaceMethodSingleImplementationInfo(klass,
&interface_method,
implementation_method,
invalidated_single_impl_methods,
image_pointer_size);
}
}
}
void ClassHierarchyAnalysis::InvalidateSingleImplementationMethods(
std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) { if (!invalidated_single_impl_methods.empty()) {
Runtime* const runtime = Runtime::Current();
Thread *self = Thread::Current(); // Method headers for compiled code to be invalidated.
std::unordered_set<OatQuickMethodHeader*> dependent_method_headers;
PointerSize image_pointer_size =
Runtime::Current()->GetClassLinker()->GetImagePointerSize();
{ // We do this under cha_lock_. Committing code also grabs this lock to // make sure the code is only committed when all single-implementation // assumptions are still true.
std::vector<std::pair<ArtMethod*, OatQuickMethodHeader*>> headers;
{
MutexLock cha_mu(self, *Locks::cha_lock_); // Invalidate compiled methods that assume some virtual calls have only // single implementations. for (ArtMethod* invalidated : invalidated_single_impl_methods) { if (!invalidated->HasSingleImplementation()) { // It might have been invalidated already when other class linking is // going on. 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 // run any code. continue;
}
// Invalidate all dependents. for (constauto& dependent : GetDependents(invalidated)) {
ArtMethod* method = dependent.first;;
OatQuickMethodHeader* method_header = dependent.second;
VLOG(class_linker) << "CHA invalidated compiled code for " << method->PrettyMethod();
DCHECK(runtime->UseJitCompilation()); // We need to call JitCodeCache::InvalidateCompiledCodeFor but we cannot do it here // since it would run into problems with lock-ordering. We don't want to re-order the // locks since that would make code-commit racy.
headers.push_back({method, method_header});
dependent_method_headers.insert(method_header);
}
RemoveAllDependenciesFor(invalidated);
}
} // Since we are still loading the class that invalidated the code it's fine we have this after // getting rid of the dependency. Any calls would need to be with the old version (since the // new one isn't loaded yet) which still works fine. We will deoptimize just after this to // ensure everything gets the new state.
jit::Jit* jit = Runtime::Current()->GetJit(); if (jit != nullptr) {
jit::JitCodeCache* code_cache = jit->GetCodeCache(); for (constauto& pair : headers) {
code_cache->InvalidateCompiledCodeFor(pair.first, pair.second);
}
}
}
if (dependent_method_headers.empty()) { return;
} // Deoptimze compiled code on stack that should have been invalidated.
CHACheckpoint checkpoint(dependent_method_headers);
size_t threads_running_checkpoint = runtime->GetThreadList()->RunCheckpoint(&checkpoint); if (threads_running_checkpoint != 0) {
checkpoint.WaitForThreadsToRunThroughCheckpoint(threads_running_checkpoint);
}
}
}
void ClassHierarchyAnalysis::RemoveDependenciesForLinearAlloc(Thread* self, const LinearAlloc* linear_alloc) {
MutexLock mu(self, *Locks::cha_lock_); for (auto it = cha_dependency_map_.begin(); it != cha_dependency_map_.end(); ) { // Use unsafe to avoid locking since the allocator is going to be deleted. if (linear_alloc->ContainsUnsafe(it->first)) { // About to delete the ArtMethod, erase the entry from the map.
it = cha_dependency_map_.erase(it);
} else {
++it;
}
}
}
} // namespace art
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-06-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.