ART: Switch to name-based IMT hashing
Use a hash scheme based on the name. This keeps IMT slots stable
when dex tables change.
This incurs a severe performance penalty for computing the slot.
Measurements on host degraded from 30ns to an average of 85mus.
However, calls in compiled code will not incur this overhead.
Added a test comparing similar interfaces in similar dex files.
Bug: 31594153
Test: test-art-host
Change-Id: Ibb86679ee94bec561984ea25826e56b1a7964cd0
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 850702a..4f273e5 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -29,6 +29,8 @@
GetMethodSignature \
ImageLayoutA \
ImageLayoutB \
+ IMTA \
+ IMTB \
Instrumentation \
Interfaces \
Lookup \
@@ -88,6 +90,7 @@
ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics
ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle
ART_GTEST_image_test_DEX_DEPS := ImageLayoutA ImageLayoutB
+ART_GTEST_imtable_test_DEX_DEPS := IMTA IMTB
ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation
ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives
ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods
@@ -593,6 +596,7 @@
ART_GTEST_exception_test_DEX_DEPS :=
ART_GTEST_elf_writer_test_HOST_DEPS :=
ART_GTEST_elf_writer_test_TARGET_DEPS :=
+ART_GTEST_imtable_test_DEX_DEPS :=
ART_GTEST_jni_compiler_test_DEX_DEPS :=
ART_GTEST_jni_internal_test_DEX_DEPS :=
ART_GTEST_oat_file_assistant_test_DEX_DEPS :=
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index f758414..2fcc0cf 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2864,10 +2864,17 @@
std::cerr << " " << iface->GetDescriptor(&iface_name) << std::endl;
for (ArtMethod& iface_method : iface->GetVirtualMethods(pointer_size)) {
- uint32_t base_hash = ImTable::GetBaseImtHash(&iface_method);
+ uint32_t class_hash, name_hash, signature_hash;
+ ImTable::GetImtHashComponents(&iface_method, &class_hash, &name_hash, &signature_hash);
uint32_t imt_slot = ImTable::GetImtIndex(&iface_method);
- std::cerr << " " << PrettyMethod(&iface_method, true) << " slot=" << std::dec
- << imt_slot << " base_hash=0x" << std::hex << base_hash << std::endl;
+ std::cerr << " " << PrettyMethod(&iface_method, true)
+ << " slot=" << imt_slot
+ << std::hex
+ << " class_hash=0x" << class_hash
+ << " name_hash=0x" << name_hash
+ << " signature_hash=0x" << signature_hash
+ << std::dec
+ << std::endl;
}
}
}
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 31f2490..6945eb0 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -536,6 +536,7 @@
"gc/task_processor_test.cc",
"gtest_test.cc",
"handle_scope_test.cc",
+ "imtable_test.cc",
"indenter_test.cc",
"indirect_reference_table_test.cc",
"instrumentation_test.cc",
diff --git a/runtime/imtable-inl.h b/runtime/imtable-inl.h
index 0cb9b5e..cb85fa6 100644
--- a/runtime/imtable-inl.h
+++ b/runtime/imtable-inl.h
@@ -20,15 +20,82 @@
#include "imtable.h"
#include "art_method-inl.h"
+#include "dex_file.h"
+#include "utf.h"
namespace art {
-inline uint32_t ImTable::GetBaseImtHash(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
- return method->GetDexMethodIndex();
+static constexpr bool kImTableHashUseName = true;
+static constexpr bool kImTableHashUseCoefficients = true;
+
+// Magic configuration that minimizes some common runtime calls.
+static constexpr uint32_t kImTableHashCoefficientClass = 427;
+static constexpr uint32_t kImTableHashCoefficientName = 16;
+static constexpr uint32_t kImTableHashCoefficientSignature = 14;
+
+inline void ImTable::GetImtHashComponents(ArtMethod* method,
+ uint32_t* class_hash,
+ uint32_t* name_hash,
+ uint32_t* signature_hash) {
+ if (kImTableHashUseName) {
+ if (method->IsProxyMethod()) {
+ *class_hash = 0;
+ *name_hash = 0;
+ *signature_hash = 0;
+ return;
+ }
+
+ const DexFile* dex_file = method->GetDexFile();
+ const DexFile::MethodId& method_id = dex_file->GetMethodId(method->GetDexMethodIndex());
+
+ // Class descriptor for the class component.
+ *class_hash = ComputeModifiedUtf8Hash(dex_file->GetMethodDeclaringClassDescriptor(method_id));
+
+ // Method name for the method component.
+ *name_hash = ComputeModifiedUtf8Hash(dex_file->GetMethodName(method_id));
+
+ const DexFile::ProtoId& proto_id = dex_file->GetMethodPrototype(method_id);
+
+ // Read the proto for the signature component.
+ uint32_t tmp = ComputeModifiedUtf8Hash(
+ dex_file->GetTypeDescriptor(dex_file->GetTypeId(proto_id.return_type_idx_)));
+
+ // Mix in the argument types.
+ // Note: we could consider just using the shorty. This would be faster, at the price of
+ // potential collisions.
+ const DexFile::TypeList* param_types = dex_file->GetProtoParameters(proto_id);
+ if (param_types != nullptr) {
+ for (size_t i = 0; i != param_types->Size(); ++i) {
+ const DexFile::TypeItem& type = param_types->GetTypeItem(i);
+ tmp = 31 * tmp + ComputeModifiedUtf8Hash(
+ dex_file->GetTypeDescriptor(dex_file->GetTypeId(type.type_idx_)));
+ }
+ }
+
+ *signature_hash = tmp;
+ return;
+ } else {
+ *class_hash = method->GetDexMethodIndex();
+ *name_hash = 0;
+ *signature_hash = 0;
+ return;
+ }
}
inline uint32_t ImTable::GetImtIndex(ArtMethod* method) {
- return GetBaseImtHash(method) % ImTable::kSize;
+ uint32_t class_hash, name_hash, signature_hash;
+ GetImtHashComponents(method, &class_hash, &name_hash, &signature_hash);
+
+ uint32_t mixed_hash;
+ if (!kImTableHashUseCoefficients) {
+ mixed_hash = class_hash + name_hash + signature_hash;
+ } else {
+ mixed_hash = kImTableHashCoefficientClass * class_hash +
+ kImTableHashCoefficientName * name_hash +
+ kImTableHashCoefficientSignature * signature_hash;
+ }
+
+ return mixed_hash % ImTable::kSize;
}
} // namespace art
diff --git a/runtime/imtable.h b/runtime/imtable.h
index 6df890d..b7066bd 100644
--- a/runtime/imtable.h
+++ b/runtime/imtable.h
@@ -23,6 +23,7 @@
#include "base/enums.h"
#include "base/macros.h"
+#include "base/mutex.h"
namespace art {
@@ -74,18 +75,17 @@
return kSize * static_cast<size_t>(pointer_size);
}
- // Converts a method to the base hash used in GetImtIndex.
- ALWAYS_INLINE static inline uint32_t GetBaseImtHash(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_);
- ALWAYS_INLINE static inline uint32_t GetBaseImtHash(const DexFile* dex_file, uint32_t method_idx)
+ // Converts a method to the base hash components used in GetImtIndex.
+ ALWAYS_INLINE static inline void GetImtHashComponents(ArtMethod* method,
+ uint32_t* class_hash,
+ uint32_t* name_hash,
+ uint32_t* signature_hash)
REQUIRES_SHARED(Locks::mutator_lock_);
// The (complete) hashing scheme to map an ArtMethod to a slot in the Interface Method Table
// (IMT).
ALWAYS_INLINE static inline uint32_t GetImtIndex(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_);
- ALWAYS_INLINE static inline uint32_t GetImtIndex(const DexFile* dex_file, uint32_t method_idx)
- REQUIRES_SHARED(Locks::mutator_lock_);
};
} // namespace art
diff --git a/runtime/imtable_test.cc b/runtime/imtable_test.cc
new file mode 100644
index 0000000..8cbe291
--- /dev/null
+++ b/runtime/imtable_test.cc
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "imtable-inl.h"
+
+#include <memory>
+#include <string>
+
+#include "jni.h"
+
+#include "base/mutex.h"
+#include "class_linker.h"
+#include "common_runtime_test.h"
+#include "mirror/accessible_object.h"
+#include "mirror/class.h"
+#include "mirror/class_loader.h"
+#include "handle_scope-inl.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+
+namespace art {
+
+class ImTableTest : public CommonRuntimeTest {
+ public:
+ std::pair<mirror::Class*, mirror::Class*> LoadClasses(const std::string& class_name)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ jobject jclass_loader_a = LoadDex("IMTA");
+ CHECK(jclass_loader_a != nullptr);
+ jobject jclass_loader_b = LoadDex("IMTB");
+ CHECK(jclass_loader_b != nullptr);
+
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ Thread* self = Thread::Current();
+
+ StackHandleScope<3> hs(self);
+ MutableHandle<mirror::ClassLoader> h_class_loader = hs.NewHandle<mirror::ClassLoader>(nullptr);
+
+ // A.
+ h_class_loader.Assign(
+ ObjPtr<mirror::ClassLoader>::DownCast(self->DecodeJObject(jclass_loader_a)));
+ Handle<mirror::Class> h_class_a(
+ hs.NewHandle(class_linker->FindClass(self, class_name.c_str(), h_class_loader)));
+ if (h_class_a.Get() == nullptr) {
+ LOG(ERROR) << self->GetException()->Dump();
+ CHECK(false) << "h_class_a == nullptr";
+ }
+
+ // B.
+ h_class_loader.Assign(
+ ObjPtr<mirror::ClassLoader>::DownCast(self->DecodeJObject(jclass_loader_b)));
+ Handle<mirror::Class> h_class_b(
+ hs.NewHandle(class_linker->FindClass(self, class_name.c_str(), h_class_loader)));
+ if (h_class_b.Get() == nullptr) {
+ LOG(ERROR) << self->GetException()->Dump();
+ CHECK(false) << "h_class_b == nullptr";
+ }
+
+ return std::make_pair(h_class_a.Get(), h_class_b.Get());
+ }
+
+ std::pair<ArtMethod*, ArtMethod*> LoadMethods(const std::string& class_name,
+ const std::string& method_name)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::pair<mirror::Class*, mirror::Class*> classes = LoadClasses(class_name);
+
+ const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+
+ ArtMethod* method_a =
+ classes.first->FindDeclaredVirtualMethodByName(method_name, pointer_size);
+ ArtMethod* method_b =
+ classes.second->FindDeclaredVirtualMethodByName(method_name, pointer_size);
+
+ return std::make_pair(method_a, method_b);
+ }
+};
+
+TEST_F(ImTableTest, NewMethodBefore) {
+ ScopedObjectAccess soa(Thread::Current());
+
+ std::pair<ArtMethod*, ArtMethod*> methods = LoadMethods("LInterfaces$A;", "foo");
+ CHECK_EQ(ImTable::GetImtIndex(methods.first), ImTable::GetImtIndex(methods.second));
+}
+
+TEST_F(ImTableTest, NewClassBefore) {
+ ScopedObjectAccess soa(Thread::Current());
+
+ std::pair<ArtMethod*, ArtMethod*> methods = LoadMethods("LInterfaces$Z;", "foo");
+ CHECK_EQ(ImTable::GetImtIndex(methods.first), ImTable::GetImtIndex(methods.second));
+}
+
+} // namespace art
diff --git a/test/IMTA/Interfaces.java b/test/IMTA/Interfaces.java
new file mode 100644
index 0000000..4322f15
--- /dev/null
+++ b/test/IMTA/Interfaces.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+class Interfaces {
+ interface A {
+ public void foo();
+ }
+ interface Z {
+ public void foo();
+ }
+}
diff --git a/test/IMTB/Interfaces.java b/test/IMTB/Interfaces.java
new file mode 100644
index 0000000..f252624
--- /dev/null
+++ b/test/IMTB/Interfaces.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+class Interfaces {
+ interface A {
+ public void bar();
+ public void foo();
+ }
+ interface L {
+ public void foo();
+ }
+ interface Z {
+ public void foo();
+ }
+}