Merge "Freeze capabilities JVMTI tests request by default."
diff --git a/build/Android.bp b/build/Android.bp
index c5ff486..d617116 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -99,6 +99,15 @@
// Bug: 15446488. We don't omit the frame pointer to work around
// clang/libunwind bugs that cause SEGVs in run-test-004-ThreadStress.
"-fno-omit-frame-pointer",
+ // The build assumes that all our x86/x86_64 hosts (such as buildbots and developer
+ // desktops) support at least sse4.2/popcount. This firstly implies that the ART
+ // runtime binary itself may exploit these features. Secondly, this implies that
+ // the ART runtime passes these feature flags to dex2oat and JIT by calling the
+ // method InstructionSetFeatures::FromCppDefines(). Since invoking dex2oat directly
+ // does not pick up these flags, cross-compiling from a x86/x86_64 host to a
+ // x86/x86_64 target should not be affected.
+ "-msse4.2",
+ "-mpopcnt",
],
host_ldlibs: [
"-ldl",
diff --git a/build/art.go b/build/art.go
index 6c9aa89..19b39cd 100644
--- a/build/art.go
+++ b/build/art.go
@@ -153,6 +153,11 @@
cflags = append(cflags, "-DART_BASE_ADDRESS_MIN_DELTA="+minDelta)
cflags = append(cflags, "-DART_BASE_ADDRESS_MAX_DELTA="+maxDelta)
+ if len(ctx.AConfig().SanitizeHost()) > 0 && !envFalse(ctx, "ART_ENABLE_ADDRESS_SANITIZER") {
+ // We enable full sanitization on the host by default.
+ cflags = append(cflags, "-DART_ENABLE_ADDRESS_SANITIZER=1")
+ }
+
return cflags
}
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index 3683695..07bfe31 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -207,8 +207,10 @@
compiler_options_.reset(new CompilerOptions);
verification_results_.reset(new VerificationResults(compiler_options_.get()));
- callbacks_.reset(new QuickCompilerCallbacks(verification_results_.get(),
- CompilerCallbacks::CallbackMode::kCompileApp));
+ QuickCompilerCallbacks* callbacks =
+ new QuickCompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp);
+ callbacks->SetVerificationResults(verification_results_.get());
+ callbacks_.reset(callbacks);
}
Compiler::Kind CommonCompilerTest::GetCompilerKind() const {
@@ -265,8 +267,8 @@
mirror::Class* klass = class_linker_->FindClass(self, class_descriptor.c_str(), class_loader);
CHECK(klass != nullptr) << "Class not found " << class_name;
auto pointer_size = class_linker_->GetImagePointerSize();
- ArtMethod* method = klass->FindDirectMethod(method_name, signature, pointer_size);
- CHECK(method != nullptr) << "Direct method not found: "
+ ArtMethod* method = klass->FindClassMethod(method_name, signature, pointer_size);
+ CHECK(method != nullptr && method->IsDirect()) << "Direct method not found: "
<< class_name << "." << method_name << signature;
CompileMethod(method);
}
@@ -279,8 +281,8 @@
mirror::Class* klass = class_linker_->FindClass(self, class_descriptor.c_str(), class_loader);
CHECK(klass != nullptr) << "Class not found " << class_name;
auto pointer_size = class_linker_->GetImagePointerSize();
- ArtMethod* method = klass->FindVirtualMethod(method_name, signature, pointer_size);
- CHECK(method != nullptr) << "Virtual method not found: "
+ ArtMethod* method = klass->FindClassMethod(method_name, signature, pointer_size);
+ CHECK(method != nullptr && !method->IsDirect()) << "Virtual method not found: "
<< class_name << "." << method_name << signature;
CompileMethod(method);
}
diff --git a/compiler/dex/dex_to_dex_compiler.cc b/compiler/dex/dex_to_dex_compiler.cc
index fba1136..9d57b96 100644
--- a/compiler/dex/dex_to_dex_compiler.cc
+++ b/compiler/dex/dex_to_dex_compiler.cc
@@ -291,13 +291,14 @@
ScopedObjectAccess soa(Thread::Current());
ClassLinker* class_linker = unit_.GetClassLinker();
- ArtMethod* resolved_method = class_linker->ResolveMethod<ClassLinker::kForceICCECheck>(
- GetDexFile(),
- method_idx,
- unit_.GetDexCache(),
- unit_.GetClassLoader(),
- /* referrer */ nullptr,
- kVirtual);
+ ArtMethod* resolved_method =
+ class_linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
+ GetDexFile(),
+ method_idx,
+ unit_.GetDexCache(),
+ unit_.GetClassLoader(),
+ /* referrer */ nullptr,
+ kVirtual);
if (UNLIKELY(resolved_method == nullptr)) {
// Clean up any exception left by type resolution.
diff --git a/compiler/dex/inline_method_analyser.cc b/compiler/dex/inline_method_analyser.cc
index 2572291..e5ff7fc 100644
--- a/compiler/dex/inline_method_analyser.cc
+++ b/compiler/dex/inline_method_analyser.cc
@@ -145,9 +145,8 @@
DCHECK_EQ(invoke_direct->VRegC_35c(),
method->GetCodeItem()->registers_size_ - method->GetCodeItem()->ins_size_);
uint32_t method_index = invoke_direct->VRegB_35c();
- PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- ArtMethod* target_method =
- method->GetDexCache()->GetResolvedMethod(method_index, pointer_size);
+ ArtMethod* target_method = Runtime::Current()->GetClassLinker()->LookupResolvedMethod(
+ method_index, method->GetDexCache(), method->GetClassLoader());
if (kIsDebugBuild && target_method != nullptr) {
CHECK(!target_method->IsStatic());
CHECK(target_method->IsConstructor());
diff --git a/compiler/dex/quick_compiler_callbacks.h b/compiler/dex/quick_compiler_callbacks.h
index 2100522..a3a6c09 100644
--- a/compiler/dex/quick_compiler_callbacks.h
+++ b/compiler/dex/quick_compiler_callbacks.h
@@ -26,11 +26,8 @@
class QuickCompilerCallbacks FINAL : public CompilerCallbacks {
public:
- QuickCompilerCallbacks(VerificationResults* verification_results,
- CompilerCallbacks::CallbackMode mode)
- : CompilerCallbacks(mode),
- verification_results_(verification_results),
- verifier_deps_(nullptr) {}
+ explicit QuickCompilerCallbacks(CompilerCallbacks::CallbackMode mode)
+ : CompilerCallbacks(mode) {}
~QuickCompilerCallbacks() { }
@@ -52,8 +49,12 @@
verifier_deps_.reset(deps);
}
+ void SetVerificationResults(VerificationResults* verification_results) {
+ verification_results_ = verification_results;
+ }
+
private:
- VerificationResults* const verification_results_;
+ VerificationResults* verification_results_ = nullptr;
std::unique_ptr<verifier::VerifierDeps> verifier_deps_;
};
diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h
index db95bd6..b043929 100644
--- a/compiler/driver/compiler_driver-inl.h
+++ b/compiler/driver/compiler_driver-inl.h
@@ -111,7 +111,7 @@
InvokeType invoke_type) {
DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
ArtMethod* resolved_method =
- mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kForceICCECheck>(
+ mUnit->GetClassLinker()->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
*dex_cache->GetDexFile(), method_idx, dex_cache, class_loader, nullptr, invoke_type);
if (UNLIKELY(resolved_method == nullptr)) {
DCHECK(soa.Self()->IsExceptionPending());
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index f3718c4..dd58bc7 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -373,14 +373,12 @@
REQUIRES_SHARED(Locks::mutator_lock_) {
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
PointerSize image_size = class_linker->GetImagePointerSize();
- mirror::Class* cls = class_linker->FindSystemClass(self, class_name);
+ ObjPtr<mirror::Class> cls = class_linker->FindSystemClass(self, class_name);
if (cls == nullptr) {
LOG(FATAL) << "Could not find class of intrinsic " << class_name;
}
- ArtMethod* method = (invoke_type == kStatic || invoke_type == kDirect)
- ? cls->FindDeclaredDirectMethod(method_name, signature, image_size)
- : cls->FindDeclaredVirtualMethod(method_name, signature, image_size);
- if (method == nullptr) {
+ ArtMethod* method = cls->FindClassMethod(method_name, signature, image_size);
+ if (method == nullptr || method->GetDeclaringClass() != cls) {
LOG(FATAL) << "Could not find method of intrinsic "
<< class_name << " " << method_name << " " << signature;
}
@@ -543,7 +541,7 @@
// TODO: Lookup annotation from DexFile directly without resolving method.
ArtMethod* method =
- Runtime::Current()->GetClassLinker()->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+ Runtime::Current()->GetClassLinker()->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
dex_file,
method_idx,
dex_cache,
@@ -1755,7 +1753,7 @@
}
if (resolve_fields_and_methods) {
while (it.HasNextDirectMethod()) {
- ArtMethod* method = class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+ ArtMethod* method = class_linker->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
dex_file, it.GetMemberIndex(), dex_cache, class_loader, nullptr,
it.GetMethodInvokeType(class_def));
if (method == nullptr) {
@@ -1764,7 +1762,7 @@
it.Next();
}
while (it.HasNextVirtualMethod()) {
- ArtMethod* method = class_linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+ ArtMethod* method = class_linker->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
dex_file, it.GetMemberIndex(), dex_cache, class_loader, nullptr,
it.GetMethodInvokeType(class_def));
if (method == nullptr) {
@@ -2333,10 +2331,8 @@
// a ReaderWriterMutex but we're holding the mutator lock so we fail mutex sanity
// checks in Thread::AssertThreadSuspensionIsAllowable.
Runtime* const runtime = Runtime::Current();
- Transaction transaction;
-
// Run the class initializer in transaction mode.
- runtime->EnterTransactionMode(&transaction);
+ runtime->EnterTransactionMode(klass.Get());
bool success = manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, true,
true);
// TODO we detach transaction from runtime to indicate we quit the transactional
@@ -2345,7 +2341,11 @@
{
ScopedAssertNoThreadSuspension ants("Transaction end");
- runtime->ExitTransactionMode();
+
+ if (success) {
+ runtime->ExitTransactionMode();
+ DCHECK(!runtime->IsActiveTransaction());
+ }
if (!success) {
CHECK(soa.Self()->IsExceptionPending());
@@ -2359,7 +2359,7 @@
*file_log << exception->Dump() << "\n";
}
soa.Self()->ClearException();
- transaction.Rollback();
+ runtime->RollbackAndExitTransactionMode();
CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored";
} else if (is_boot_image) {
// For boot image, we want to put the updated status in the oat class since we can't
diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc
index b4ad325..5d1d972 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -118,10 +118,12 @@
EXPECT_TRUE(type != nullptr) << "type_idx=" << i
<< " " << dex.GetTypeDescriptor(dex.GetTypeId(dex::TypeIndex(i)));
}
- EXPECT_EQ(dex.NumMethodIds(), dex_cache->NumResolvedMethods());
+ EXPECT_TRUE(dex_cache->StaticMethodSize() == dex_cache->NumResolvedMethods()
+ || dex.NumMethodIds() == dex_cache->NumResolvedMethods());
auto* cl = Runtime::Current()->GetClassLinker();
auto pointer_size = cl->GetImagePointerSize();
for (size_t i = 0; i < dex_cache->NumResolvedMethods(); i++) {
+ // FIXME: This is outdated for hash-based method array.
ArtMethod* method = dex_cache->GetResolvedMethod(i, pointer_size);
EXPECT_TRUE(method != nullptr) << "method_idx=" << i
<< " " << dex.GetMethodDeclaringClassDescriptor(dex.GetMethodId(i))
@@ -133,6 +135,7 @@
EXPECT_TRUE(dex_cache->StaticArtFieldSize() == dex_cache->NumResolvedFields()
|| dex.NumFieldIds() == dex_cache->NumResolvedFields());
for (size_t i = 0; i < dex_cache->NumResolvedFields(); i++) {
+ // FIXME: This is outdated for hash-based field array.
ArtField* field = dex_cache->GetResolvedField(i, cl->GetImagePointerSize());
EXPECT_TRUE(field != nullptr) << "field_idx=" << i
<< " " << dex.GetFieldDeclaringClassDescriptor(dex.GetFieldId(i))
diff --git a/compiler/exception_test.cc b/compiler/exception_test.cc
index b4777df..0b3ca69 100644
--- a/compiler/exception_test.cc
+++ b/compiler/exception_test.cc
@@ -102,12 +102,14 @@
CHECK_ALIGNED(stack_maps_offset, 2);
}
- method_f_ = my_klass_->FindVirtualMethod("f", "()I", kRuntimePointerSize);
+ method_f_ = my_klass_->FindClassMethod("f", "()I", kRuntimePointerSize);
ASSERT_TRUE(method_f_ != nullptr);
+ ASSERT_FALSE(method_f_->IsDirect());
method_f_->SetEntryPointFromQuickCompiledCode(code_ptr);
- method_g_ = my_klass_->FindVirtualMethod("g", "(I)V", kRuntimePointerSize);
+ method_g_ = my_klass_->FindClassMethod("g", "(I)V", kRuntimePointerSize);
ASSERT_TRUE(method_g_ != nullptr);
+ ASSERT_FALSE(method_g_->IsDirect());
method_g_->SetEntryPointFromQuickCompiledCode(code_ptr);
}
diff --git a/compiler/image_test.cc b/compiler/image_test.cc
index 9d7aff7..252fdd6 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -113,9 +113,9 @@
mirror::Class* iface_klass = class_linker_->LookupClass(
self, "LIface;", ObjPtr<mirror::ClassLoader>());
ASSERT_NE(nullptr, iface_klass);
- ArtMethod* origin = iface_klass->FindDeclaredVirtualMethod(
- "defaultMethod", "()V", pointer_size);
+ ArtMethod* origin = iface_klass->FindInterfaceMethod("defaultMethod", "()V", pointer_size);
ASSERT_NE(nullptr, origin);
+ ASSERT_TRUE(origin->GetDeclaringClass() == iface_klass);
const void* code = origin->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size);
// The origin method should have a pointer to quick code
ASSERT_NE(nullptr, code);
@@ -134,9 +134,11 @@
mirror::Class* iterable_klass = class_linker_->LookupClass(
self, "Ljava/lang/Iterable;", ObjPtr<mirror::ClassLoader>());
ASSERT_NE(nullptr, iterable_klass);
- origin = iterable_klass->FindDeclaredVirtualMethod(
+ origin = iterable_klass->FindClassMethod(
"forEach", "(Ljava/util/function/Consumer;)V", pointer_size);
ASSERT_NE(nullptr, origin);
+ ASSERT_FALSE(origin->IsDirect());
+ ASSERT_TRUE(origin->GetDeclaringClass() == iterable_klass);
code = origin->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size);
// the origin method should have a pointer to quick code
ASSERT_NE(nullptr, code);
diff --git a/compiler/image_test.h b/compiler/image_test.h
index fa714ad..6c3a89b 100644
--- a/compiler/image_test.h
+++ b/compiler/image_test.h
@@ -84,9 +84,10 @@
void SetUpRuntimeOptions(RuntimeOptions* options) OVERRIDE {
CommonCompilerTest::SetUpRuntimeOptions(options);
- callbacks_.reset(new QuickCompilerCallbacks(
- verification_results_.get(),
- CompilerCallbacks::CallbackMode::kCompileBootImage));
+ QuickCompilerCallbacks* new_callbacks =
+ new QuickCompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileBootImage);
+ new_callbacks->SetVerificationResults(verification_results_.get());
+ callbacks_.reset(new_callbacks);
options->push_back(std::make_pair("compilercallbacks", callbacks_.get()));
}
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index f92bf95..51730cf 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -1023,41 +1023,58 @@
Runtime* runtime = Runtime::Current();
ClassLinker* class_linker = runtime->GetClassLinker();
- ArtMethod* resolution_method = runtime->GetResolutionMethod();
const DexFile& dex_file = *dex_cache->GetDexFile();
// Prune methods.
- ArtMethod** resolved_methods = dex_cache->GetResolvedMethods();
- for (size_t i = 0, num = dex_cache->NumResolvedMethods(); i != num; ++i) {
- ArtMethod* method =
- mirror::DexCache::GetElementPtrSize(resolved_methods, i, target_ptr_size_);
- DCHECK(method != nullptr) << "Expected resolution method instead of null method";
+ mirror::MethodDexCacheType* resolved_methods = dex_cache->GetResolvedMethods();
+ dex::TypeIndex last_class_idx; // Initialized to invalid index.
+ ObjPtr<mirror::Class> last_class = nullptr;
+ for (size_t i = 0, num = dex_cache->GetDexFile()->NumMethodIds(); i != num; ++i) {
+ uint32_t slot_idx = dex_cache->MethodSlotIndex(i);
+ auto pair =
+ mirror::DexCache::GetNativePairPtrSize(resolved_methods, slot_idx, target_ptr_size_);
+ uint32_t stored_index = pair.index;
+ ArtMethod* method = pair.object;
+ if (method != nullptr && i > stored_index) {
+ continue; // Already checked.
+ }
// Check if the referenced class is in the image. Note that we want to check the referenced
// class rather than the declaring class to preserve the semantics, i.e. using a MethodId
// results in resolving the referenced class and that can for example throw OOME.
- ObjPtr<mirror::Class> referencing_class = class_linker->LookupResolvedType(
- dex_file,
- dex_file.GetMethodId(i).class_idx_,
- dex_cache,
- class_loader);
- // Copied methods may be held live by a class which was not an image class but have a
- // declaring class which is an image class. Set it to the resolution method to be safe and
- // prevent dangling pointers.
- if (method->IsCopied() || !KeepClass(referencing_class)) {
- mirror::DexCache::SetElementPtrSize(resolved_methods,
- i,
- resolution_method,
- target_ptr_size_);
- } else if (kIsDebugBuild) {
- // Check that the class is still in the classes table.
- ReaderMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
- CHECK(class_linker->ClassInClassTable(referencing_class)) << "Class "
- << Class::PrettyClass(referencing_class) << " not in class linker table";
+ const DexFile::MethodId& method_id = dex_file.GetMethodId(i);
+ if (method_id.class_idx_ != last_class_idx) {
+ last_class_idx = method_id.class_idx_;
+ last_class = class_linker->LookupResolvedType(
+ dex_file, last_class_idx, dex_cache, class_loader);
+ if (last_class != nullptr && !KeepClass(last_class)) {
+ last_class = nullptr;
+ }
+ }
+ if (method == nullptr || i < stored_index) {
+ if (last_class != nullptr) {
+ const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
+ Signature signature = dex_file.GetMethodSignature(method_id);
+ if (last_class->IsInterface()) {
+ method = last_class->FindInterfaceMethod(name, signature, target_ptr_size_);
+ } else {
+ method = last_class->FindClassMethod(name, signature, target_ptr_size_);
+ }
+ if (method != nullptr) {
+ // If the referenced class is in the image, the defining class must also be there.
+ DCHECK(KeepClass(method->GetDeclaringClass()));
+ dex_cache->SetResolvedMethod(i, method, target_ptr_size_);
+ }
+ }
+ } else {
+ DCHECK_EQ(i, stored_index);
+ if (last_class == nullptr) {
+ dex_cache->ClearResolvedMethod(stored_index, target_ptr_size_);
+ }
}
}
// Prune fields and make the contents of the field array deterministic.
mirror::FieldDexCacheType* resolved_fields = dex_cache->GetResolvedFields();
- dex::TypeIndex last_class_idx; // Initialized to invalid index.
- ObjPtr<mirror::Class> last_class = nullptr;
+ last_class_idx = dex::TypeIndex(); // Initialized to invalid index.
+ last_class = nullptr;
for (size_t i = 0, end = dex_file.NumFieldIds(); i < end; ++i) {
uint32_t slot_idx = dex_cache->FieldSlotIndex(i);
auto pair = mirror::DexCache::GetNativePairPtrSize(resolved_fields, slot_idx, target_ptr_size_);
@@ -2401,17 +2418,19 @@
orig_dex_cache->FixupResolvedTypes(NativeCopyLocation(orig_types, orig_dex_cache),
fixup_visitor);
}
- ArtMethod** orig_methods = orig_dex_cache->GetResolvedMethods();
+ mirror::MethodDexCacheType* orig_methods = orig_dex_cache->GetResolvedMethods();
if (orig_methods != nullptr) {
copy_dex_cache->SetFieldPtrWithSize<false>(mirror::DexCache::ResolvedMethodsOffset(),
NativeLocationInImage(orig_methods),
PointerSize::k64);
- ArtMethod** copy_methods = NativeCopyLocation(orig_methods, orig_dex_cache);
+ mirror::MethodDexCacheType* copy_methods = NativeCopyLocation(orig_methods, orig_dex_cache);
for (size_t i = 0, num = orig_dex_cache->NumResolvedMethods(); i != num; ++i) {
- ArtMethod* orig = mirror::DexCache::GetElementPtrSize(orig_methods, i, target_ptr_size_);
+ mirror::MethodDexCachePair orig_pair =
+ mirror::DexCache::GetNativePairPtrSize(orig_methods, i, target_ptr_size_);
// NativeLocationInImage also handles runtime methods since these have relocation info.
- ArtMethod* copy = NativeLocationInImage(orig);
- mirror::DexCache::SetElementPtrSize(copy_methods, i, copy, target_ptr_size_);
+ mirror::MethodDexCachePair copy_pair(NativeLocationInImage(orig_pair.object),
+ orig_pair.index);
+ mirror::DexCache::SetNativePairPtrSize(copy_methods, i, copy_pair, target_ptr_size_);
}
}
mirror::FieldDexCacheType* orig_fields = orig_dex_cache->GetResolvedFields();
@@ -2552,7 +2571,8 @@
CopyReference(copy->GetDeclaringClassAddressWithoutBarrier(), orig->GetDeclaringClassUnchecked());
- ArtMethod** orig_resolved_methods = orig->GetDexCacheResolvedMethods(target_ptr_size_);
+ mirror::MethodDexCacheType* orig_resolved_methods =
+ orig->GetDexCacheResolvedMethods(target_ptr_size_);
copy->SetDexCacheResolvedMethods(NativeLocationInImage(orig_resolved_methods), target_ptr_size_);
// OatWriter replaces the code_ with an offset value. Here we re-adjust to a pointer relative to
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 6c7baf0..3460efe 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -247,9 +247,9 @@
// Compile the native method before starting the runtime
mirror::Class* c = class_linker_->FindClass(soa.Self(), "LMyClassNatives;", loader);
const auto pointer_size = class_linker_->GetImagePointerSize();
- ArtMethod* method = direct ? c->FindDirectMethod(method_name, method_sig, pointer_size) :
- c->FindVirtualMethod(method_name, method_sig, pointer_size);
+ ArtMethod* method = c->FindClassMethod(method_name, method_sig, pointer_size);
ASSERT_TRUE(method != nullptr) << method_name << " " << method_sig;
+ ASSERT_EQ(direct, method->IsDirect()) << method_name << " " << method_sig;
if (check_generic_jni_) {
method->SetEntryPointFromQuickCompiledCode(class_linker_->GetRuntimeQuickGenericJniStub());
} else {
diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc
index 910d7a7..6f89049 100644
--- a/compiler/oat_test.cc
+++ b/compiler/oat_test.cc
@@ -104,8 +104,8 @@
compiler_options_->ParseCompilerOption(option, Usage);
}
verification_results_.reset(new VerificationResults(compiler_options_.get()));
- callbacks_.reset(new QuickCompilerCallbacks(verification_results_.get(),
- CompilerCallbacks::CallbackMode::kCompileApp));
+ callbacks_.reset(new QuickCompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp));
+ callbacks_->SetVerificationResults(verification_results_.get());
Runtime::Current()->SetCompilerCallbacks(callbacks_.get());
timer_.reset(new CumulativeLogger("Compilation times"));
compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 6120ed0..4d258af 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -1116,6 +1116,7 @@
const std::vector<const DexFile*>* dex_files)
: OatDexMethodVisitor(writer, offset),
pointer_size_(GetInstructionSetPointerSize(writer_->compiler_driver_->GetInstructionSet())),
+ class_loader_(writer->HasImage() ? writer->image_writer_->GetClassLoader() : nullptr),
dex_files_(dex_files),
class_linker_(Runtime::Current()->GetClassLinker()) {}
@@ -1131,10 +1132,7 @@
if (!IsImageClass()) {
return true;
}
- ScopedObjectAccessUnchecked soa(Thread::Current());
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::DexCache> dex_cache = hs.NewHandle(
- class_linker_->FindDexCache(Thread::Current(), *dex_file));
+ ObjPtr<mirror::DexCache> dex_cache = class_linker_->FindDexCache(Thread::Current(), *dex_file);
const DexFile::ClassDef& class_def = dex_file->GetClassDef(class_def_index);
mirror::Class* klass = dex_cache->GetResolvedType(class_def.class_idx_);
if (klass != nullptr) {
@@ -1143,11 +1141,13 @@
// in the copied method should be the same as in the origin
// method.
mirror::Class* declaring_class = method.GetDeclaringClass();
- ArtMethod* origin = declaring_class->FindDeclaredVirtualMethod(
+ ArtMethod* origin = declaring_class->FindClassMethod(
declaring_class->GetDexCache(),
method.GetDexMethodIndex(),
pointer_size_);
CHECK(origin != nullptr);
+ CHECK(!origin->IsDirect());
+ CHECK(origin->GetDeclaringClass() == declaring_class);
if (IsInOatFile(&declaring_class->GetDexFile())) {
const void* code_ptr =
origin->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_);
@@ -1180,36 +1180,36 @@
++method_offsets_index_;
}
- // Unchecked as we hold mutator_lock_ on entry.
- ScopedObjectAccessUnchecked soa(Thread::Current());
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker_->FindDexCache(
- Thread::Current(), *dex_file_)));
+ Thread* self = Thread::Current();
+ ObjPtr<mirror::DexCache> dex_cache = class_linker_->FindDexCache(self, *dex_file_);
ArtMethod* method;
if (writer_->HasBootImage()) {
const InvokeType invoke_type = it.GetMethodInvokeType(
dex_file_->GetClassDef(class_def_index_));
- method = class_linker_->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+ // Unchecked as we hold mutator_lock_ on entry.
+ ScopedObjectAccessUnchecked soa(self);
+ StackHandleScope<1> hs(self);
+ method = class_linker_->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
*dex_file_,
it.GetMemberIndex(),
- dex_cache,
+ hs.NewHandle(dex_cache),
ScopedNullHandle<mirror::ClassLoader>(),
nullptr,
invoke_type);
if (method == nullptr) {
LOG(FATAL_WITHOUT_ABORT) << "Unexpected failure to resolve a method: "
<< dex_file_->PrettyMethod(it.GetMemberIndex(), true);
- soa.Self()->AssertPendingException();
- mirror::Throwable* exc = soa.Self()->GetException();
+ self->AssertPendingException();
+ mirror::Throwable* exc = self->GetException();
std::string dump = exc->Dump();
LOG(FATAL) << dump;
UNREACHABLE();
}
} else {
- // Should already have been resolved by the compiler, just peek into the dex cache.
+ // Should already have been resolved by the compiler.
// It may not be resolved if the class failed to verify, in this case, don't set the
- // entrypoint. This is not fatal since the dex cache will contain a resolution method.
- method = dex_cache->GetResolvedMethod(it.GetMemberIndex(), pointer_size_);
+ // entrypoint. This is not fatal since we shall use a resolution method.
+ method = class_linker_->LookupResolvedMethod(it.GetMemberIndex(), dex_cache, class_loader_);
}
if (method != nullptr &&
compiled_method != nullptr &&
@@ -1250,6 +1250,7 @@
private:
const PointerSize pointer_size_;
+ ObjPtr<mirror::ClassLoader> class_loader_;
const std::vector<const DexFile*>* dex_files_;
ClassLinker* const class_linker_;
std::vector<std::pair<ArtMethod*, ArtMethod*>> methods_to_process_;
@@ -1469,7 +1470,8 @@
ObjPtr<mirror::DexCache> dex_cache =
(dex_file_ == ref.dex_file) ? dex_cache_ : class_linker_->FindDexCache(
Thread::Current(), *ref.dex_file);
- ArtMethod* method = dex_cache->GetResolvedMethod(ref.dex_method_index, pointer_size_);
+ ArtMethod* method =
+ class_linker_->LookupResolvedMethod(ref.dex_method_index, dex_cache, class_loader_);
CHECK(method != nullptr);
return method;
}
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index b9d4700..430cdde 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -8269,19 +8269,41 @@
const HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
if (instruction->GetType() == Primitive::kPrimInt) {
- DCHECK(!HDataProcWithShifterOp::IsExtensionOp(op_kind));
-
+ const vixl32::Register first = InputRegisterAt(instruction, 0);
+ const vixl32::Register output = OutputRegister(instruction);
const vixl32::Register second = instruction->InputAt(1)->GetType() == Primitive::kPrimLong
? LowRegisterFrom(locations->InAt(1))
: InputRegisterAt(instruction, 1);
- GenerateDataProcInstruction(kind,
- OutputRegister(instruction),
- InputRegisterAt(instruction, 0),
- Operand(second,
- ShiftFromOpKind(op_kind),
- instruction->GetShiftAmount()),
- codegen_);
+ if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
+ DCHECK_EQ(kind, HInstruction::kAdd);
+
+ switch (op_kind) {
+ case HDataProcWithShifterOp::kUXTB:
+ __ Uxtab(output, first, second);
+ break;
+ case HDataProcWithShifterOp::kUXTH:
+ __ Uxtah(output, first, second);
+ break;
+ case HDataProcWithShifterOp::kSXTB:
+ __ Sxtab(output, first, second);
+ break;
+ case HDataProcWithShifterOp::kSXTH:
+ __ Sxtah(output, first, second);
+ break;
+ default:
+ LOG(FATAL) << "Unexpected operation kind: " << op_kind;
+ UNREACHABLE();
+ }
+ } else {
+ GenerateDataProcInstruction(kind,
+ output,
+ first,
+ Operand(second,
+ ShiftFromOpKind(op_kind),
+ instruction->GetShiftAmount()),
+ codegen_);
+ }
} else {
DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index 23d188d..b6eb5c1 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -16,6 +16,7 @@
#include "code_generator_mips.h"
+#include "arch/mips/asm_support_mips.h"
#include "arch/mips/entrypoints_direct_mips.h"
#include "arch/mips/instruction_set_features_mips.h"
#include "art_method.h"
@@ -40,6 +41,11 @@
static constexpr int kCurrentMethodStackOffset = 0;
static constexpr Register kMethodRegisterArgument = A0;
+// Flags controlling the use of thunks for Baker read barriers.
+constexpr bool kBakerReadBarrierThunksEnableForFields = true;
+constexpr bool kBakerReadBarrierThunksEnableForArrays = true;
+constexpr bool kBakerReadBarrierThunksEnableForGcRoots = true;
+
Location MipsReturnLocation(Primitive::Type return_type) {
switch (return_type) {
case Primitive::kPrimBoolean:
@@ -1486,7 +1492,8 @@
__ Mfc1(dst_low, src);
__ MoveFromFpuHigh(dst_high, src);
} else {
- DCHECK(source.IsDoubleStackSlot()) << "Cannot move from " << source << " to " << destination;
+ DCHECK(source.IsDoubleStackSlot())
+ << "Cannot move from " << source << " to " << destination;
int32_t off = source.GetStackIndex();
Register r = destination.AsRegisterPairLow<Register>();
__ LoadFromOffset(kLoadDoubleword, r, SP, off);
@@ -1539,7 +1546,8 @@
} else if (source.IsFpuRegister()) {
__ StoreDToOffset(source.AsFpuRegister<FRegister>(), SP, dst_offset);
} else {
- DCHECK(source.IsDoubleStackSlot()) << "Cannot move from " << source << " to " << destination;
+ DCHECK(source.IsDoubleStackSlot())
+ << "Cannot move from " << source << " to " << destination;
__ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex());
__ StoreToOffset(kStoreWord, TMP, SP, dst_offset);
__ LoadFromOffset(kLoadWord, TMP, SP, source.GetStackIndex() + 4);
@@ -1763,8 +1771,10 @@
}
// A following instruction will add the sign-extended low half of the 32-bit
// offset to `out` (e.g. lw, jialc, addiu).
- DCHECK_EQ(info_low->patch_info_high, info_high);
- __ Bind(&info_low->label);
+ if (info_low != nullptr) {
+ DCHECK_EQ(info_low->patch_info_high, info_high);
+ __ Bind(&info_low->label);
+ }
}
CodeGeneratorMIPS::JitPatchInfo* CodeGeneratorMIPS::NewJitRootStringPatch(
@@ -1791,25 +1801,26 @@
const uint8_t* roots_data,
const CodeGeneratorMIPS::JitPatchInfo& info,
uint64_t index_in_table) const {
- uint32_t literal_offset = GetAssembler().GetLabelLocation(&info.high_label);
+ uint32_t high_literal_offset = GetAssembler().GetLabelLocation(&info.high_label);
+ uint32_t low_literal_offset = GetAssembler().GetLabelLocation(&info.low_label);
uintptr_t address =
reinterpret_cast<uintptr_t>(roots_data) + index_in_table * sizeof(GcRoot<mirror::Object>);
uint32_t addr32 = dchecked_integral_cast<uint32_t>(address);
// lui reg, addr32_high
- DCHECK_EQ(code[literal_offset + 0], 0x34);
- DCHECK_EQ(code[literal_offset + 1], 0x12);
- DCHECK_EQ((code[literal_offset + 2] & 0xE0), 0x00);
- DCHECK_EQ(code[literal_offset + 3], 0x3C);
+ DCHECK_EQ(code[high_literal_offset + 0], 0x34);
+ DCHECK_EQ(code[high_literal_offset + 1], 0x12);
+ DCHECK_EQ((code[high_literal_offset + 2] & 0xE0), 0x00);
+ DCHECK_EQ(code[high_literal_offset + 3], 0x3C);
// instr reg, reg, addr32_low
- DCHECK_EQ(code[literal_offset + 4], 0x78);
- DCHECK_EQ(code[literal_offset + 5], 0x56);
+ DCHECK_EQ(code[low_literal_offset + 0], 0x78);
+ DCHECK_EQ(code[low_literal_offset + 1], 0x56);
addr32 += (addr32 & 0x8000) << 1; // Account for sign extension in "instr reg, reg, addr32_low".
// lui reg, addr32_high
- code[literal_offset + 0] = static_cast<uint8_t>(addr32 >> 16);
- code[literal_offset + 1] = static_cast<uint8_t>(addr32 >> 24);
+ code[high_literal_offset + 0] = static_cast<uint8_t>(addr32 >> 16);
+ code[high_literal_offset + 1] = static_cast<uint8_t>(addr32 >> 24);
// instr reg, reg, addr32_low
- code[literal_offset + 4] = static_cast<uint8_t>(addr32 >> 0);
- code[literal_offset + 5] = static_cast<uint8_t>(addr32 >> 8);
+ code[low_literal_offset + 0] = static_cast<uint8_t>(addr32 >> 0);
+ code[low_literal_offset + 1] = static_cast<uint8_t>(addr32 >> 8);
}
void CodeGeneratorMIPS::EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) {
@@ -2545,7 +2556,12 @@
// We need a temporary register for the read barrier marking slow
// path in CodeGeneratorMIPS::GenerateArrayLoadWithBakerReadBarrier.
if (object_array_get_with_read_barrier && kUseBakerReadBarrier) {
- locations->AddTemp(Location::RequiresRegister());
+ bool temp_needed = instruction->GetIndex()->IsConstant()
+ ? !kBakerReadBarrierThunksEnableForFields
+ : !kBakerReadBarrierThunksEnableForArrays;
+ if (temp_needed) {
+ locations->AddTemp(Location::RequiresRegister());
+ }
}
}
@@ -2681,16 +2697,32 @@
// /* HeapReference<Object> */ out =
// *(obj + data_offset + index * sizeof(HeapReference<Object>))
if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
- Location temp = locations->GetTemp(0);
+ bool temp_needed = index.IsConstant()
+ ? !kBakerReadBarrierThunksEnableForFields
+ : !kBakerReadBarrierThunksEnableForArrays;
+ Location temp = temp_needed ? locations->GetTemp(0) : Location::NoLocation();
// Note that a potential implicit null check is handled in this
// CodeGeneratorMIPS::GenerateArrayLoadWithBakerReadBarrier call.
- codegen_->GenerateArrayLoadWithBakerReadBarrier(instruction,
- out_loc,
- obj,
- data_offset,
- index,
- temp,
- /* needs_null_check */ true);
+ DCHECK(!instruction->CanDoImplicitNullCheckOn(instruction->InputAt(0)));
+ if (index.IsConstant()) {
+ // Array load with a constant index can be treated as a field load.
+ size_t offset =
+ (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
+ codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction,
+ out_loc,
+ obj,
+ offset,
+ temp,
+ /* needs_null_check */ false);
+ } else {
+ codegen_->GenerateArrayLoadWithBakerReadBarrier(instruction,
+ out_loc,
+ obj,
+ data_offset,
+ index,
+ temp,
+ /* needs_null_check */ false);
+ }
} else {
Register out = out_loc.AsRegister<Register>();
if (index.IsConstant()) {
@@ -3093,6 +3125,7 @@
// Temp is used for read barrier.
static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
if (kEmitCompilerReadBarrier &&
+ !(kUseBakerReadBarrier && kBakerReadBarrierThunksEnableForFields) &&
(kUseBakerReadBarrier ||
type_check_kind == TypeCheckKind::kAbstractClassCheck ||
type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -6096,7 +6129,9 @@
if (object_field_get_with_read_barrier && kUseBakerReadBarrier) {
// We need a temporary register for the read barrier marking slow
// path in CodeGeneratorMIPS::GenerateFieldLoadWithBakerReadBarrier.
- locations->AddTemp(Location::RequiresRegister());
+ if (!kBakerReadBarrierThunksEnableForFields) {
+ locations->AddTemp(Location::RequiresRegister());
+ }
}
}
}
@@ -6171,7 +6206,8 @@
if (type == Primitive::kPrimNot) {
// /* HeapReference<Object> */ dst = *(obj + offset)
if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
- Location temp_loc = locations->GetTemp(0);
+ Location temp_loc =
+ kBakerReadBarrierThunksEnableForFields ? Location::NoLocation() : locations->GetTemp(0);
// Note that a potential implicit null check is handled in this
// CodeGeneratorMIPS::GenerateFieldLoadWithBakerReadBarrier call.
codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction,
@@ -6395,7 +6431,9 @@
Register out_reg = out.AsRegister<Register>();
if (read_barrier_option == kWithReadBarrier) {
CHECK(kEmitCompilerReadBarrier);
- DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ if (!kUseBakerReadBarrier || !kBakerReadBarrierThunksEnableForFields) {
+ DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ }
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(out + offset)
@@ -6435,7 +6473,9 @@
if (read_barrier_option == kWithReadBarrier) {
CHECK(kEmitCompilerReadBarrier);
if (kUseBakerReadBarrier) {
- DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ if (!kBakerReadBarrierThunksEnableForFields) {
+ DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ }
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(obj + offset)
codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction,
@@ -6458,67 +6498,172 @@
}
}
+static inline int GetBakerMarkThunkNumber(Register reg) {
+ static_assert(BAKER_MARK_INTROSPECTION_REGISTER_COUNT == 21, "Expecting equal");
+ if (reg >= V0 && reg <= T7) { // 14 consequtive regs.
+ return reg - V0;
+ } else if (reg >= S2 && reg <= S7) { // 6 consequtive regs.
+ return 14 + (reg - S2);
+ } else if (reg == FP) { // One more.
+ return 20;
+ }
+ LOG(FATAL) << "Unexpected register " << reg;
+ UNREACHABLE();
+}
+
+static inline int GetBakerMarkFieldArrayThunkDisplacement(Register reg, bool short_offset) {
+ int num = GetBakerMarkThunkNumber(reg) +
+ (short_offset ? BAKER_MARK_INTROSPECTION_REGISTER_COUNT : 0);
+ return num * BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE;
+}
+
+static inline int GetBakerMarkGcRootThunkDisplacement(Register reg) {
+ return GetBakerMarkThunkNumber(reg) * BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE +
+ BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET;
+}
+
void InstructionCodeGeneratorMIPS::GenerateGcRootFieldLoad(HInstruction* instruction,
Location root,
Register obj,
uint32_t offset,
- ReadBarrierOption read_barrier_option) {
+ ReadBarrierOption read_barrier_option,
+ MipsLabel* label_low) {
+ bool reordering;
+ if (label_low != nullptr) {
+ DCHECK_EQ(offset, 0x5678u);
+ }
Register root_reg = root.AsRegister<Register>();
if (read_barrier_option == kWithReadBarrier) {
DCHECK(kEmitCompilerReadBarrier);
if (kUseBakerReadBarrier) {
// Fast path implementation of art::ReadBarrier::BarrierForRoot when
// Baker's read barrier are used:
- //
- // root = obj.field;
- // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
- // if (temp != null) {
- // root = temp(root)
- // }
+ if (kBakerReadBarrierThunksEnableForGcRoots) {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded GC root or not. Instead, we
+ // load into `temp` (T9) the read barrier mark introspection entrypoint.
+ // If `temp` is null, it means that `GetIsGcMarking()` is false, and
+ // vice versa.
+ //
+ // We use thunks for the slow path. That thunk checks the reference
+ // and jumps to the entrypoint if needed.
+ //
+ // temp = Thread::Current()->pReadBarrierMarkReg00
+ // // AKA &art_quick_read_barrier_mark_introspection.
+ // GcRoot<mirror::Object> root = *(obj+offset); // Original reference load.
+ // if (temp != nullptr) {
+ // temp = &gc_root_thunk<root_reg>
+ // root = temp(root)
+ // }
- // /* GcRoot<mirror::Object> */ root = *(obj + offset)
- __ LoadFromOffset(kLoadWord, root_reg, obj, offset);
- static_assert(
- sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(GcRoot<mirror::Object>),
- "art::mirror::CompressedReference<mirror::Object> and art::GcRoot<mirror::Object> "
- "have different sizes.");
- static_assert(sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(int32_t),
- "art::mirror::CompressedReference<mirror::Object> and int32_t "
- "have different sizes.");
+ bool isR6 = codegen_->GetInstructionSetFeatures().IsR6();
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(0);
+ const int thunk_disp = GetBakerMarkGcRootThunkDisplacement(root_reg);
+ int16_t offset_low = Low16Bits(offset);
+ int16_t offset_high = High16Bits(offset - offset_low); // Accounts for sign
+ // extension in lw.
+ bool short_offset = IsInt<16>(static_cast<int32_t>(offset));
+ Register base = short_offset ? obj : TMP;
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadWord, T9, TR, entry_point_offset);
+ reordering = __ SetReorder(false);
+ if (!short_offset) {
+ DCHECK(!label_low);
+ __ AddUpper(base, obj, offset_high);
+ }
+ __ Beqz(T9, (isR6 ? 2 : 4)); // Skip jialc / addiu+jalr+nop.
+ if (label_low != nullptr) {
+ DCHECK(short_offset);
+ __ Bind(label_low);
+ }
+ // /* GcRoot<mirror::Object> */ root = *(obj + offset)
+ __ LoadFromOffset(kLoadWord, root_reg, base, offset_low); // Single instruction
+ // in delay slot.
+ if (isR6) {
+ __ Jialc(T9, thunk_disp);
+ } else {
+ __ Addiu(T9, T9, thunk_disp);
+ __ Jalr(T9);
+ __ Nop();
+ }
+ __ SetReorder(reordering);
+ } else {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded GC root or not. Instead, we
+ // load into `temp` (T9) the read barrier mark entry point corresponding
+ // to register `root`. If `temp` is null, it means that `GetIsGcMarking()`
+ // is false, and vice versa.
+ //
+ // GcRoot<mirror::Object> root = *(obj+offset); // Original reference load.
+ // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
+ // if (temp != null) {
+ // root = temp(root)
+ // }
- // Slow path marking the GC root `root`.
- Location temp = Location::RegisterLocation(T9);
- SlowPathCodeMIPS* slow_path =
- new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathMIPS(
- instruction,
- root,
- /*entrypoint*/ temp);
- codegen_->AddSlowPath(slow_path);
+ if (label_low != nullptr) {
+ reordering = __ SetReorder(false);
+ __ Bind(label_low);
+ }
+ // /* GcRoot<mirror::Object> */ root = *(obj + offset)
+ __ LoadFromOffset(kLoadWord, root_reg, obj, offset);
+ if (label_low != nullptr) {
+ __ SetReorder(reordering);
+ }
+ static_assert(
+ sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(GcRoot<mirror::Object>),
+ "art::mirror::CompressedReference<mirror::Object> and art::GcRoot<mirror::Object> "
+ "have different sizes.");
+ static_assert(sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(int32_t),
+ "art::mirror::CompressedReference<mirror::Object> and int32_t "
+ "have different sizes.");
- // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
- const int32_t entry_point_offset =
- Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(root.reg() - 1);
- // Loading the entrypoint does not require a load acquire since it is only changed when
- // threads are suspended or running a checkpoint.
- __ LoadFromOffset(kLoadWord, temp.AsRegister<Register>(), TR, entry_point_offset);
- // The entrypoint is null when the GC is not marking, this prevents one load compared to
- // checking GetIsGcMarking.
- __ Bnez(temp.AsRegister<Register>(), slow_path->GetEntryLabel());
- __ Bind(slow_path->GetExitLabel());
+ // Slow path marking the GC root `root`.
+ Location temp = Location::RegisterLocation(T9);
+ SlowPathCodeMIPS* slow_path =
+ new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathMIPS(
+ instruction,
+ root,
+ /*entrypoint*/ temp);
+ codegen_->AddSlowPath(slow_path);
+
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(root.reg() - 1);
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadWord, temp.AsRegister<Register>(), TR, entry_point_offset);
+ __ Bnez(temp.AsRegister<Register>(), slow_path->GetEntryLabel());
+ __ Bind(slow_path->GetExitLabel());
+ }
} else {
+ if (label_low != nullptr) {
+ reordering = __ SetReorder(false);
+ __ Bind(label_low);
+ }
// GC root loaded through a slow path for read barriers other
// than Baker's.
// /* GcRoot<mirror::Object>* */ root = obj + offset
__ Addiu32(root_reg, obj, offset);
+ if (label_low != nullptr) {
+ __ SetReorder(reordering);
+ }
// /* mirror::Object* */ root = root->Read()
codegen_->GenerateReadBarrierForRootSlow(instruction, root, root);
}
} else {
+ if (label_low != nullptr) {
+ reordering = __ SetReorder(false);
+ __ Bind(label_low);
+ }
// Plain GC root load with no read barrier.
// /* GcRoot<mirror::Object> */ root = *(obj + offset)
__ LoadFromOffset(kLoadWord, root_reg, obj, offset);
// Note that GC roots are not affected by heap poisoning, thus we
// do not have to unpoison `root_reg` here.
+ if (label_low != nullptr) {
+ __ SetReorder(reordering);
+ }
}
}
@@ -6531,6 +6676,88 @@
DCHECK(kEmitCompilerReadBarrier);
DCHECK(kUseBakerReadBarrier);
+ if (kBakerReadBarrierThunksEnableForFields) {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded reference or not. Instead, we
+ // load into `temp` (T9) the read barrier mark introspection entrypoint.
+ // If `temp` is null, it means that `GetIsGcMarking()` is false, and
+ // vice versa.
+ //
+ // We use thunks for the slow path. That thunk checks the reference
+ // and jumps to the entrypoint if needed. If the holder is not gray,
+ // it issues a load-load memory barrier and returns to the original
+ // reference load.
+ //
+ // temp = Thread::Current()->pReadBarrierMarkReg00
+ // // AKA &art_quick_read_barrier_mark_introspection.
+ // if (temp != nullptr) {
+ // temp = &field_array_thunk<holder_reg>
+ // temp()
+ // }
+ // not_gray_return_address:
+ // // If the offset is too large to fit into the lw instruction, we
+ // // use an adjusted base register (TMP) here. This register
+ // // receives bits 16 ... 31 of the offset before the thunk invocation
+ // // and the thunk benefits from it.
+ // HeapReference<mirror::Object> reference = *(obj+offset); // Original reference load.
+ // gray_return_address:
+
+ DCHECK(temp.IsInvalid());
+ bool isR6 = GetInstructionSetFeatures().IsR6();
+ int16_t offset_low = Low16Bits(offset);
+ int16_t offset_high = High16Bits(offset - offset_low); // Accounts for sign extension in lw.
+ bool short_offset = IsInt<16>(static_cast<int32_t>(offset));
+ bool reordering = __ SetReorder(false);
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(0);
+ // There may have or may have not been a null check if the field offset is smaller than
+ // the page size.
+ // There must've been a null check in case it's actually a load from an array.
+ // We will, however, perform an explicit null check in the thunk as it's easier to
+ // do it than not.
+ if (instruction->IsArrayGet()) {
+ DCHECK(!needs_null_check);
+ }
+ const int thunk_disp = GetBakerMarkFieldArrayThunkDisplacement(obj, short_offset);
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadWord, T9, TR, entry_point_offset);
+ Register ref_reg = ref.AsRegister<Register>();
+ Register base = short_offset ? obj : TMP;
+ if (short_offset) {
+ if (isR6) {
+ __ Beqzc(T9, 2); // Skip jialc.
+ __ Nop(); // In forbidden slot.
+ __ Jialc(T9, thunk_disp);
+ } else {
+ __ Beqz(T9, 3); // Skip jalr+nop.
+ __ Addiu(T9, T9, thunk_disp); // In delay slot.
+ __ Jalr(T9);
+ __ Nop(); // In delay slot.
+ }
+ } else {
+ if (isR6) {
+ __ Beqz(T9, 2); // Skip jialc.
+ __ Aui(base, obj, offset_high); // In delay slot.
+ __ Jialc(T9, thunk_disp);
+ } else {
+ __ Lui(base, offset_high);
+ __ Beqz(T9, 2); // Skip jalr.
+ __ Addiu(T9, T9, thunk_disp); // In delay slot.
+ __ Jalr(T9);
+ __ Addu(base, base, obj); // In delay slot.
+ }
+ }
+ // /* HeapReference<Object> */ ref = *(obj + offset)
+ __ LoadFromOffset(kLoadWord, ref_reg, base, offset_low); // Single instruction.
+ if (needs_null_check) {
+ MaybeRecordImplicitNullCheck(instruction);
+ }
+ __ MaybeUnpoisonHeapReference(ref_reg);
+ __ SetReorder(reordering);
+ return;
+ }
+
// /* HeapReference<Object> */ ref = *(obj + offset)
Location no_index = Location::NoLocation();
ScaleFactor no_scale_factor = TIMES_1;
@@ -6557,9 +6784,69 @@
static_assert(
sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
"art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
+ ScaleFactor scale_factor = TIMES_4;
+
+ if (kBakerReadBarrierThunksEnableForArrays) {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded reference or not. Instead, we
+ // load into `temp` (T9) the read barrier mark introspection entrypoint.
+ // If `temp` is null, it means that `GetIsGcMarking()` is false, and
+ // vice versa.
+ //
+ // We use thunks for the slow path. That thunk checks the reference
+ // and jumps to the entrypoint if needed. If the holder is not gray,
+ // it issues a load-load memory barrier and returns to the original
+ // reference load.
+ //
+ // temp = Thread::Current()->pReadBarrierMarkReg00
+ // // AKA &art_quick_read_barrier_mark_introspection.
+ // if (temp != nullptr) {
+ // temp = &field_array_thunk<holder_reg>
+ // temp()
+ // }
+ // not_gray_return_address:
+ // // The element address is pre-calculated in the TMP register before the
+ // // thunk invocation and the thunk benefits from it.
+ // HeapReference<mirror::Object> reference = data[index]; // Original reference load.
+ // gray_return_address:
+
+ DCHECK(temp.IsInvalid());
+ DCHECK(index.IsValid());
+ bool reordering = __ SetReorder(false);
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(0);
+ // We will not do the explicit null check in the thunk as some form of a null check
+ // must've been done earlier.
+ DCHECK(!needs_null_check);
+ const int thunk_disp = GetBakerMarkFieldArrayThunkDisplacement(obj, /* short_offset */ false);
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadWord, T9, TR, entry_point_offset);
+ Register ref_reg = ref.AsRegister<Register>();
+ Register index_reg = index.IsRegisterPair()
+ ? index.AsRegisterPairLow<Register>()
+ : index.AsRegister<Register>();
+ if (GetInstructionSetFeatures().IsR6()) {
+ __ Beqz(T9, 2); // Skip jialc.
+ __ Lsa(TMP, index_reg, obj, scale_factor); // In delay slot.
+ __ Jialc(T9, thunk_disp);
+ } else {
+ __ Sll(TMP, index_reg, scale_factor);
+ __ Beqz(T9, 2); // Skip jalr.
+ __ Addiu(T9, T9, thunk_disp); // In delay slot.
+ __ Jalr(T9);
+ __ Addu(TMP, TMP, obj); // In delay slot.
+ }
+ // /* HeapReference<Object> */ ref = *(obj + data_offset + (index << scale_factor))
+ DCHECK(IsInt<16>(static_cast<int32_t>(data_offset))) << data_offset;
+ __ LoadFromOffset(kLoadWord, ref_reg, TMP, data_offset); // Single instruction.
+ __ MaybeUnpoisonHeapReference(ref_reg);
+ __ SetReorder(reordering);
+ return;
+ }
+
// /* HeapReference<Object> */ ref =
// *(obj + data_offset + index * sizeof(HeapReference<Object>))
- ScaleFactor scale_factor = TIMES_4;
GenerateReferenceLoadWithBakerReadBarrier(instruction,
ref,
obj,
@@ -7461,10 +7748,14 @@
bool reordering = __ SetReorder(false);
codegen_->EmitPcRelativeAddressPlaceholderHigh(bss_info_high,
temp,
- base_or_current_method_reg,
- info_low);
- GenerateGcRootFieldLoad(cls, out_loc, temp, /* placeholder */ 0x5678, read_barrier_option);
+ base_or_current_method_reg);
__ SetReorder(reordering);
+ GenerateGcRootFieldLoad(cls,
+ out_loc,
+ temp,
+ /* placeholder */ 0x5678,
+ read_barrier_option,
+ &info_low->label);
generate_null_check = true;
break;
}
@@ -7475,8 +7766,13 @@
bool reordering = __ SetReorder(false);
__ Bind(&info->high_label);
__ Lui(out, /* placeholder */ 0x1234);
- GenerateGcRootFieldLoad(cls, out_loc, out, /* placeholder */ 0x5678, read_barrier_option);
__ SetReorder(reordering);
+ GenerateGcRootFieldLoad(cls,
+ out_loc,
+ out,
+ /* placeholder */ 0x5678,
+ read_barrier_option,
+ &info->low_label);
break;
}
case HLoadClass::LoadKind::kRuntimeCall:
@@ -7623,14 +7919,14 @@
bool reordering = __ SetReorder(false);
codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high,
temp,
- base_or_current_method_reg,
- info_low);
+ base_or_current_method_reg);
+ __ SetReorder(reordering);
GenerateGcRootFieldLoad(load,
out_loc,
temp,
/* placeholder */ 0x5678,
- kCompilerReadBarrierOption);
- __ SetReorder(reordering);
+ kCompilerReadBarrierOption,
+ &info_low->label);
SlowPathCodeMIPS* slow_path =
new (GetGraph()->GetArena()) LoadStringSlowPathMIPS(load, info_high);
codegen_->AddSlowPath(slow_path);
@@ -7646,12 +7942,13 @@
bool reordering = __ SetReorder(false);
__ Bind(&info->high_label);
__ Lui(out, /* placeholder */ 0x1234);
+ __ SetReorder(reordering);
GenerateGcRootFieldLoad(load,
out_loc,
out,
/* placeholder */ 0x5678,
- kCompilerReadBarrierOption);
- __ SetReorder(reordering);
+ kCompilerReadBarrierOption,
+ &info->low_label);
return;
}
default:
diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h
index 52ee852..7195b9d 100644
--- a/compiler/optimizing/code_generator_mips.h
+++ b/compiler/optimizing/code_generator_mips.h
@@ -285,7 +285,8 @@
Location root,
Register obj,
uint32_t offset,
- ReadBarrierOption read_barrier_option);
+ ReadBarrierOption read_barrier_option,
+ MipsLabel* label_low = nullptr);
void GenerateIntCompare(IfCondition cond, LocationSummary* locations);
// When the function returns `false` it means that the condition holds if `dst` is non-zero
@@ -637,7 +638,7 @@
void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high,
Register out,
Register base,
- PcRelativePatchInfo* info_low);
+ PcRelativePatchInfo* info_low = nullptr);
// The JitPatchInfo is used for JIT string and class loads.
struct JitPatchInfo {
@@ -649,8 +650,9 @@
// String/type index.
uint64_t index;
// Label for the instruction loading the most significant half of the address.
- // The least significant half is loaded with the instruction that follows immediately.
MipsLabel high_label;
+ // Label for the instruction supplying the least significant half of the address.
+ MipsLabel low_label;
};
void PatchJitRootUse(uint8_t* code,
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 454a2dd..3e79f47 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -16,6 +16,7 @@
#include "code_generator_mips64.h"
+#include "arch/mips64/asm_support_mips64.h"
#include "art_method.h"
#include "code_generator_utils.h"
#include "compiled_method.h"
@@ -38,6 +39,11 @@
static constexpr int kCurrentMethodStackOffset = 0;
static constexpr GpuRegister kMethodRegisterArgument = A0;
+// Flags controlling the use of thunks for Baker read barriers.
+constexpr bool kBakerReadBarrierThunksEnableForFields = true;
+constexpr bool kBakerReadBarrierThunksEnableForArrays = true;
+constexpr bool kBakerReadBarrierThunksEnableForGcRoots = true;
+
Location Mips64ReturnLocation(Primitive::Type return_type) {
switch (return_type) {
case Primitive::kPrimBoolean:
@@ -1649,8 +1655,10 @@
__ Auipc(out, /* placeholder */ 0x1234);
// A following instruction will add the sign-extended low half of the 32-bit
// offset to `out` (e.g. ld, jialc, daddiu).
- DCHECK_EQ(info_low->patch_info_high, info_high);
- __ Bind(&info_low->label);
+ if (info_low != nullptr) {
+ DCHECK_EQ(info_low->patch_info_high, info_high);
+ __ Bind(&info_low->label);
+ }
}
Literal* CodeGeneratorMIPS64::DeduplicateJitStringLiteral(const DexFile& dex_file,
@@ -2117,7 +2125,12 @@
// We need a temporary register for the read barrier marking slow
// path in CodeGeneratorMIPS64::GenerateArrayLoadWithBakerReadBarrier.
if (object_array_get_with_read_barrier && kUseBakerReadBarrier) {
- locations->AddTemp(Location::RequiresRegister());
+ bool temp_needed = instruction->GetIndex()->IsConstant()
+ ? !kBakerReadBarrierThunksEnableForFields
+ : !kBakerReadBarrierThunksEnableForArrays;
+ if (temp_needed) {
+ locations->AddTemp(Location::RequiresRegister());
+ }
}
}
@@ -2254,16 +2267,32 @@
// /* HeapReference<Object> */ out =
// *(obj + data_offset + index * sizeof(HeapReference<Object>))
if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
- Location temp = locations->GetTemp(0);
+ bool temp_needed = index.IsConstant()
+ ? !kBakerReadBarrierThunksEnableForFields
+ : !kBakerReadBarrierThunksEnableForArrays;
+ Location temp = temp_needed ? locations->GetTemp(0) : Location::NoLocation();
// Note that a potential implicit null check is handled in this
// CodeGeneratorMIPS64::GenerateArrayLoadWithBakerReadBarrier call.
- codegen_->GenerateArrayLoadWithBakerReadBarrier(instruction,
- out_loc,
- obj,
- data_offset,
- index,
- temp,
- /* needs_null_check */ true);
+ DCHECK(!instruction->CanDoImplicitNullCheckOn(instruction->InputAt(0)));
+ if (index.IsConstant()) {
+ // Array load with a constant index can be treated as a field load.
+ size_t offset =
+ (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
+ codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction,
+ out_loc,
+ obj,
+ offset,
+ temp,
+ /* needs_null_check */ false);
+ } else {
+ codegen_->GenerateArrayLoadWithBakerReadBarrier(instruction,
+ out_loc,
+ obj,
+ data_offset,
+ index,
+ temp,
+ /* needs_null_check */ false);
+ }
} else {
GpuRegister out = out_loc.AsRegister<GpuRegister>();
if (index.IsConstant()) {
@@ -2666,6 +2695,7 @@
// Temp is used for read barrier.
static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
if (kEmitCompilerReadBarrier &&
+ !(kUseBakerReadBarrier && kBakerReadBarrierThunksEnableForFields) &&
(kUseBakerReadBarrier ||
type_check_kind == TypeCheckKind::kAbstractClassCheck ||
type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -4118,7 +4148,9 @@
if (object_field_get_with_read_barrier && kUseBakerReadBarrier) {
// We need a temporary register for the read barrier marking slow
// path in CodeGeneratorMIPS64::GenerateFieldLoadWithBakerReadBarrier.
- locations->AddTemp(Location::RequiresRegister());
+ if (!kBakerReadBarrierThunksEnableForFields) {
+ locations->AddTemp(Location::RequiresRegister());
+ }
}
}
@@ -4168,7 +4200,8 @@
if (type == Primitive::kPrimNot) {
// /* HeapReference<Object> */ dst = *(obj + offset)
if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
- Location temp_loc = locations->GetTemp(0);
+ Location temp_loc =
+ kBakerReadBarrierThunksEnableForFields ? Location::NoLocation() : locations->GetTemp(0);
// Note that a potential implicit null check is handled in this
// CodeGeneratorMIPS64::GenerateFieldLoadWithBakerReadBarrier call.
codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction,
@@ -4318,7 +4351,9 @@
GpuRegister out_reg = out.AsRegister<GpuRegister>();
if (read_barrier_option == kWithReadBarrier) {
CHECK(kEmitCompilerReadBarrier);
- DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ if (!kUseBakerReadBarrier || !kBakerReadBarrierThunksEnableForFields) {
+ DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ }
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(out + offset)
@@ -4358,7 +4393,9 @@
if (read_barrier_option == kWithReadBarrier) {
CHECK(kEmitCompilerReadBarrier);
if (kUseBakerReadBarrier) {
- DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ if (!kBakerReadBarrierThunksEnableForFields) {
+ DCHECK(maybe_temp.IsRegister()) << maybe_temp;
+ }
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(obj + offset)
codegen_->GenerateFieldLoadWithBakerReadBarrier(instruction,
@@ -4381,55 +4418,134 @@
}
}
-void InstructionCodeGeneratorMIPS64::GenerateGcRootFieldLoad(
- HInstruction* instruction,
- Location root,
- GpuRegister obj,
- uint32_t offset,
- ReadBarrierOption read_barrier_option) {
+static inline int GetBakerMarkThunkNumber(GpuRegister reg) {
+ static_assert(BAKER_MARK_INTROSPECTION_REGISTER_COUNT == 20, "Expecting equal");
+ if (reg >= V0 && reg <= T2) { // 13 consequtive regs.
+ return reg - V0;
+ } else if (reg >= S2 && reg <= S7) { // 6 consequtive regs.
+ return 13 + (reg - S2);
+ } else if (reg == S8) { // One more.
+ return 19;
+ }
+ LOG(FATAL) << "Unexpected register " << reg;
+ UNREACHABLE();
+}
+
+static inline int GetBakerMarkFieldArrayThunkDisplacement(GpuRegister reg, bool short_offset) {
+ int num = GetBakerMarkThunkNumber(reg) +
+ (short_offset ? BAKER_MARK_INTROSPECTION_REGISTER_COUNT : 0);
+ return num * BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE;
+}
+
+static inline int GetBakerMarkGcRootThunkDisplacement(GpuRegister reg) {
+ return GetBakerMarkThunkNumber(reg) * BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE +
+ BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET;
+}
+
+void InstructionCodeGeneratorMIPS64::GenerateGcRootFieldLoad(HInstruction* instruction,
+ Location root,
+ GpuRegister obj,
+ uint32_t offset,
+ ReadBarrierOption read_barrier_option,
+ Mips64Label* label_low) {
+ if (label_low != nullptr) {
+ DCHECK_EQ(offset, 0x5678u);
+ }
GpuRegister root_reg = root.AsRegister<GpuRegister>();
if (read_barrier_option == kWithReadBarrier) {
DCHECK(kEmitCompilerReadBarrier);
if (kUseBakerReadBarrier) {
// Fast path implementation of art::ReadBarrier::BarrierForRoot when
// Baker's read barrier are used:
- //
- // root = obj.field;
- // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
- // if (temp != null) {
- // root = temp(root)
- // }
+ if (kBakerReadBarrierThunksEnableForGcRoots) {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded GC root or not. Instead, we
+ // load into `temp` (T9) the read barrier mark introspection entrypoint.
+ // If `temp` is null, it means that `GetIsGcMarking()` is false, and
+ // vice versa.
+ //
+ // We use thunks for the slow path. That thunk checks the reference
+ // and jumps to the entrypoint if needed.
+ //
+ // temp = Thread::Current()->pReadBarrierMarkReg00
+ // // AKA &art_quick_read_barrier_mark_introspection.
+ // GcRoot<mirror::Object> root = *(obj+offset); // Original reference load.
+ // if (temp != nullptr) {
+ // temp = &gc_root_thunk<root_reg>
+ // root = temp(root)
+ // }
- // /* GcRoot<mirror::Object> */ root = *(obj + offset)
- __ LoadFromOffset(kLoadUnsignedWord, root_reg, obj, offset);
- static_assert(
- sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(GcRoot<mirror::Object>),
- "art::mirror::CompressedReference<mirror::Object> and art::GcRoot<mirror::Object> "
- "have different sizes.");
- static_assert(sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(int32_t),
- "art::mirror::CompressedReference<mirror::Object> and int32_t "
- "have different sizes.");
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(0);
+ const int thunk_disp = GetBakerMarkGcRootThunkDisplacement(root_reg);
+ int16_t offset_low = Low16Bits(offset);
+ int16_t offset_high = High16Bits(offset - offset_low); // Accounts for sign
+ // extension in lwu.
+ bool short_offset = IsInt<16>(static_cast<int32_t>(offset));
+ GpuRegister base = short_offset ? obj : TMP;
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadDoubleword, T9, TR, entry_point_offset);
+ if (!short_offset) {
+ DCHECK(!label_low);
+ __ Daui(base, obj, offset_high);
+ }
+ __ Beqz(T9, 2); // Skip jialc.
+ if (label_low != nullptr) {
+ DCHECK(short_offset);
+ __ Bind(label_low);
+ }
+ // /* GcRoot<mirror::Object> */ root = *(obj + offset)
+ __ LoadFromOffset(kLoadUnsignedWord, root_reg, base, offset_low); // Single instruction
+ // in delay slot.
+ __ Jialc(T9, thunk_disp);
+ } else {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded GC root or not. Instead, we
+ // load into `temp` (T9) the read barrier mark entry point corresponding
+ // to register `root`. If `temp` is null, it means that `GetIsGcMarking()`
+ // is false, and vice versa.
+ //
+ // GcRoot<mirror::Object> root = *(obj+offset); // Original reference load.
+ // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
+ // if (temp != null) {
+ // root = temp(root)
+ // }
- // Slow path marking the GC root `root`.
- Location temp = Location::RegisterLocation(T9);
- SlowPathCodeMIPS64* slow_path =
- new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathMIPS64(
- instruction,
- root,
- /*entrypoint*/ temp);
- codegen_->AddSlowPath(slow_path);
+ if (label_low != nullptr) {
+ __ Bind(label_low);
+ }
+ // /* GcRoot<mirror::Object> */ root = *(obj + offset)
+ __ LoadFromOffset(kLoadUnsignedWord, root_reg, obj, offset);
+ static_assert(
+ sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(GcRoot<mirror::Object>),
+ "art::mirror::CompressedReference<mirror::Object> and art::GcRoot<mirror::Object> "
+ "have different sizes.");
+ static_assert(sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(int32_t),
+ "art::mirror::CompressedReference<mirror::Object> and int32_t "
+ "have different sizes.");
- // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
- const int32_t entry_point_offset =
- Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(root.reg() - 1);
- // Loading the entrypoint does not require a load acquire since it is only changed when
- // threads are suspended or running a checkpoint.
- __ LoadFromOffset(kLoadDoubleword, temp.AsRegister<GpuRegister>(), TR, entry_point_offset);
- // The entrypoint is null when the GC is not marking, this prevents one load compared to
- // checking GetIsGcMarking.
- __ Bnezc(temp.AsRegister<GpuRegister>(), slow_path->GetEntryLabel());
- __ Bind(slow_path->GetExitLabel());
+ // Slow path marking the GC root `root`.
+ Location temp = Location::RegisterLocation(T9);
+ SlowPathCodeMIPS64* slow_path =
+ new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathMIPS64(
+ instruction,
+ root,
+ /*entrypoint*/ temp);
+ codegen_->AddSlowPath(slow_path);
+
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(root.reg() - 1);
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadDoubleword, temp.AsRegister<GpuRegister>(), TR, entry_point_offset);
+ __ Bnezc(temp.AsRegister<GpuRegister>(), slow_path->GetEntryLabel());
+ __ Bind(slow_path->GetExitLabel());
+ }
} else {
+ if (label_low != nullptr) {
+ __ Bind(label_low);
+ }
// GC root loaded through a slow path for read barriers other
// than Baker's.
// /* GcRoot<mirror::Object>* */ root = obj + offset
@@ -4438,6 +4554,9 @@
codegen_->GenerateReadBarrierForRootSlow(instruction, root, root);
}
} else {
+ if (label_low != nullptr) {
+ __ Bind(label_low);
+ }
// Plain GC root load with no read barrier.
// /* GcRoot<mirror::Object> */ root = *(obj + offset)
__ LoadFromOffset(kLoadUnsignedWord, root_reg, obj, offset);
@@ -4455,6 +4574,71 @@
DCHECK(kEmitCompilerReadBarrier);
DCHECK(kUseBakerReadBarrier);
+ if (kBakerReadBarrierThunksEnableForFields) {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded reference or not. Instead, we
+ // load into `temp` (T9) the read barrier mark introspection entrypoint.
+ // If `temp` is null, it means that `GetIsGcMarking()` is false, and
+ // vice versa.
+ //
+ // We use thunks for the slow path. That thunk checks the reference
+ // and jumps to the entrypoint if needed. If the holder is not gray,
+ // it issues a load-load memory barrier and returns to the original
+ // reference load.
+ //
+ // temp = Thread::Current()->pReadBarrierMarkReg00
+ // // AKA &art_quick_read_barrier_mark_introspection.
+ // if (temp != nullptr) {
+ // temp = &field_array_thunk<holder_reg>
+ // temp()
+ // }
+ // not_gray_return_address:
+ // // If the offset is too large to fit into the lw instruction, we
+ // // use an adjusted base register (TMP) here. This register
+ // // receives bits 16 ... 31 of the offset before the thunk invocation
+ // // and the thunk benefits from it.
+ // HeapReference<mirror::Object> reference = *(obj+offset); // Original reference load.
+ // gray_return_address:
+
+ DCHECK(temp.IsInvalid());
+ bool short_offset = IsInt<16>(static_cast<int32_t>(offset));
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(0);
+ // There may have or may have not been a null check if the field offset is smaller than
+ // the page size.
+ // There must've been a null check in case it's actually a load from an array.
+ // We will, however, perform an explicit null check in the thunk as it's easier to
+ // do it than not.
+ if (instruction->IsArrayGet()) {
+ DCHECK(!needs_null_check);
+ }
+ const int thunk_disp = GetBakerMarkFieldArrayThunkDisplacement(obj, short_offset);
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadDoubleword, T9, TR, entry_point_offset);
+ GpuRegister ref_reg = ref.AsRegister<GpuRegister>();
+ if (short_offset) {
+ __ Beqzc(T9, 2); // Skip jialc.
+ __ Nop(); // In forbidden slot.
+ __ Jialc(T9, thunk_disp);
+ // /* HeapReference<Object> */ ref = *(obj + offset)
+ __ LoadFromOffset(kLoadUnsignedWord, ref_reg, obj, offset); // Single instruction.
+ } else {
+ int16_t offset_low = Low16Bits(offset);
+ int16_t offset_high = High16Bits(offset - offset_low); // Accounts for sign extension in lwu.
+ __ Beqz(T9, 2); // Skip jialc.
+ __ Daui(TMP, obj, offset_high); // In delay slot.
+ __ Jialc(T9, thunk_disp);
+ // /* HeapReference<Object> */ ref = *(obj + offset)
+ __ LoadFromOffset(kLoadUnsignedWord, ref_reg, TMP, offset_low); // Single instruction.
+ }
+ if (needs_null_check) {
+ MaybeRecordImplicitNullCheck(instruction);
+ }
+ __ MaybeUnpoisonHeapReference(ref_reg);
+ return;
+ }
+
// /* HeapReference<Object> */ ref = *(obj + offset)
Location no_index = Location::NoLocation();
ScaleFactor no_scale_factor = TIMES_1;
@@ -4481,9 +4665,57 @@
static_assert(
sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
"art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
+ ScaleFactor scale_factor = TIMES_4;
+
+ if (kBakerReadBarrierThunksEnableForArrays) {
+ // Note that we do not actually check the value of `GetIsGcMarking()`
+ // to decide whether to mark the loaded reference or not. Instead, we
+ // load into `temp` (T9) the read barrier mark introspection entrypoint.
+ // If `temp` is null, it means that `GetIsGcMarking()` is false, and
+ // vice versa.
+ //
+ // We use thunks for the slow path. That thunk checks the reference
+ // and jumps to the entrypoint if needed. If the holder is not gray,
+ // it issues a load-load memory barrier and returns to the original
+ // reference load.
+ //
+ // temp = Thread::Current()->pReadBarrierMarkReg00
+ // // AKA &art_quick_read_barrier_mark_introspection.
+ // if (temp != nullptr) {
+ // temp = &field_array_thunk<holder_reg>
+ // temp()
+ // }
+ // not_gray_return_address:
+ // // The element address is pre-calculated in the TMP register before the
+ // // thunk invocation and the thunk benefits from it.
+ // HeapReference<mirror::Object> reference = data[index]; // Original reference load.
+ // gray_return_address:
+
+ DCHECK(temp.IsInvalid());
+ DCHECK(index.IsValid());
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(0);
+ // We will not do the explicit null check in the thunk as some form of a null check
+ // must've been done earlier.
+ DCHECK(!needs_null_check);
+ const int thunk_disp = GetBakerMarkFieldArrayThunkDisplacement(obj, /* short_offset */ false);
+ // Loading the entrypoint does not require a load acquire since it is only changed when
+ // threads are suspended or running a checkpoint.
+ __ LoadFromOffset(kLoadDoubleword, T9, TR, entry_point_offset);
+ __ Beqz(T9, 2); // Skip jialc.
+ GpuRegister ref_reg = ref.AsRegister<GpuRegister>();
+ GpuRegister index_reg = index.AsRegister<GpuRegister>();
+ __ Dlsa(TMP, index_reg, obj, scale_factor); // In delay slot.
+ __ Jialc(T9, thunk_disp);
+ // /* HeapReference<Object> */ ref = *(obj + data_offset + (index << scale_factor))
+ DCHECK(IsInt<16>(static_cast<int32_t>(data_offset))) << data_offset;
+ __ LoadFromOffset(kLoadUnsignedWord, ref_reg, TMP, data_offset); // Single instruction.
+ __ MaybeUnpoisonHeapReference(ref_reg);
+ return;
+ }
+
// /* HeapReference<Object> */ ref =
// *(obj + data_offset + index * sizeof(HeapReference<Object>))
- ScaleFactor scale_factor = TIMES_4;
GenerateReferenceLoadWithBakerReadBarrier(instruction,
ref,
obj,
@@ -5278,8 +5510,13 @@
GpuRegister temp = non_baker_read_barrier
? out
: locations->GetTemp(0).AsRegister<GpuRegister>();
- codegen_->EmitPcRelativeAddressPlaceholderHigh(bss_info_high, temp, info_low);
- GenerateGcRootFieldLoad(cls, out_loc, temp, /* placeholder */ 0x5678, read_barrier_option);
+ codegen_->EmitPcRelativeAddressPlaceholderHigh(bss_info_high, temp);
+ GenerateGcRootFieldLoad(cls,
+ out_loc,
+ temp,
+ /* placeholder */ 0x5678,
+ read_barrier_option,
+ &info_low->label);
generate_null_check = true;
break;
}
@@ -5399,12 +5636,13 @@
GpuRegister temp = non_baker_read_barrier
? out
: locations->GetTemp(0).AsRegister<GpuRegister>();
- codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, temp, info_low);
+ codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, temp);
GenerateGcRootFieldLoad(load,
out_loc,
temp,
/* placeholder */ 0x5678,
- kCompilerReadBarrierOption);
+ kCompilerReadBarrierOption,
+ &info_low->label);
SlowPathCodeMIPS64* slow_path =
new (GetGraph()->GetArena()) LoadStringSlowPathMIPS64(load, info_high);
codegen_->AddSlowPath(slow_path);
diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h
index c94cc93..d03a9ea 100644
--- a/compiler/optimizing/code_generator_mips64.h
+++ b/compiler/optimizing/code_generator_mips64.h
@@ -281,7 +281,8 @@
Location root,
GpuRegister obj,
uint32_t offset,
- ReadBarrierOption read_barrier_option);
+ ReadBarrierOption read_barrier_option,
+ Mips64Label* label_low = nullptr);
void GenerateTestAndBranch(HInstruction* instruction,
size_t condition_input_index,
@@ -592,7 +593,7 @@
void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high,
GpuRegister out,
- PcRelativePatchInfo* info_low);
+ PcRelativePatchInfo* info_low = nullptr);
void PatchJitRootUse(uint8_t* code,
const uint8_t* roots_data,
diff --git a/compiler/optimizing/common_arm.h b/compiler/optimizing/common_arm.h
index 01304ac..8fcceed 100644
--- a/compiler/optimizing/common_arm.h
+++ b/compiler/optimizing/common_arm.h
@@ -227,14 +227,6 @@
return Location::FpuRegisterPairLocation(low.GetCode(), high.GetCode());
}
-inline bool ShifterOperandSupportsExtension(HInstruction* instruction) {
- DCHECK(HasShifterOperand(instruction, kArm));
- // TODO: HAdd applied to the other integral types could make use of
- // the SXTAB, SXTAH, UXTAB and UXTAH instructions.
- return instruction->GetType() == Primitive::kPrimLong &&
- (instruction->IsAdd() || instruction->IsSub());
-}
-
} // namespace helpers
} // namespace arm
} // namespace art
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index 839f328..8054140 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -664,10 +664,7 @@
// TODO: remove redundant constructor fences (b/36656456).
if (RequiresConstructorBarrier(dex_compilation_unit_, compiler_driver_)) {
// Compiling instance constructor.
- if (kIsDebugBuild) {
- std::string method_name = graph_->GetMethodName();
- CHECK_EQ(std::string("<init>"), method_name);
- }
+ DCHECK_STREQ("<init>", graph_->GetMethodName());
HInstruction* fence_target = current_this_parameter_;
DCHECK(fence_target != nullptr);
@@ -710,29 +707,18 @@
ArtMethod* HInstructionBuilder::ResolveMethod(uint16_t method_idx, InvokeType invoke_type) {
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<2> hs(soa.Self());
ClassLinker* class_linker = dex_compilation_unit_->GetClassLinker();
Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
- Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass()));
- // We fetch the referenced class eagerly (that is, the class pointed by in the MethodId
- // at method_idx), as `CanAccessResolvedMethod` expects it be be in the dex cache.
- Handle<mirror::Class> methods_class(hs.NewHandle(class_linker->ResolveReferencedClassOfMethod(
- method_idx, dex_compilation_unit_->GetDexCache(), class_loader)));
- if (UNLIKELY(methods_class == nullptr)) {
- // Clean up any exception left by type resolution.
- soa.Self()->ClearException();
- return nullptr;
- }
-
- ArtMethod* resolved_method = class_linker->ResolveMethod<ClassLinker::kForceICCECheck>(
- *dex_compilation_unit_->GetDexFile(),
- method_idx,
- dex_compilation_unit_->GetDexCache(),
- class_loader,
- /* referrer */ nullptr,
- invoke_type);
+ ArtMethod* resolved_method =
+ class_linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
+ *dex_compilation_unit_->GetDexFile(),
+ method_idx,
+ dex_compilation_unit_->GetDexCache(),
+ class_loader,
+ graph_->GetArtMethod(),
+ invoke_type);
if (UNLIKELY(resolved_method == nullptr)) {
// Clean up any exception left by type resolution.
@@ -740,17 +726,14 @@
return nullptr;
}
- // Check access. The class linker has a fast path for looking into the dex cache
- // and does not check the access if it hits it.
- if (compiling_class == nullptr) {
+ // The referrer may be unresolved for AOT if we're compiling a class that cannot be
+ // resolved because, for example, we don't find a superclass in the classpath.
+ if (graph_->GetArtMethod() == nullptr) {
+ // The class linker cannot check access without a referrer, so we have to do it.
+ // Fall back to HInvokeUnresolved if the method isn't public.
if (!resolved_method->IsPublic()) {
return nullptr;
}
- } else if (!compiling_class->CanAccessResolvedMethod(resolved_method->GetDeclaringClass(),
- resolved_method,
- dex_compilation_unit_->GetDexCache().Get(),
- method_idx)) {
- return nullptr;
}
// We have to special case the invoke-super case, as ClassLinker::ResolveMethod does not.
@@ -758,19 +741,26 @@
// make this an invoke-unresolved to handle cross-dex invokes or abstract super methods, both of
// which require runtime handling.
if (invoke_type == kSuper) {
+ ObjPtr<mirror::Class> compiling_class = GetCompilingClass();
if (compiling_class == nullptr) {
// We could not determine the method's class we need to wait until runtime.
DCHECK(Runtime::Current()->IsAotCompiler());
return nullptr;
}
- if (!methods_class->IsAssignableFrom(compiling_class.Get())) {
+ ObjPtr<mirror::Class> referenced_class = class_linker->LookupResolvedType(
+ *dex_compilation_unit_->GetDexFile(),
+ dex_compilation_unit_->GetDexFile()->GetMethodId(method_idx).class_idx_,
+ dex_compilation_unit_->GetDexCache().Get(),
+ class_loader.Get());
+ DCHECK(referenced_class != nullptr); // We have already resolved a method from this class.
+ if (!referenced_class->IsAssignableFrom(compiling_class)) {
// We cannot statically determine the target method. The runtime will throw a
// NoSuchMethodError on this one.
return nullptr;
}
ArtMethod* actual_method;
- if (methods_class->IsInterface()) {
- actual_method = methods_class->FindVirtualMethodForInterfaceSuper(
+ if (referenced_class->IsInterface()) {
+ actual_method = referenced_class->FindVirtualMethodForInterfaceSuper(
resolved_method, class_linker->GetImagePointerSize());
} else {
uint16_t vtable_index = resolved_method->GetMethodIndex();
@@ -797,12 +787,6 @@
resolved_method = actual_method;
}
- // Check for incompatible class changes. The class linker has a fast path for
- // looking into the dex cache and does not check incompatible class changes if it hits it.
- if (resolved_method->CheckIncompatibleClassChange(invoke_type)) {
- return nullptr;
- }
-
return resolved_method;
}
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index d147166..f2a8cc0 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -1867,33 +1867,35 @@
ArtMethod* method = nullptr;
switch (source_component_type) {
case Primitive::kPrimBoolean:
- method = system->FindDeclaredDirectMethod("arraycopy", "([ZI[ZII)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([ZI[ZII)V", image_size);
break;
case Primitive::kPrimByte:
- method = system->FindDeclaredDirectMethod("arraycopy", "([BI[BII)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([BI[BII)V", image_size);
break;
case Primitive::kPrimChar:
- method = system->FindDeclaredDirectMethod("arraycopy", "([CI[CII)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([CI[CII)V", image_size);
break;
case Primitive::kPrimShort:
- method = system->FindDeclaredDirectMethod("arraycopy", "([SI[SII)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([SI[SII)V", image_size);
break;
case Primitive::kPrimInt:
- method = system->FindDeclaredDirectMethod("arraycopy", "([II[III)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([II[III)V", image_size);
break;
case Primitive::kPrimFloat:
- method = system->FindDeclaredDirectMethod("arraycopy", "([FI[FII)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([FI[FII)V", image_size);
break;
case Primitive::kPrimLong:
- method = system->FindDeclaredDirectMethod("arraycopy", "([JI[JII)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([JI[JII)V", image_size);
break;
case Primitive::kPrimDouble:
- method = system->FindDeclaredDirectMethod("arraycopy", "([DI[DII)V", image_size);
+ method = system->FindClassMethod("arraycopy", "([DI[DII)V", image_size);
break;
default:
LOG(FATAL) << "Unreachable";
}
DCHECK(method != nullptr);
+ DCHECK(method->IsStatic());
+ DCHECK(method->GetDeclaringClass() == system);
invoke->SetResolvedMethod(method);
// Sharpen the new invoke. Note that we do not update the dex method index of
// the invoke, as we would need to look it up in the current dex file, and it
diff --git a/compiler/optimizing/instruction_simplifier_arm.cc b/compiler/optimizing/instruction_simplifier_arm.cc
index fe22595..a025fb1 100644
--- a/compiler/optimizing/instruction_simplifier_arm.cc
+++ b/compiler/optimizing/instruction_simplifier_arm.cc
@@ -29,8 +29,6 @@
namespace arm {
-using helpers::ShifterOperandSupportsExtension;
-
bool InstructionSimplifierArmVisitor::TryMergeIntoShifterOperand(HInstruction* use,
HInstruction* bitfield_op,
bool do_merge) {
@@ -76,7 +74,7 @@
: kMaxLongShiftDistance;
if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
- if (!ShifterOperandSupportsExtension(use)) {
+ if (!use->IsAdd() && (!use->IsSub() || use->GetType() != Primitive::kPrimLong)) {
return false;
}
// Shift by 1 is a special case that results in the same number and type of instructions
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index 98332d3..f172e16 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -525,8 +525,8 @@
// Use a null loader. We should probably use the compiling method's class loader,
// but then we would need to pass it to RTPVisitor just for this debug check. Since
// the method is from the String class, the null loader is good enough.
- Handle<mirror::ClassLoader> loader;
- ArtMethod* method = cl->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+ Handle<mirror::ClassLoader> loader(hs.NewHandle<mirror::ClassLoader>(nullptr));
+ ArtMethod* method = cl->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
dex_file, invoke->GetDexMethodIndex(), dex_cache, loader, nullptr, kDirect);
DCHECK(method != nullptr);
mirror::Class* declaring_class = method->GetDeclaringClass();
diff --git a/compiler/optimizing/scheduler_arm.cc b/compiler/optimizing/scheduler_arm.cc
index e78cd78..f025c0a 100644
--- a/compiler/optimizing/scheduler_arm.cc
+++ b/compiler/optimizing/scheduler_arm.cc
@@ -167,22 +167,346 @@
HandleShiftLatencies(instr);
}
-void SchedulingLatencyVisitorARM::VisitCondition(HCondition* instr) {
- switch (instr->GetLeft()->GetType()) {
- case Primitive::kPrimLong:
- last_visited_internal_latency_ = 4 * kArmIntegerOpLatency;
+void SchedulingLatencyVisitorARM::HandleGenerateConditionWithZero(IfCondition condition) {
+ switch (condition) {
+ case kCondEQ:
+ case kCondBE:
+ case kCondNE:
+ case kCondA:
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ last_visited_latency_ = kArmIntegerOpLatency;
break;
- case Primitive::kPrimFloat:
- case Primitive::kPrimDouble:
- last_visited_internal_latency_ = 2 * kArmFloatingPointOpLatency;
+ case kCondGE:
+ // Mvn
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ FALLTHROUGH_INTENDED;
+ case kCondLT:
+ // Lsr
+ last_visited_latency_ = kArmIntegerOpLatency;
+ break;
+ case kCondAE:
+ // Trivially true.
+ // Mov
+ last_visited_latency_ = kArmIntegerOpLatency;
+ break;
+ case kCondB:
+ // Trivially false.
+ // Mov
+ last_visited_latency_ = kArmIntegerOpLatency;
break;
default:
- last_visited_internal_latency_ = 2 * kArmIntegerOpLatency;
- break;
+ LOG(FATAL) << "Unexpected condition " << condition;
+ UNREACHABLE();
}
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateLongTestConstant(HCondition* condition) {
+ DCHECK_EQ(condition->GetLeft()->GetType(), Primitive::kPrimLong);
+
+ IfCondition cond = condition->GetCondition();
+
+ HInstruction* right = condition->InputAt(1);
+
+ int64_t value = Uint64ConstantFrom(right);
+
+ // Comparisons against 0 are common enough, so codegen has special handling for them.
+ if (value == 0) {
+ switch (cond) {
+ case kCondNE:
+ case kCondA:
+ case kCondEQ:
+ case kCondBE:
+ // Orrs
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ return;
+ case kCondLT:
+ case kCondGE:
+ // Cmp
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ return;
+ case kCondB:
+ case kCondAE:
+ // Cmp
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ return;
+ default:
+ break;
+ }
+ }
+
+ switch (cond) {
+ case kCondEQ:
+ case kCondNE:
+ case kCondB:
+ case kCondBE:
+ case kCondA:
+ case kCondAE: {
+ // Cmp, IT, Cmp
+ last_visited_internal_latency_ += 3 * kArmIntegerOpLatency;
+ break;
+ }
+ case kCondLE:
+ case kCondGT:
+ // Trivially true or false.
+ if (value == std::numeric_limits<int64_t>::max()) {
+ // Cmp
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ break;
+ }
+ FALLTHROUGH_INTENDED;
+ case kCondGE:
+ case kCondLT: {
+ // Cmp, Sbcs
+ last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unreachable";
+ UNREACHABLE();
+ }
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateLongTest(HCondition* condition) {
+ DCHECK_EQ(condition->GetLeft()->GetType(), Primitive::kPrimLong);
+
+ IfCondition cond = condition->GetCondition();
+
+ switch (cond) {
+ case kCondEQ:
+ case kCondNE:
+ case kCondB:
+ case kCondBE:
+ case kCondA:
+ case kCondAE: {
+ // Cmp, IT, Cmp
+ last_visited_internal_latency_ += 3 * kArmIntegerOpLatency;
+ break;
+ }
+ case kCondLE:
+ case kCondGT:
+ case kCondGE:
+ case kCondLT: {
+ // Cmp, Sbcs
+ last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unreachable";
+ UNREACHABLE();
+ }
+}
+
+// The GenerateTest series of function all counted as internal latency.
+void SchedulingLatencyVisitorARM::HandleGenerateTest(HCondition* condition) {
+ const Primitive::Type type = condition->GetLeft()->GetType();
+
+ if (type == Primitive::kPrimLong) {
+ condition->InputAt(1)->IsConstant()
+ ? HandleGenerateLongTestConstant(condition)
+ : HandleGenerateLongTest(condition);
+ } else if (Primitive::IsFloatingPointType(type)) {
+ // GenerateVcmp + Vmrs
+ last_visited_internal_latency_ += 2 * kArmFloatingPointOpLatency;
+ } else {
+ // Cmp
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ }
+}
+
+bool SchedulingLatencyVisitorARM::CanGenerateTest(HCondition* condition) {
+ if (condition->GetLeft()->GetType() == Primitive::kPrimLong) {
+ HInstruction* right = condition->InputAt(1);
+
+ if (right->IsConstant()) {
+ IfCondition c = condition->GetCondition();
+ const uint64_t value = Uint64ConstantFrom(right);
+
+ if (c < kCondLT || c > kCondGE) {
+ if (value != 0) {
+ return false;
+ }
+ } else if (c == kCondLE || c == kCondGT) {
+ if (value < std::numeric_limits<int64_t>::max() &&
+ !codegen_->GetAssembler()->ShifterOperandCanHold(SBC, High32Bits(value + 1), kCcSet)) {
+ return false;
+ }
+ } else if (!codegen_->GetAssembler()->ShifterOperandCanHold(SBC, High32Bits(value), kCcSet)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateConditionGeneric(HCondition* cond) {
+ HandleGenerateTest(cond);
+
+ // Unlike codegen pass, we cannot check 'out' register IsLow() here,
+ // because scheduling is before liveness(location builder) and register allocator,
+ // so we can only choose to follow one path of codegen by assuming otu.IsLow() is true.
+ last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
last_visited_latency_ = kArmIntegerOpLatency;
}
+void SchedulingLatencyVisitorARM::HandleGenerateEqualLong(HCondition* cond) {
+ DCHECK_EQ(cond->GetLeft()->GetType(), Primitive::kPrimLong);
+
+ IfCondition condition = cond->GetCondition();
+
+ last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+
+ if (condition == kCondNE) {
+ // Orrs, IT, Mov
+ last_visited_internal_latency_ += 3 * kArmIntegerOpLatency;
+ } else {
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ HandleGenerateConditionWithZero(condition);
+ }
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateLongComparesAndJumps() {
+ last_visited_internal_latency_ += 4 * kArmIntegerOpLatency;
+ last_visited_internal_latency_ += kArmBranchLatency;
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateConditionLong(HCondition* cond) {
+ DCHECK_EQ(cond->GetLeft()->GetType(), Primitive::kPrimLong);
+
+ IfCondition condition = cond->GetCondition();
+ HInstruction* right = cond->InputAt(1);
+
+ if (right->IsConstant()) {
+ // Comparisons against 0 are common enough, so codegen has special handling for them.
+ if (Uint64ConstantFrom(right) == 0) {
+ switch (condition) {
+ case kCondNE:
+ case kCondA:
+ case kCondEQ:
+ case kCondBE:
+ // Orr
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ HandleGenerateConditionWithZero(condition);
+ return;
+ case kCondLT:
+ case kCondGE:
+ FALLTHROUGH_INTENDED;
+ case kCondAE:
+ case kCondB:
+ HandleGenerateConditionWithZero(condition);
+ return;
+ case kCondLE:
+ case kCondGT:
+ default:
+ break;
+ }
+ }
+ }
+
+ if ((condition == kCondEQ || condition == kCondNE) &&
+ !CanGenerateTest(cond)) {
+ HandleGenerateEqualLong(cond);
+ return;
+ }
+
+ if (CanGenerateTest(cond)) {
+ HandleGenerateConditionGeneric(cond);
+ return;
+ }
+
+ HandleGenerateLongComparesAndJumps();
+
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ last_visited_latency_ = kArmBranchLatency;;
+}
+
+void SchedulingLatencyVisitorARM::HandleGenerateConditionIntegralOrNonPrimitive(HCondition* cond) {
+ const Primitive::Type type = cond->GetLeft()->GetType();
+
+ DCHECK(Primitive::IsIntegralType(type) || type == Primitive::kPrimNot) << type;
+
+ if (type == Primitive::kPrimLong) {
+ HandleGenerateConditionLong(cond);
+ return;
+ }
+
+ IfCondition condition = cond->GetCondition();
+ HInstruction* right = cond->InputAt(1);
+ int64_t value;
+
+ if (right->IsConstant()) {
+ value = Uint64ConstantFrom(right);
+
+ // Comparisons against 0 are common enough, so codegen has special handling for them.
+ if (value == 0) {
+ switch (condition) {
+ case kCondNE:
+ case kCondA:
+ case kCondEQ:
+ case kCondBE:
+ case kCondLT:
+ case kCondGE:
+ case kCondAE:
+ case kCondB:
+ HandleGenerateConditionWithZero(condition);
+ return;
+ case kCondLE:
+ case kCondGT:
+ default:
+ break;
+ }
+ }
+ }
+
+ if (condition == kCondEQ || condition == kCondNE) {
+ if (condition == kCondNE) {
+ // CMP, IT, MOV.ne
+ last_visited_internal_latency_ += 2 * kArmIntegerOpLatency;
+ last_visited_latency_ = kArmIntegerOpLatency;
+ } else {
+ last_visited_internal_latency_ += kArmIntegerOpLatency;
+ HandleGenerateConditionWithZero(condition);
+ }
+ return;
+ }
+
+ HandleGenerateConditionGeneric(cond);
+}
+
+void SchedulingLatencyVisitorARM::HandleCondition(HCondition* cond) {
+ if (cond->IsEmittedAtUseSite()) {
+ last_visited_latency_ = 0;
+ return;
+ }
+
+ const Primitive::Type type = cond->GetLeft()->GetType();
+
+ if (Primitive::IsFloatingPointType(type)) {
+ HandleGenerateConditionGeneric(cond);
+ return;
+ }
+
+ DCHECK(Primitive::IsIntegralType(type) || type == Primitive::kPrimNot) << type;
+
+ const IfCondition condition = cond->GetCondition();
+
+ if (type == Primitive::kPrimBoolean &&
+ cond->GetRight()->GetType() == Primitive::kPrimBoolean &&
+ (condition == kCondEQ || condition == kCondNE)) {
+ if (condition == kCondEQ) {
+ last_visited_internal_latency_ = kArmIntegerOpLatency;
+ }
+ last_visited_latency_ = kArmIntegerOpLatency;
+ return;
+ }
+
+ HandleGenerateConditionIntegralOrNonPrimitive(cond);
+}
+
+void SchedulingLatencyVisitorARM::VisitCondition(HCondition* instr) {
+ HandleCondition(instr);
+}
+
void SchedulingLatencyVisitorARM::VisitCompare(HCompare* instr) {
Primitive::Type type = instr->InputAt(0)->GetType();
switch (type) {
@@ -269,7 +593,6 @@
const HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
if (instruction->GetType() == Primitive::kPrimInt) {
- DCHECK(!HDataProcWithShifterOp::IsExtensionOp(op_kind));
HandleGenerateDataProcInstruction();
} else {
DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
diff --git a/compiler/optimizing/scheduler_arm.h b/compiler/optimizing/scheduler_arm.h
index a9f2295..fe274d2 100644
--- a/compiler/optimizing/scheduler_arm.h
+++ b/compiler/optimizing/scheduler_arm.h
@@ -109,6 +109,17 @@
#undef DECLARE_VISIT_INSTRUCTION
private:
+ bool CanGenerateTest(HCondition* cond);
+ void HandleGenerateConditionWithZero(IfCondition cond);
+ void HandleGenerateLongTestConstant(HCondition* cond);
+ void HandleGenerateLongTest(HCondition* cond);
+ void HandleGenerateLongComparesAndJumps();
+ void HandleGenerateTest(HCondition* cond);
+ void HandleGenerateConditionGeneric(HCondition* cond);
+ void HandleGenerateEqualLong(HCondition* cond);
+ void HandleGenerateConditionLong(HCondition* cond);
+ void HandleGenerateConditionIntegralOrNonPrimitive(HCondition* cond);
+ void HandleCondition(HCondition* instr);
void HandleBinaryOperationLantencies(HBinaryOperation* instr);
void HandleBitwiseOperationLantencies(HBinaryOperation* instr);
void HandleShiftLatencies(HBinaryOperation* instr);
diff --git a/compiler/utils/mips/assembler_mips.cc b/compiler/utils/mips/assembler_mips.cc
index c581f1c..24e3450 100644
--- a/compiler/utils/mips/assembler_mips.cc
+++ b/compiler/utils/mips/assembler_mips.cc
@@ -828,6 +828,22 @@
DsFsmInstrRrr(EmitI(0xf, rs, rt, imm16), rt, rt, rs);
}
+void MipsAssembler::AddUpper(Register rt, Register rs, uint16_t imm16, Register tmp) {
+ bool increment = (rs == rt);
+ if (increment) {
+ CHECK_NE(rs, tmp);
+ }
+ if (IsR6()) {
+ Aui(rt, rs, imm16);
+ } else if (increment) {
+ Lui(tmp, imm16);
+ Addu(rt, rs, tmp);
+ } else {
+ Lui(rt, imm16);
+ Addu(rt, rs, rt);
+ }
+}
+
void MipsAssembler::Sync(uint32_t stype) {
DsFsmInstrNop(EmitR(0, ZERO, ZERO, ZERO, stype & 0x1f, 0xf));
}
diff --git a/compiler/utils/mips/assembler_mips.h b/compiler/utils/mips/assembler_mips.h
index 33803bb..e42bb3f 100644
--- a/compiler/utils/mips/assembler_mips.h
+++ b/compiler/utils/mips/assembler_mips.h
@@ -280,6 +280,7 @@
void Lwpc(Register rs, uint32_t imm19); // R6
void Lui(Register rt, uint16_t imm16);
void Aui(Register rt, Register rs, uint16_t imm16); // R6
+ void AddUpper(Register rt, Register rs, uint16_t imm16, Register tmp = AT);
void Sync(uint32_t stype);
void Mfhi(Register rd); // R2
void Mflo(Register rd); // R2
diff --git a/compiler/utils/mips64/assembler_mips64.cc b/compiler/utils/mips64/assembler_mips64.cc
index 24900a7..9039854 100644
--- a/compiler/utils/mips64/assembler_mips64.cc
+++ b/compiler/utils/mips64/assembler_mips64.cc
@@ -795,6 +795,10 @@
EmitFI(0x11, 0xD, ft, imm16);
}
+void Mips64Assembler::Beqz(GpuRegister rt, uint16_t imm16) {
+ EmitI(0x4, ZERO, rt, imm16);
+}
+
void Mips64Assembler::EmitBcondc(BranchCondition cond,
GpuRegister rs,
GpuRegister rt,
diff --git a/compiler/utils/mips64/assembler_mips64.h b/compiler/utils/mips64/assembler_mips64.h
index 773db9b..5e88033 100644
--- a/compiler/utils/mips64/assembler_mips64.h
+++ b/compiler/utils/mips64/assembler_mips64.h
@@ -563,6 +563,7 @@
void Bnezc(GpuRegister rs, uint32_t imm21);
void Bc1eqz(FpuRegister ft, uint16_t imm16);
void Bc1nez(FpuRegister ft, uint16_t imm16);
+ void Beqz(GpuRegister rt, uint16_t imm16);
void AddS(FpuRegister fd, FpuRegister fs, FpuRegister ft);
void SubS(FpuRegister fd, FpuRegister fs, FpuRegister ft);
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 686da21..72e2a6c 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -155,13 +155,14 @@
ArtMethod* method = nullptr;
while (it.HasNextDirectMethod()) {
- ArtMethod* resolved_method = class_linker_->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
- *primary_dex_file_,
- it.GetMemberIndex(),
- dex_cache_handle,
- class_loader_handle,
- nullptr,
- it.GetMethodInvokeType(*class_def));
+ ArtMethod* resolved_method =
+ class_linker_->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
+ *primary_dex_file_,
+ it.GetMemberIndex(),
+ dex_cache_handle,
+ class_loader_handle,
+ nullptr,
+ it.GetMethodInvokeType(*class_def));
CHECK(resolved_method != nullptr);
if (method_name == resolved_method->GetName()) {
method = resolved_method;
@@ -369,18 +370,14 @@
// Iterates over all method resolution records, finds an entry which matches
// the given field kind+class+name+signature and tests its properties.
- bool HasMethod(const std::string& expected_kind,
- const std::string& expected_klass,
+ bool HasMethod(const std::string& expected_klass,
const std::string& expected_name,
const std::string& expected_signature,
bool expected_resolved,
const std::string& expected_access_flags = "",
const std::string& expected_decl_klass = "") {
for (auto& dex_dep : verifier_deps_->dex_deps_) {
- auto& storage = (expected_kind == "direct") ? dex_dep.second->direct_methods_
- : (expected_kind == "virtual") ? dex_dep.second->virtual_methods_
- : dex_dep.second->interface_methods_;
- for (auto& entry : storage) {
+ for (const VerifierDeps::MethodResolution& entry : dex_dep.second->methods_) {
if (expected_resolved != entry.IsResolved()) {
continue;
}
@@ -441,9 +438,7 @@
has_assignability |= !entry.second->unassignable_types_.empty();
has_classes |= !entry.second->classes_.empty();
has_fields |= !entry.second->fields_.empty();
- has_methods |= !entry.second->direct_methods_.empty();
- has_methods |= !entry.second->virtual_methods_.empty();
- has_methods |= !entry.second->interface_methods_.empty();
+ has_methods |= !entry.second->methods_.empty();
has_unverified_classes |= !entry.second->unverified_classes_.empty();
}
@@ -455,18 +450,6 @@
has_unverified_classes;
}
- static std::set<VerifierDeps::MethodResolution>* GetMethods(
- VerifierDeps::DexFileDeps* deps, MethodResolutionKind resolution_kind) {
- if (resolution_kind == kDirectMethodResolution) {
- return &deps->direct_methods_;
- } else if (resolution_kind == kVirtualMethodResolution) {
- return &deps->virtual_methods_;
- } else {
- DCHECK_EQ(resolution_kind, kInterfaceMethodResolution);
- return &deps->interface_methods_;
- }
- }
-
std::unique_ptr<verifier::VerifierDeps> verifier_deps_;
std::vector<const DexFile*> dex_files_;
const DexFile* primary_dex_file_;
@@ -604,11 +587,10 @@
ASSERT_TRUE(VerifyMethod("InvokeArgumentType"));
ASSERT_TRUE(HasClass("Ljava/text/SimpleDateFormat;", true, "public"));
ASSERT_TRUE(HasClass("Ljava/util/SimpleTimeZone;", true, "public"));
- ASSERT_TRUE(HasMethod("virtual",
- "Ljava/text/SimpleDateFormat;",
+ ASSERT_TRUE(HasMethod("Ljava/text/SimpleDateFormat;",
"setTimeZone",
"(Ljava/util/TimeZone;)V",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/text/DateFormat;"));
ASSERT_TRUE(HasAssignable("Ljava/util/TimeZone;", "Ljava/util/SimpleTimeZone;", true));
@@ -840,11 +822,10 @@
TEST_F(VerifierDepsTest, InvokeStatic_Resolved_DeclaredInReferenced) {
ASSERT_TRUE(VerifyMethod("InvokeStatic_Resolved_DeclaredInReferenced"));
ASSERT_TRUE(HasClass("Ljava/net/Socket;", true, "public"));
- ASSERT_TRUE(HasMethod("direct",
- "Ljava/net/Socket;",
+ ASSERT_TRUE(HasMethod("Ljava/net/Socket;",
"setSocketImplFactory",
"(Ljava/net/SocketImplFactory;)V",
- true,
+ /* expect_resolved */ true,
"public static",
"Ljava/net/Socket;"));
}
@@ -852,22 +833,20 @@
TEST_F(VerifierDepsTest, InvokeStatic_Resolved_DeclaredInSuperclass1) {
ASSERT_TRUE(VerifyMethod("InvokeStatic_Resolved_DeclaredInSuperclass1"));
ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public"));
- ASSERT_TRUE(HasMethod("direct",
- "Ljavax/net/ssl/SSLSocket;",
+ ASSERT_TRUE(HasMethod("Ljavax/net/ssl/SSLSocket;",
"setSocketImplFactory",
"(Ljava/net/SocketImplFactory;)V",
- true,
+ /* expect_resolved */ true,
"public static",
"Ljava/net/Socket;"));
}
TEST_F(VerifierDepsTest, InvokeStatic_Resolved_DeclaredInSuperclass2) {
ASSERT_TRUE(VerifyMethod("InvokeStatic_Resolved_DeclaredInSuperclass2"));
- ASSERT_TRUE(HasMethod("direct",
- "LMySSLSocket;",
+ ASSERT_TRUE(HasMethod("LMySSLSocket;",
"setSocketImplFactory",
"(Ljava/net/SocketImplFactory;)V",
- true,
+ /* expect_resolved */ true,
"public static",
"Ljava/net/Socket;"));
}
@@ -875,11 +854,10 @@
TEST_F(VerifierDepsTest, InvokeStatic_DeclaredInInterface1) {
ASSERT_TRUE(VerifyMethod("InvokeStatic_DeclaredInInterface1"));
ASSERT_TRUE(HasClass("Ljava/util/Map$Entry;", true, "public interface"));
- ASSERT_TRUE(HasMethod("direct",
- "Ljava/util/Map$Entry;",
+ ASSERT_TRUE(HasMethod("Ljava/util/Map$Entry;",
"comparingByKey",
"()Ljava/util/Comparator;",
- true,
+ /* expect_resolved */ true,
"public static",
"Ljava/util/Map$Entry;"));
}
@@ -887,68 +865,85 @@
TEST_F(VerifierDepsTest, InvokeStatic_DeclaredInInterface2) {
ASSERT_FALSE(VerifyMethod("InvokeStatic_DeclaredInInterface2"));
ASSERT_TRUE(HasClass("Ljava/util/AbstractMap$SimpleEntry;", true, "public"));
- ASSERT_TRUE(HasMethod("direct",
- "Ljava/util/AbstractMap$SimpleEntry;",
+ ASSERT_TRUE(HasMethod("Ljava/util/AbstractMap$SimpleEntry;",
"comparingByKey",
"()Ljava/util/Comparator;",
- false));
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeStatic_Unresolved1) {
ASSERT_FALSE(VerifyMethod("InvokeStatic_Unresolved1"));
ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public"));
- ASSERT_TRUE(HasMethod("direct", "Ljavax/net/ssl/SSLSocket;", "x", "()V", false));
+ ASSERT_TRUE(HasMethod("Ljavax/net/ssl/SSLSocket;",
+ "x",
+ "()V",
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeStatic_Unresolved2) {
ASSERT_FALSE(VerifyMethod("InvokeStatic_Unresolved2"));
- ASSERT_TRUE(HasMethod("direct", "LMySSLSocket;", "x", "()V", false));
+ ASSERT_TRUE(HasMethod("LMySSLSocket;",
+ "x",
+ "()V",
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeDirect_Resolved_DeclaredInReferenced) {
ASSERT_TRUE(VerifyMethod("InvokeDirect_Resolved_DeclaredInReferenced"));
ASSERT_TRUE(HasClass("Ljava/net/Socket;", true, "public"));
- ASSERT_TRUE(HasMethod(
- "direct", "Ljava/net/Socket;", "<init>", "()V", true, "public", "Ljava/net/Socket;"));
+ ASSERT_TRUE(HasMethod("Ljava/net/Socket;",
+ "<init>",
+ "()V",
+ /* expect_resolved */ true,
+ "public",
+ "Ljava/net/Socket;"));
}
TEST_F(VerifierDepsTest, InvokeDirect_Resolved_DeclaredInSuperclass1) {
ASSERT_FALSE(VerifyMethod("InvokeDirect_Resolved_DeclaredInSuperclass1"));
ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public"));
- ASSERT_TRUE(HasMethod("direct",
- "Ljavax/net/ssl/SSLSocket;",
+ ASSERT_TRUE(HasMethod("Ljavax/net/ssl/SSLSocket;",
"checkOldImpl",
"()V",
- true,
+ /* expect_resolved */ true,
"private",
"Ljava/net/Socket;"));
}
TEST_F(VerifierDepsTest, InvokeDirect_Resolved_DeclaredInSuperclass2) {
ASSERT_FALSE(VerifyMethod("InvokeDirect_Resolved_DeclaredInSuperclass2"));
- ASSERT_TRUE(HasMethod(
- "direct", "LMySSLSocket;", "checkOldImpl", "()V", true, "private", "Ljava/net/Socket;"));
+ ASSERT_TRUE(HasMethod("LMySSLSocket;",
+ "checkOldImpl",
+ "()V",
+ /* expect_resolved */ true,
+ "private",
+ "Ljava/net/Socket;"));
}
TEST_F(VerifierDepsTest, InvokeDirect_Unresolved1) {
ASSERT_FALSE(VerifyMethod("InvokeDirect_Unresolved1"));
ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public"));
- ASSERT_TRUE(HasMethod("direct", "Ljavax/net/ssl/SSLSocket;", "x", "()V", false));
+ ASSERT_TRUE(HasMethod("Ljavax/net/ssl/SSLSocket;",
+ "x",
+ "()V",
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeDirect_Unresolved2) {
ASSERT_FALSE(VerifyMethod("InvokeDirect_Unresolved2"));
- ASSERT_TRUE(HasMethod("direct", "LMySSLSocket;", "x", "()V", false));
+ ASSERT_TRUE(HasMethod("LMySSLSocket;",
+ "x",
+ "()V",
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInReferenced) {
ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInReferenced"));
ASSERT_TRUE(HasClass("Ljava/lang/Throwable;", true, "public"));
- ASSERT_TRUE(HasMethod("virtual",
- "Ljava/lang/Throwable;",
+ ASSERT_TRUE(HasMethod("Ljava/lang/Throwable;",
"getMessage",
"()Ljava/lang/String;",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/lang/Throwable;"));
// Type dependency on `this` argument.
@@ -958,11 +953,10 @@
TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInSuperclass1) {
ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInSuperclass1"));
ASSERT_TRUE(HasClass("Ljava/io/InterruptedIOException;", true, "public"));
- ASSERT_TRUE(HasMethod("virtual",
- "Ljava/io/InterruptedIOException;",
+ ASSERT_TRUE(HasMethod("Ljava/io/InterruptedIOException;",
"getMessage",
"()Ljava/lang/String;",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/lang/Throwable;"));
// Type dependency on `this` argument.
@@ -971,22 +965,20 @@
TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInSuperclass2) {
ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInSuperclass2"));
- ASSERT_TRUE(HasMethod("virtual",
- "LMySocketTimeoutException;",
+ ASSERT_TRUE(HasMethod("LMySocketTimeoutException;",
"getMessage",
"()Ljava/lang/String;",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/lang/Throwable;"));
}
TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInSuperinterface) {
ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInSuperinterface"));
- ASSERT_TRUE(HasMethod("virtual",
- "LMyThreadSet;",
+ ASSERT_TRUE(HasMethod("LMyThreadSet;",
"size",
"()I",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/util/Set;"));
}
@@ -994,61 +986,59 @@
TEST_F(VerifierDepsTest, InvokeVirtual_Unresolved1) {
ASSERT_FALSE(VerifyMethod("InvokeVirtual_Unresolved1"));
ASSERT_TRUE(HasClass("Ljava/io/InterruptedIOException;", true, "public"));
- ASSERT_TRUE(HasMethod("virtual", "Ljava/io/InterruptedIOException;", "x", "()V", false));
+ ASSERT_TRUE(HasMethod("Ljava/io/InterruptedIOException;",
+ "x",
+ "()V",
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeVirtual_Unresolved2) {
ASSERT_FALSE(VerifyMethod("InvokeVirtual_Unresolved2"));
- ASSERT_TRUE(HasMethod("virtual", "LMySocketTimeoutException;", "x", "()V", false));
-}
-
-TEST_F(VerifierDepsTest, InvokeVirtual_ActuallyDirect) {
- ASSERT_FALSE(VerifyMethod("InvokeVirtual_ActuallyDirect"));
- ASSERT_TRUE(HasMethod("virtual", "LMyThread;", "activeCount", "()I", false));
- ASSERT_TRUE(HasMethod("direct",
- "LMyThread;",
- "activeCount",
- "()I",
- true,
- "public static",
- "Ljava/lang/Thread;"));
+ ASSERT_TRUE(HasMethod("LMySocketTimeoutException;",
+ "x",
+ "()V",
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInReferenced) {
ASSERT_TRUE(VerifyMethod("InvokeInterface_Resolved_DeclaredInReferenced"));
ASSERT_TRUE(HasClass("Ljava/lang/Runnable;", true, "public interface"));
- ASSERT_TRUE(HasMethod("interface",
- "Ljava/lang/Runnable;",
+ ASSERT_TRUE(HasMethod("Ljava/lang/Runnable;",
"run",
"()V",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/lang/Runnable;"));
}
TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInSuperclass) {
ASSERT_FALSE(VerifyMethod("InvokeInterface_Resolved_DeclaredInSuperclass"));
- ASSERT_TRUE(HasMethod("interface", "LMyThread;", "join", "()V", false));
+ // TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
+ ASSERT_TRUE(HasMethod("LMyThread;",
+ "join",
+ "()V",
+ /* expect_resolved */ true,
+ "public",
+ "Ljava/lang/Thread;"));
}
TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInSuperinterface1) {
ASSERT_FALSE(VerifyMethod("InvokeInterface_Resolved_DeclaredInSuperinterface1"));
- ASSERT_TRUE(HasMethod("interface",
- "LMyThreadSet;",
+ // TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
+ ASSERT_TRUE(HasMethod("LMyThreadSet;",
"run",
"()V",
- true,
+ /* expect_resolved */ true,
"public",
- "Ljava/lang/Runnable;"));
+ "Ljava/lang/Thread;"));
}
TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInSuperinterface2) {
ASSERT_FALSE(VerifyMethod("InvokeInterface_Resolved_DeclaredInSuperinterface2"));
- ASSERT_TRUE(HasMethod("interface",
- "LMyThreadSet;",
+ ASSERT_TRUE(HasMethod("LMyThreadSet;",
"isEmpty",
"()Z",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/util/Set;"));
}
@@ -1056,23 +1046,25 @@
TEST_F(VerifierDepsTest, InvokeInterface_Unresolved1) {
ASSERT_FALSE(VerifyMethod("InvokeInterface_Unresolved1"));
ASSERT_TRUE(HasClass("Ljava/lang/Runnable;", true, "public interface"));
- ASSERT_TRUE(HasMethod("interface", "Ljava/lang/Runnable;", "x", "()V", false));
+ ASSERT_TRUE(HasMethod("Ljava/lang/Runnable;",
+ "x",
+ "()V",
+ /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeInterface_Unresolved2) {
ASSERT_FALSE(VerifyMethod("InvokeInterface_Unresolved2"));
- ASSERT_TRUE(HasMethod("interface", "LMyThreadSet;", "x", "()V", false));
+ ASSERT_TRUE(HasMethod("LMyThreadSet;", "x", "()V", /* expect_resolved */ false));
}
TEST_F(VerifierDepsTest, InvokeSuper_ThisAssignable) {
ASSERT_TRUE(VerifyMethod("InvokeSuper_ThisAssignable"));
ASSERT_TRUE(HasClass("Ljava/lang/Runnable;", true, "public interface"));
ASSERT_TRUE(HasAssignable("Ljava/lang/Runnable;", "Ljava/lang/Thread;", true));
- ASSERT_TRUE(HasMethod("interface",
- "Ljava/lang/Runnable;",
+ ASSERT_TRUE(HasMethod("Ljava/lang/Runnable;",
"run",
"()V",
- true,
+ /* expect_resolved */ true,
"public",
"Ljava/lang/Runnable;"));
}
@@ -1081,8 +1073,10 @@
ASSERT_FALSE(VerifyMethod("InvokeSuper_ThisNotAssignable"));
ASSERT_TRUE(HasClass("Ljava/lang/Integer;", true, "public"));
ASSERT_TRUE(HasAssignable("Ljava/lang/Integer;", "Ljava/lang/Thread;", false));
- ASSERT_TRUE(HasMethod(
- "virtual", "Ljava/lang/Integer;", "intValue", "()I", true, "public", "Ljava/lang/Integer;"));
+ ASSERT_TRUE(HasMethod("Ljava/lang/Integer;",
+ "intValue", "()I",
+ /* expect_resolved */ true,
+ "public", "Ljava/lang/Integer;"));
}
TEST_F(VerifierDepsTest, ArgumentType_ResolvedReferenceArray) {
@@ -1150,18 +1144,6 @@
ASSERT_TRUE(HasUnverifiedClass("LMyClassWithNoSuperButFailures;"));
}
-// Returns the next resolution kind in the enum.
-static MethodResolutionKind GetNextResolutionKind(MethodResolutionKind resolution_kind) {
- if (resolution_kind == kDirectMethodResolution) {
- return kVirtualMethodResolution;
- } else if (resolution_kind == kVirtualMethodResolution) {
- return kInterfaceMethodResolution;
- } else {
- DCHECK_EQ(resolution_kind, kInterfaceMethodResolution);
- return kDirectMethodResolution;
- }
-}
-
TEST_F(VerifierDepsTest, VerifyDeps) {
VerifyDexFile();
@@ -1338,131 +1320,82 @@
}
// Mess up with methods.
- for (MethodResolutionKind resolution_kind :
- { kDirectMethodResolution, kVirtualMethodResolution, kInterfaceMethodResolution }) {
- {
- VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
- VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
- bool found = false;
- std::set<VerifierDeps::MethodResolution>* methods = GetMethods(deps, resolution_kind);
- for (const auto& entry : *methods) {
- if (entry.IsResolved()) {
- methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
- VerifierDeps::kUnresolvedMarker,
- entry.GetDeclaringClassIndex()));
- found = true;
- break;
- }
+ {
+ VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
+ VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
+ bool found = false;
+ std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
+ for (const auto& entry : *methods) {
+ if (entry.IsResolved()) {
+ methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
+ VerifierDeps::kUnresolvedMarker,
+ entry.GetDeclaringClassIndex()));
+ found = true;
+ break;
}
- ASSERT_TRUE(found);
- new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
- ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
}
+ ASSERT_TRUE(found);
+ new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
+ ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
+ }
- {
- VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
- VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
- bool found = false;
- std::set<VerifierDeps::MethodResolution>* methods = GetMethods(deps, resolution_kind);
- for (const auto& entry : *methods) {
- if (!entry.IsResolved()) {
- constexpr dex::StringIndex kStringIndexZero(0); // We know there is a class there.
- methods->insert(VerifierDeps::MethodResolution(0 /* we know there is a method there */,
- VerifierDeps::kUnresolvedMarker - 1,
- kStringIndexZero));
- found = true;
- break;
- }
+ {
+ VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
+ VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
+ bool found = false;
+ std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
+ for (const auto& entry : *methods) {
+ if (!entry.IsResolved()) {
+ constexpr dex::StringIndex kStringIndexZero(0); // We know there is a class there.
+ methods->insert(VerifierDeps::MethodResolution(0 /* we know there is a method there */,
+ VerifierDeps::kUnresolvedMarker - 1,
+ kStringIndexZero));
+ found = true;
+ break;
}
- ASSERT_TRUE(found);
- new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
- ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
}
+ ASSERT_TRUE(found);
+ new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
+ ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
+ }
- {
- VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
- VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
- bool found = false;
- std::set<VerifierDeps::MethodResolution>* methods = GetMethods(deps, resolution_kind);
- for (const auto& entry : *methods) {
- if (entry.IsResolved()) {
- methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
- entry.GetAccessFlags() - 1,
- entry.GetDeclaringClassIndex()));
- found = true;
- break;
- }
+ {
+ VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
+ VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
+ bool found = false;
+ std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
+ for (const auto& entry : *methods) {
+ if (entry.IsResolved()) {
+ methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
+ entry.GetAccessFlags() - 1,
+ entry.GetDeclaringClassIndex()));
+ found = true;
+ break;
}
- ASSERT_TRUE(found);
- new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
- ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
}
+ ASSERT_TRUE(found);
+ new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
+ ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
+ }
- {
- VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
- VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
- bool found = false;
- std::set<VerifierDeps::MethodResolution>* methods = GetMethods(deps, resolution_kind);
- for (const auto& entry : *methods) {
- constexpr dex::StringIndex kNewTypeIndex(0);
- if (entry.IsResolved() && entry.GetDeclaringClassIndex() != kNewTypeIndex) {
- methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
- entry.GetAccessFlags(),
- kNewTypeIndex));
- found = true;
- break;
- }
+ {
+ VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
+ VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
+ bool found = false;
+ std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
+ for (const auto& entry : *methods) {
+ constexpr dex::StringIndex kNewTypeIndex(0);
+ if (entry.IsResolved() && entry.GetDeclaringClassIndex() != kNewTypeIndex) {
+ methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
+ entry.GetAccessFlags(),
+ kNewTypeIndex));
+ found = true;
+ break;
}
- ASSERT_TRUE(found);
- new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
- ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
}
-
- // The two tests below make sure that fiddling with the method kind
- // (static, virtual, interface) is detected by `ValidateDependencies`.
-
- // An interface method lookup can succeed with a virtual method lookup on the same class.
- // That's OK, as we only want to make sure there is a method being defined with the right
- // flags. Therefore, polluting the interface methods with virtual methods does not have
- // to fail verification.
- if (resolution_kind != kVirtualMethodResolution) {
- VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
- VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
- bool found = false;
- std::set<VerifierDeps::MethodResolution>* methods = GetMethods(deps, resolution_kind);
- for (const auto& entry : *methods) {
- if (entry.IsResolved()) {
- GetMethods(deps, GetNextResolutionKind(resolution_kind))->insert(
- VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
- entry.GetAccessFlags(),
- entry.GetDeclaringClassIndex()));
- found = true;
- }
- }
- ASSERT_TRUE(found);
- new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
- ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
- }
-
- // See comment above that applies the same way.
- if (resolution_kind != kInterfaceMethodResolution) {
- VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
- VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
- bool found = false;
- std::set<VerifierDeps::MethodResolution>* methods = GetMethods(deps, resolution_kind);
- for (const auto& entry : *methods) {
- if (entry.IsResolved()) {
- GetMethods(deps, GetNextResolutionKind(GetNextResolutionKind(resolution_kind)))->insert(
- VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
- entry.GetAccessFlags(),
- entry.GetDeclaringClassIndex()));
- found = true;
- }
- }
- ASSERT_TRUE(found);
- new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
- ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
- }
+ ASSERT_TRUE(found);
+ new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
+ ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
}
}
diff --git a/dalvikvm/dalvikvm.cc b/dalvikvm/dalvikvm.cc
index dcb49a0..e735e2f 100644
--- a/dalvikvm/dalvikvm.cc
+++ b/dalvikvm/dalvikvm.cc
@@ -21,8 +21,8 @@
#include <algorithm>
#include <memory>
-#include "JniInvocation.h"
#include "jni.h"
+#include "nativehelper/JniInvocation.h"
#include "nativehelper/ScopedLocalRef.h"
#include "nativehelper/toStringArray.h"
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 0ef496f..3cc41a6 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1497,10 +1497,9 @@
Runtime* runtime = Runtime::Current();
CHECK(runtime != nullptr);
// Filter out class path classes since we don't want to include these in the image.
- std::set<DexCacheResolvedClasses> resolved_classes(
- profile_compilation_info_->GetResolvedClasses(dex_files_));
- image_classes_.reset(new std::unordered_set<std::string>(
- runtime->GetClassLinker()->GetClassDescriptorsForResolvedClasses(resolved_classes)));
+ image_classes_.reset(
+ new std::unordered_set<std::string>(
+ profile_compilation_info_->GetClassDescriptors(dex_files_)));
VLOG(compiler) << "Loaded " << image_classes_->size()
<< " image class descriptors from profile";
if (VLOG_IS_ON(compiler)) {
@@ -1520,18 +1519,15 @@
return dex2oat::ReturnCode::kOther;
}
- if (CompilerFilter::IsAnyCompilationEnabled(compiler_options_->GetCompilerFilter())) {
- // Only modes with compilation require verification results.
- verification_results_.reset(new VerificationResults(compiler_options_.get()));
- }
+ // Verification results are null since we don't know if we will need them yet as the compler
+ // filter may change.
callbacks_.reset(new QuickCompilerCallbacks(
- verification_results_.get(),
IsBootImage() ?
CompilerCallbacks::CallbackMode::kCompileBootImage :
CompilerCallbacks::CallbackMode::kCompileApp));
RuntimeArgumentMap runtime_options;
- if (!PrepareRuntimeOptions(&runtime_options)) {
+ if (!PrepareRuntimeOptions(&runtime_options, callbacks_.get())) {
return dex2oat::ReturnCode::kOther;
}
@@ -1662,6 +1658,28 @@
dex_files_ = MakeNonOwningPointerVector(opened_dex_files_);
+ // If we need to downgrade the compiler-filter for size reasons.
+ if (!IsBootImage() && IsVeryLarge(dex_files_)) {
+ if (!CompilerFilter::IsAsGoodAs(kLargeAppFilter, compiler_options_->GetCompilerFilter())) {
+ LOG(INFO) << "Very large app, downgrading to verify.";
+ // Note: this change won't be reflected in the key-value store, as that had to be
+ // finalized before loading the dex files. This setup is currently required
+ // to get the size from the DexFile objects.
+ // TODO: refactor. b/29790079
+ compiler_options_->SetCompilerFilter(kLargeAppFilter);
+ }
+ }
+
+ if (CompilerFilter::IsAnyCompilationEnabled(compiler_options_->GetCompilerFilter())) {
+ // Only modes with compilation require verification results, do this here instead of when we
+ // create the compilation callbacks since the compilation mode may have been changed by the
+ // very large app logic.
+ // Avoiding setting the verification results saves RAM by not adding the dex files later in
+ // the function.
+ verification_results_.reset(new VerificationResults(compiler_options_.get()));
+ callbacks_->SetVerificationResults(verification_results_.get());
+ }
+
// We had to postpone the swap decision till now, as this is the point when we actually
// know about the dex files we're going to use.
@@ -1678,19 +1696,6 @@
}
}
// Note that dex2oat won't close the swap_fd_. The compiler driver's swap space will do that.
-
- // If we need to downgrade the compiler-filter for size reasons, do that check now.
- if (!IsBootImage() && IsVeryLarge(dex_files_)) {
- if (!CompilerFilter::IsAsGoodAs(kLargeAppFilter, compiler_options_->GetCompilerFilter())) {
- LOG(INFO) << "Very large app, downgrading to verify.";
- // Note: this change won't be reflected in the key-value store, as that had to be
- // finalized before loading the dex files. This setup is currently required
- // to get the size from the DexFile objects.
- // TODO: refactor. b/29790079
- compiler_options_->SetCompilerFilter(kLargeAppFilter);
- }
- }
-
if (IsBootImage()) {
// For boot image, pass opened dex files to the Runtime::Create().
// Note: Runtime acquires ownership of these dex files.
@@ -2456,7 +2461,8 @@
}
}
- bool PrepareRuntimeOptions(RuntimeArgumentMap* runtime_options) {
+ bool PrepareRuntimeOptions(RuntimeArgumentMap* runtime_options,
+ QuickCompilerCallbacks* callbacks) {
RuntimeOptions raw_options;
if (boot_image_filename_.empty()) {
std::string boot_class_path = "-Xbootclasspath:";
@@ -2474,7 +2480,7 @@
raw_options.push_back(std::make_pair(runtime_args_[i], nullptr));
}
- raw_options.push_back(std::make_pair("compilercallbacks", callbacks_.get()));
+ raw_options.push_back(std::make_pair("compilercallbacks", callbacks));
raw_options.push_back(
std::make_pair("imageinstructionset", GetInstructionSetString(instruction_set_)));
@@ -2546,7 +2552,6 @@
runtime_->SetCalleeSaveMethod(runtime_->CreateCalleeSaveMethod(), type);
}
}
- runtime_->GetClassLinker()->FixupDexCaches(runtime_->GetResolutionMethod());
// Initialize maps for unstarted runtime. This needs to be here, as running clinits needs this
// set up.
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index e2c159a..fc72bbd 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -97,6 +97,9 @@
UsageError(" --android-data=<directory>: optional, the directory which should be used as");
UsageError(" android-data. By default ANDROID_DATA env variable is used.");
UsageError("");
+ UsageError(" --downgrade: optional, if the purpose of dexopt is to downgrade the dex file");
+ UsageError(" By default, dexopt considers upgrade case.");
+ UsageError("");
UsageError("Return code:");
UsageError(" To make it easier to integrate with the internal tools this command will make");
UsageError(" available its result (dexoptNeeded) as the exit/return code. i.e. it will not");
@@ -121,7 +124,9 @@
class DexoptAnalyzer FINAL {
public:
- DexoptAnalyzer() : assume_profile_changed_(false) {}
+ DexoptAnalyzer() :
+ assume_profile_changed_(false),
+ downgrade_(false) {}
void ParseArgs(int argc, char **argv) {
original_argc = argc;
@@ -160,9 +165,9 @@
// compute dalvik-cache folder). This is mostly used in tests.
std::string new_android_data = option.substr(strlen("--android-data=")).ToString();
setenv("ANDROID_DATA", new_android_data.c_str(), 1);
- } else {
- Usage("Unknown argument '%s'", option.data());
- }
+ } else if (option.starts_with("--downgrade")) {
+ downgrade_ = true;
+ } else { Usage("Unknown argument '%s'", option.data()); }
}
if (image_.empty()) {
@@ -225,7 +230,7 @@
return kNoDexOptNeeded;
}
int dexoptNeeded = oat_file_assistant.GetDexOptNeeded(
- compiler_filter_, assume_profile_changed_);
+ compiler_filter_, assume_profile_changed_, downgrade_);
// Convert OatFileAssitant codes to dexoptanalyzer codes.
switch (dexoptNeeded) {
@@ -249,6 +254,7 @@
InstructionSet isa_;
CompilerFilter::Filter compiler_filter_;
bool assume_profile_changed_;
+ bool downgrade_;
std::string image_;
};
diff --git a/dexoptanalyzer/dexoptanalyzer_test.cc b/dexoptanalyzer/dexoptanalyzer_test.cc
index 1703ff4..1cbf546 100644
--- a/dexoptanalyzer/dexoptanalyzer_test.cc
+++ b/dexoptanalyzer/dexoptanalyzer_test.cc
@@ -71,12 +71,13 @@
// as the output of OatFileAssistant::GetDexOptNeeded.
void Verify(const std::string& dex_file,
CompilerFilter::Filter compiler_filter,
- bool assume_profile_changed = false) {
+ bool assume_profile_changed = false,
+ bool downgrade = false) {
int dexoptanalyzerResult = Analyze(dex_file, compiler_filter, assume_profile_changed);
dexoptanalyzerResult = DexoptanalyzerToOatFileAssistant(dexoptanalyzerResult);
OatFileAssistant oat_file_assistant(dex_file.c_str(), kRuntimeISA, /*load_executable*/ false);
int assistantResult = oat_file_assistant.GetDexOptNeeded(
- compiler_filter, assume_profile_changed);
+ compiler_filter, assume_profile_changed, downgrade);
EXPECT_EQ(assistantResult, dexoptanalyzerResult);
}
};
@@ -118,6 +119,16 @@
Verify(dex_location, CompilerFilter::kQuicken, true);
}
+TEST_F(DexoptAnalyzerTest, Downgrade) {
+ std::string dex_location = GetScratchDir() + "/Downgrade.jar";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOatForTest(dex_location.c_str(), CompilerFilter::kQuicken);
+
+ Verify(dex_location, CompilerFilter::kSpeedProfile, false, true);
+ Verify(dex_location, CompilerFilter::kQuicken, false, true);
+ Verify(dex_location, CompilerFilter::kVerify, false, true);
+}
+
// Case: We have a MultiDEX file and up-to-date OAT file for it.
TEST_F(DexoptAnalyzerTest, MultiDexOatUpToDate) {
std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar";
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index 2763c07..5d9e361 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -50,6 +50,803 @@
using android::base::StringPrintf;
+namespace {
+
+constexpr size_t kMaxAddressPrint = 5;
+
+enum class ProcessType {
+ kZygote,
+ kRemote
+};
+
+enum class RemoteProcesses {
+ kImageOnly,
+ kZygoteOnly,
+ kImageAndZygote
+};
+
+struct MappingData {
+ // The count of pages that are considered dirty by the OS.
+ size_t dirty_pages = 0;
+ // The count of pages that differ by at least one byte.
+ size_t different_pages = 0;
+ // The count of differing bytes.
+ size_t different_bytes = 0;
+ // The count of differing four-byte units.
+ size_t different_int32s = 0;
+ // The count of pages that have mapping count == 1.
+ size_t private_pages = 0;
+ // The count of private pages that are also dirty.
+ size_t private_dirty_pages = 0;
+ // The count of pages that are marked dirty but do not differ.
+ size_t false_dirty_pages = 0;
+ // Set of the local virtual page indices that are dirty.
+ std::set<size_t> dirty_page_set;
+};
+
+static std::string GetClassDescriptor(mirror::Class* klass)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ CHECK(klass != nullptr);
+
+ std::string descriptor;
+ const char* descriptor_str = klass->GetDescriptor(&descriptor /*out*/);
+
+ return std::string(descriptor_str);
+}
+
+static std::string PrettyFieldValue(ArtField* field, mirror::Object* object)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ std::ostringstream oss;
+ switch (field->GetTypeAsPrimitiveType()) {
+ case Primitive::kPrimNot: {
+ oss << object->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
+ field->GetOffset());
+ break;
+ }
+ case Primitive::kPrimBoolean: {
+ oss << static_cast<bool>(object->GetFieldBoolean<kVerifyNone>(field->GetOffset()));
+ break;
+ }
+ case Primitive::kPrimByte: {
+ oss << static_cast<int32_t>(object->GetFieldByte<kVerifyNone>(field->GetOffset()));
+ break;
+ }
+ case Primitive::kPrimChar: {
+ oss << object->GetFieldChar<kVerifyNone>(field->GetOffset());
+ break;
+ }
+ case Primitive::kPrimShort: {
+ oss << object->GetFieldShort<kVerifyNone>(field->GetOffset());
+ break;
+ }
+ case Primitive::kPrimInt: {
+ oss << object->GetField32<kVerifyNone>(field->GetOffset());
+ break;
+ }
+ case Primitive::kPrimLong: {
+ oss << object->GetField64<kVerifyNone>(field->GetOffset());
+ break;
+ }
+ case Primitive::kPrimFloat: {
+ oss << object->GetField32<kVerifyNone>(field->GetOffset());
+ break;
+ }
+ case Primitive::kPrimDouble: {
+ oss << object->GetField64<kVerifyNone>(field->GetOffset());
+ break;
+ }
+ case Primitive::kPrimVoid: {
+ oss << "void";
+ break;
+ }
+ }
+ return oss.str();
+}
+
+template <typename K, typename V, typename D>
+static std::vector<std::pair<V, K>> SortByValueDesc(
+ const std::map<K, D> map,
+ std::function<V(const D&)> value_mapper = [](const D& d) { return static_cast<V>(d); }) {
+ // Store value->key so that we can use the default sort from pair which
+ // sorts by value first and then key
+ std::vector<std::pair<V, K>> value_key_vector;
+
+ for (const auto& kv_pair : map) {
+ value_key_vector.push_back(std::make_pair(value_mapper(kv_pair.second), kv_pair.first));
+ }
+
+ // Sort in reverse (descending order)
+ std::sort(value_key_vector.rbegin(), value_key_vector.rend());
+ return value_key_vector;
+}
+
+// Fixup a remote pointer that we read from a foreign boot.art to point to our own memory.
+// Returned pointer will point to inside of remote_contents.
+template <typename T>
+static T* FixUpRemotePointer(T* remote_ptr,
+ std::vector<uint8_t>& remote_contents,
+ const backtrace_map_t& boot_map) {
+ if (remote_ptr == nullptr) {
+ return nullptr;
+ }
+
+ uintptr_t remote = reinterpret_cast<uintptr_t>(remote_ptr);
+
+ CHECK_LE(boot_map.start, remote);
+ CHECK_GT(boot_map.end, remote);
+
+ off_t boot_offset = remote - boot_map.start;
+
+ return reinterpret_cast<T*>(&remote_contents[boot_offset]);
+}
+
+template <typename T>
+static T* RemoteContentsPointerToLocal(T* remote_ptr,
+ std::vector<uint8_t>& remote_contents,
+ const ImageHeader& image_header) {
+ if (remote_ptr == nullptr) {
+ return nullptr;
+ }
+
+ uint8_t* remote = reinterpret_cast<uint8_t*>(remote_ptr);
+ ptrdiff_t boot_offset = remote - &remote_contents[0];
+
+ const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header) + boot_offset;
+
+ return reinterpret_cast<T*>(const_cast<uint8_t*>(local_ptr));
+}
+
+template <typename T> size_t EntrySize(T* entry);
+template<> size_t EntrySize(mirror::Object* object) REQUIRES_SHARED(Locks::mutator_lock_) {
+ return object->SizeOf();
+}
+template<> size_t EntrySize(ArtMethod* art_method) REQUIRES_SHARED(Locks::mutator_lock_) {
+ return sizeof(*art_method);
+}
+
+template <typename T>
+static bool EntriesDiffer(T* entry1, T* entry2) REQUIRES_SHARED(Locks::mutator_lock_) {
+ return memcmp(entry1, entry2, EntrySize(entry1)) != 0;
+}
+
+template <typename T>
+struct RegionCommon {
+ public:
+ RegionCommon(std::ostream* os,
+ std::vector<uint8_t>* remote_contents,
+ std::vector<uint8_t>* zygote_contents,
+ const backtrace_map_t& boot_map,
+ const ImageHeader& image_header) :
+ os_(*os),
+ remote_contents_(remote_contents),
+ zygote_contents_(zygote_contents),
+ boot_map_(boot_map),
+ image_header_(image_header),
+ different_entries_(0),
+ dirty_entry_bytes_(0),
+ false_dirty_entry_bytes_(0) {
+ CHECK(remote_contents != nullptr);
+ CHECK(zygote_contents != nullptr);
+ }
+
+ void DumpSamplesAndOffsetCount() {
+ os_ << " sample object addresses: ";
+ for (size_t i = 0; i < dirty_entries_.size() && i < kMaxAddressPrint; ++i) {
+ T* entry = dirty_entries_[i];
+ os_ << reinterpret_cast<void*>(entry) << ", ";
+ }
+ os_ << "\n";
+ os_ << " dirty byte +offset:count list = ";
+ std::vector<std::pair<size_t, off_t>> field_dirty_count_sorted =
+ SortByValueDesc<off_t, size_t, size_t>(field_dirty_count_);
+ for (const std::pair<size_t, off_t>& pair : field_dirty_count_sorted) {
+ off_t offset = pair.second;
+ size_t count = pair.first;
+ os_ << "+" << offset << ":" << count << ", ";
+ }
+ os_ << "\n";
+ }
+
+ size_t GetDifferentEntryCount() const { return different_entries_; }
+ size_t GetDirtyEntryBytes() const { return dirty_entry_bytes_; }
+ size_t GetFalseDirtyEntryCount() const { return false_dirty_entries_.size(); }
+ size_t GetFalseDirtyEntryBytes() const { return false_dirty_entry_bytes_; }
+ size_t GetZygoteDirtyEntryCount() const { return zygote_dirty_entries_.size(); }
+
+ protected:
+ bool IsEntryOnDirtyPage(T* entry, const std::set<size_t>& dirty_pages) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ size_t size = EntrySize(entry);
+ size_t page_off = 0;
+ size_t current_page_idx;
+ uintptr_t entry_address = reinterpret_cast<uintptr_t>(entry);
+ // Iterate every page this entry belongs to
+ do {
+ current_page_idx = entry_address / kPageSize + page_off;
+ if (dirty_pages.find(current_page_idx) != dirty_pages.end()) {
+ // This entry is on a dirty page
+ return true;
+ }
+ page_off++;
+ } while ((current_page_idx * kPageSize) < RoundUp(entry_address + size, kObjectAlignment));
+ return false;
+ }
+
+ void AddZygoteDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
+ zygote_dirty_entries_.insert(entry);
+ }
+
+ void AddImageDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
+ image_dirty_entries_.insert(entry);
+ }
+
+ void AddFalseDirtyEntry(T* entry) REQUIRES_SHARED(Locks::mutator_lock_) {
+ false_dirty_entries_.push_back(entry);
+ false_dirty_entry_bytes_ += EntrySize(entry);
+ }
+
+ // The output stream to write to.
+ std::ostream& os_;
+ // The byte contents of the remote (image) process' image.
+ std::vector<uint8_t>* remote_contents_;
+ // The byte contents of the zygote process' image.
+ std::vector<uint8_t>* zygote_contents_;
+ const backtrace_map_t& boot_map_;
+ const ImageHeader& image_header_;
+
+ // Count of entries that are different.
+ size_t different_entries_;
+
+ // Local entries that are dirty (differ in at least one byte).
+ size_t dirty_entry_bytes_;
+ std::vector<T*> dirty_entries_;
+
+ // Local entries that are clean, but located on dirty pages.
+ size_t false_dirty_entry_bytes_;
+ std::vector<T*> false_dirty_entries_;
+
+ // Image dirty entries
+ // If zygote_pid_only_ == true, these are shared dirty entries in the zygote.
+ // If zygote_pid_only_ == false, these are private dirty entries in the application.
+ std::set<T*> image_dirty_entries_;
+
+ // Zygote dirty entries (probably private dirty).
+ // We only add entries here if they differed in both the image and the zygote, so
+ // they are probably private dirty.
+ std::set<T*> zygote_dirty_entries_;
+
+ std::map<off_t /* field offset */, size_t /* count */> field_dirty_count_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RegionCommon);
+};
+
+template <typename T>
+class RegionSpecializedBase : public RegionCommon<T> {
+};
+
+// Region analysis for mirror::Objects
+template<>
+class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object> {
+ public:
+ RegionSpecializedBase(std::ostream* os,
+ std::vector<uint8_t>* remote_contents,
+ std::vector<uint8_t>* zygote_contents,
+ const backtrace_map_t& boot_map,
+ const ImageHeader& image_header) :
+ RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header),
+ os_(*os) { }
+
+ void CheckEntrySanity(const uint8_t* current) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ CHECK_ALIGNED(current, kObjectAlignment);
+ mirror::Object* entry = reinterpret_cast<mirror::Object*>(const_cast<uint8_t*>(current));
+ // Sanity check that we are reading a real mirror::Object
+ CHECK(entry->GetClass() != nullptr) << "Image object at address "
+ << entry
+ << " has null class";
+ if (kUseBakerReadBarrier) {
+ entry->AssertReadBarrierState();
+ }
+ }
+
+ mirror::Object* GetNextEntry(mirror::Object* entry)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint8_t* next =
+ reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
+ return reinterpret_cast<mirror::Object*>(next);
+ }
+
+ void VisitEntry(mirror::Object* entry)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Unconditionally store the class descriptor in case we need it later
+ mirror::Class* klass = entry->GetClass();
+ class_data_[klass].descriptor = GetClassDescriptor(klass);
+ }
+
+ void AddCleanEntry(mirror::Object* entry)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ class_data_[entry->GetClass()].AddCleanObject();
+ }
+
+ void AddFalseDirtyEntry(mirror::Object* entry)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ RegionCommon<mirror::Object>::AddFalseDirtyEntry(entry);
+ class_data_[entry->GetClass()].AddFalseDirtyObject(entry);
+ }
+
+ void AddDirtyEntry(mirror::Object* entry, mirror::Object* entry_remote)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ size_t entry_size = EntrySize(entry);
+ ++different_entries_;
+ dirty_entry_bytes_ += entry_size;
+ // Log dirty count and objects for class objects only.
+ mirror::Class* klass = entry->GetClass();
+ if (klass->IsClassClass()) {
+ // Increment counts for the fields that are dirty
+ const uint8_t* current = reinterpret_cast<const uint8_t*>(entry);
+ const uint8_t* current_remote = reinterpret_cast<const uint8_t*>(entry_remote);
+ for (size_t i = 0; i < entry_size; ++i) {
+ if (current[i] != current_remote[i]) {
+ field_dirty_count_[i]++;
+ }
+ }
+ dirty_entries_.push_back(entry);
+ }
+ class_data_[klass].AddDirtyObject(entry, entry_remote);
+ }
+
+ void DiffEntryContents(mirror::Object* entry, uint8_t* remote_bytes)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const char* tabs = " ";
+ // Attempt to find fields for all dirty bytes.
+ mirror::Class* klass = entry->GetClass();
+ if (entry->IsClass()) {
+ os_ << tabs
+ << "Class " << mirror::Class::PrettyClass(entry->AsClass()) << " " << entry << "\n";
+ } else {
+ os_ << tabs
+ << "Instance of " << mirror::Class::PrettyClass(klass) << " " << entry << "\n";
+ }
+
+ std::unordered_set<ArtField*> dirty_instance_fields;
+ std::unordered_set<ArtField*> dirty_static_fields;
+ // Examine the bytes comprising the Object, computing which fields are dirty
+ // and recording them for later display. If the Object is an array object,
+ // compute the dirty entries.
+ const uint8_t* entry_bytes = reinterpret_cast<const uint8_t*>(entry);
+ mirror::Object* remote_entry = reinterpret_cast<mirror::Object*>(remote_bytes);
+ for (size_t i = 0, count = entry->SizeOf(); i < count; ++i) {
+ if (entry_bytes[i] != remote_bytes[i]) {
+ ArtField* field = ArtField::FindInstanceFieldWithOffset</*exact*/false>(klass, i);
+ if (field != nullptr) {
+ dirty_instance_fields.insert(field);
+ } else if (entry->IsClass()) {
+ field = ArtField::FindStaticFieldWithOffset</*exact*/false>(entry->AsClass(), i);
+ if (field != nullptr) {
+ dirty_static_fields.insert(field);
+ }
+ }
+ if (field == nullptr) {
+ if (klass->IsArrayClass()) {
+ mirror::Class* component_type = klass->GetComponentType();
+ Primitive::Type primitive_type = component_type->GetPrimitiveType();
+ size_t component_size = Primitive::ComponentSize(primitive_type);
+ size_t data_offset = mirror::Array::DataOffset(component_size).Uint32Value();
+ if (i >= data_offset) {
+ os_ << tabs << "Dirty array element " << (i - data_offset) / component_size << "\n";
+ // Skip to next element to prevent spam.
+ i += component_size - 1;
+ continue;
+ }
+ }
+ os_ << tabs << "No field for byte offset " << i << "\n";
+ }
+ }
+ }
+ // Dump different fields.
+ if (!dirty_instance_fields.empty()) {
+ os_ << tabs << "Dirty instance fields " << dirty_instance_fields.size() << "\n";
+ for (ArtField* field : dirty_instance_fields) {
+ os_ << tabs << ArtField::PrettyField(field)
+ << " original=" << PrettyFieldValue(field, entry)
+ << " remote=" << PrettyFieldValue(field, remote_entry) << "\n";
+ }
+ }
+ if (!dirty_static_fields.empty()) {
+ os_ << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n";
+ for (ArtField* field : dirty_static_fields) {
+ os_ << tabs << ArtField::PrettyField(field)
+ << " original=" << PrettyFieldValue(field, entry)
+ << " remote=" << PrettyFieldValue(field, remote_entry) << "\n";
+ }
+ }
+ os_ << "\n";
+ }
+
+ void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ // vector of pairs (size_t count, Class*)
+ auto dirty_object_class_values =
+ SortByValueDesc<mirror::Class*, size_t, ClassData>(
+ class_data_,
+ [](const ClassData& d) { return d.dirty_object_count; });
+ os_ << "\n" << " Dirty object count by class:\n";
+ for (const auto& vk_pair : dirty_object_class_values) {
+ size_t dirty_object_count = vk_pair.first;
+ mirror::Class* klass = vk_pair.second;
+ ClassData& class_data = class_data_[klass];
+ size_t object_sizes = class_data.dirty_object_size_in_bytes;
+ float avg_dirty_bytes_per_class =
+ class_data.dirty_object_byte_count * 1.0f / object_sizes;
+ float avg_object_size = object_sizes * 1.0f / dirty_object_count;
+ const std::string& descriptor = class_data.descriptor;
+ os_ << " " << mirror::Class::PrettyClass(klass) << " ("
+ << "objects: " << dirty_object_count << ", "
+ << "avg dirty bytes: " << avg_dirty_bytes_per_class << ", "
+ << "avg object size: " << avg_object_size << ", "
+ << "class descriptor: '" << descriptor << "'"
+ << ")\n";
+ if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) {
+ DumpSamplesAndOffsetCount();
+ os_ << " field contents:\n";
+ for (mirror::Object* object : class_data.dirty_objects) {
+ // remote class object
+ auto remote_klass = reinterpret_cast<mirror::Class*>(object);
+ // local class object
+ auto local_klass =
+ RemoteContentsPointerToLocal(remote_klass,
+ *RegionCommon<mirror::Object>::remote_contents_,
+ RegionCommon<mirror::Object>::image_header_);
+ os_ << " " << reinterpret_cast<const void*>(object) << " ";
+ os_ << " class_status (remote): " << remote_klass->GetStatus() << ", ";
+ os_ << " class_status (local): " << local_klass->GetStatus();
+ os_ << "\n";
+ }
+ }
+ }
+ }
+
+ void DumpFalseDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ // vector of pairs (size_t count, Class*)
+ auto false_dirty_object_class_values =
+ SortByValueDesc<mirror::Class*, size_t, ClassData>(
+ class_data_,
+ [](const ClassData& d) { return d.false_dirty_object_count; });
+ os_ << "\n" << " False-dirty object count by class:\n";
+ for (const auto& vk_pair : false_dirty_object_class_values) {
+ size_t object_count = vk_pair.first;
+ mirror::Class* klass = vk_pair.second;
+ ClassData& class_data = class_data_[klass];
+ size_t object_sizes = class_data.false_dirty_byte_count;
+ float avg_object_size = object_sizes * 1.0f / object_count;
+ const std::string& descriptor = class_data.descriptor;
+ os_ << " " << mirror::Class::PrettyClass(klass) << " ("
+ << "objects: " << object_count << ", "
+ << "avg object size: " << avg_object_size << ", "
+ << "total bytes: " << object_sizes << ", "
+ << "class descriptor: '" << descriptor << "'"
+ << ")\n";
+ }
+ }
+
+ void DumpCleanEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ // vector of pairs (size_t count, Class*)
+ auto clean_object_class_values =
+ SortByValueDesc<mirror::Class*, size_t, ClassData>(
+ class_data_,
+ [](const ClassData& d) { return d.clean_object_count; });
+ os_ << "\n" << " Clean object count by class:\n";
+ for (const auto& vk_pair : clean_object_class_values) {
+ os_ << " " << mirror::Class::PrettyClass(vk_pair.second) << " (" << vk_pair.first << ")\n";
+ }
+ }
+
+ private:
+ // Aggregate and detail class data from an image diff.
+ struct ClassData {
+ size_t dirty_object_count = 0;
+ // Track only the byte-per-byte dirtiness (in bytes)
+ size_t dirty_object_byte_count = 0;
+ // Track the object-by-object dirtiness (in bytes)
+ size_t dirty_object_size_in_bytes = 0;
+ size_t clean_object_count = 0;
+ std::string descriptor;
+ size_t false_dirty_byte_count = 0;
+ size_t false_dirty_object_count = 0;
+ std::vector<mirror::Object*> false_dirty_objects;
+ // Remote pointers to dirty objects
+ std::vector<mirror::Object*> dirty_objects;
+
+ void AddCleanObject() REQUIRES_SHARED(Locks::mutator_lock_) {
+ ++clean_object_count;
+ }
+
+ void AddDirtyObject(mirror::Object* object, mirror::Object* object_remote)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ++dirty_object_count;
+ dirty_object_byte_count += CountDirtyBytes(object, object_remote);
+ dirty_object_size_in_bytes += EntrySize(object);
+ dirty_objects.push_back(object_remote);
+ }
+
+ void AddFalseDirtyObject(mirror::Object* object) REQUIRES_SHARED(Locks::mutator_lock_) {
+ ++false_dirty_object_count;
+ false_dirty_objects.push_back(object);
+ false_dirty_byte_count += EntrySize(object);
+ }
+
+ private:
+ // Go byte-by-byte and figure out what exactly got dirtied
+ static size_t CountDirtyBytes(mirror::Object* object1, mirror::Object* object2)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const uint8_t* cur1 = reinterpret_cast<const uint8_t*>(object1);
+ const uint8_t* cur2 = reinterpret_cast<const uint8_t*>(object2);
+ size_t dirty_bytes = 0;
+ size_t object_size = EntrySize(object1);
+ for (size_t i = 0; i < object_size; ++i) {
+ if (cur1[i] != cur2[i]) {
+ dirty_bytes++;
+ }
+ }
+ return dirty_bytes;
+ }
+ };
+
+ std::ostream& os_;
+ std::map<mirror::Class*, ClassData> class_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegionSpecializedBase);
+};
+
+// Region analysis for ArtMethods.
+// TODO: most of these need work.
+template<>
+class RegionSpecializedBase<ArtMethod> : RegionCommon<ArtMethod> {
+ public:
+ RegionSpecializedBase(std::ostream* os,
+ std::vector<uint8_t>* remote_contents,
+ std::vector<uint8_t>* zygote_contents,
+ const backtrace_map_t& boot_map,
+ const ImageHeader& image_header) :
+ RegionCommon<ArtMethod>(os, remote_contents, zygote_contents, boot_map, image_header),
+ os_(*os) { }
+
+ void CheckEntrySanity(const uint8_t* current ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ }
+
+ ArtMethod* GetNextEntry(ArtMethod* entry)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint8_t* next = reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
+ return reinterpret_cast<ArtMethod*>(next);
+ }
+
+ void VisitEntry(ArtMethod* method ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ }
+
+ void AddFalseDirtyEntry(ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ RegionCommon<ArtMethod>::AddFalseDirtyEntry(method);
+ }
+
+ void AddCleanEntry(ArtMethod* method ATTRIBUTE_UNUSED) {
+ }
+
+ void AddDirtyEntry(ArtMethod* method, ArtMethod* method_remote)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ size_t entry_size = EntrySize(method);
+ ++different_entries_;
+ dirty_entry_bytes_ += entry_size;
+ // Increment counts for the fields that are dirty
+ const uint8_t* current = reinterpret_cast<const uint8_t*>(method);
+ const uint8_t* current_remote = reinterpret_cast<const uint8_t*>(method_remote);
+ // ArtMethods always log their dirty count and entries.
+ for (size_t i = 0; i < entry_size; ++i) {
+ if (current[i] != current_remote[i]) {
+ field_dirty_count_[i]++;
+ }
+ }
+ dirty_entries_.push_back(method);
+ }
+
+ void DiffEntryContents(ArtMethod* method ATTRIBUTE_UNUSED,
+ uint8_t* remote_bytes ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ }
+
+ void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ DumpSamplesAndOffsetCount();
+ os_ << " field contents:\n";
+ for (ArtMethod* method : dirty_entries_) {
+ // remote method
+ auto art_method = reinterpret_cast<ArtMethod*>(method);
+ // remote class
+ mirror::Class* remote_declaring_class =
+ FixUpRemotePointer(art_method->GetDeclaringClass(),
+ *RegionCommon<ArtMethod>::remote_contents_,
+ RegionCommon<ArtMethod>::boot_map_);
+ // local class
+ mirror::Class* declaring_class =
+ RemoteContentsPointerToLocal(remote_declaring_class,
+ *RegionCommon<ArtMethod>::remote_contents_,
+ RegionCommon<ArtMethod>::image_header_);
+ DumpOneArtMethod(art_method, declaring_class, remote_declaring_class);
+ }
+ }
+
+ void DumpFalseDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ os_ << " field contents:\n";
+ for (ArtMethod* method : false_dirty_entries_) {
+ // local class
+ mirror::Class* declaring_class = method->GetDeclaringClass();
+ DumpOneArtMethod(method, declaring_class, nullptr);
+ }
+ }
+
+ void DumpCleanEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ }
+
+ private:
+ std::ostream& os_;
+
+ void DumpOneArtMethod(ArtMethod* art_method,
+ mirror::Class* declaring_class,
+ mirror::Class* remote_declaring_class)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ PointerSize pointer_size = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet());
+ os_ << " " << reinterpret_cast<const void*>(art_method) << " ";
+ os_ << " entryPointFromJni: "
+ << reinterpret_cast<const void*>(art_method->GetDataPtrSize(pointer_size)) << ", ";
+ os_ << " entryPointFromQuickCompiledCode: "
+ << reinterpret_cast<const void*>(
+ art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size))
+ << ", ";
+ os_ << " isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
+ os_ << " class_status (local): " << declaring_class->GetStatus();
+ if (remote_declaring_class != nullptr) {
+ os_ << ", class_status (remote): " << remote_declaring_class->GetStatus();
+ }
+ os_ << "\n";
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(RegionSpecializedBase);
+};
+
+template <typename T>
+class RegionData : public RegionSpecializedBase<T> {
+ public:
+ RegionData(std::ostream* os,
+ std::vector<uint8_t>* remote_contents,
+ std::vector<uint8_t>* zygote_contents,
+ const backtrace_map_t& boot_map,
+ const ImageHeader& image_header) :
+ RegionSpecializedBase<T>(os, remote_contents, zygote_contents, boot_map, image_header),
+ os_(*os) {
+ CHECK(remote_contents != nullptr);
+ CHECK(zygote_contents != nullptr);
+ }
+
+ // Walk over the type T entries in theregion between begin_image_ptr and end_image_ptr,
+ // collecting and reporting data regarding dirty, difference, etc.
+ void ProcessRegion(const MappingData& mapping_data,
+ RemoteProcesses remotes,
+ const uint8_t* begin_image_ptr,
+ const uint8_t* end_image_ptr)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment);
+ T* entry = reinterpret_cast<T*>(const_cast<uint8_t*>(current));
+ while (reinterpret_cast<uintptr_t>(entry) < reinterpret_cast<uintptr_t>(end_image_ptr)) {
+ ComputeEntryDirty(entry, begin_image_ptr, mapping_data.dirty_page_set);
+
+ entry = RegionSpecializedBase<T>::GetNextEntry(entry);
+ }
+
+ // Looking at only dirty pages, figure out how many of those bytes belong to dirty entries.
+ // TODO: fix this now that there are multiple regions in a mapping.
+ float true_dirtied_percent =
+ RegionCommon<T>::GetDirtyEntryBytes() * 1.0f / (mapping_data.dirty_pages * kPageSize);
+
+ // Entry specific statistics.
+ os_ << RegionCommon<T>::GetDifferentEntryCount() << " different entries, \n "
+ << RegionCommon<T>::GetDirtyEntryBytes() << " different entry [bytes], \n "
+ << RegionCommon<T>::GetFalseDirtyEntryCount() << " false dirty entries,\n "
+ << RegionCommon<T>::GetFalseDirtyEntryBytes() << " false dirty entry [bytes], \n "
+ << true_dirtied_percent << " different entries-vs-total in a dirty page;\n "
+ << "";
+
+ if (RegionCommon<T>::GetZygoteDirtyEntryCount() != 0) {
+ // We only reach this point if both pids were specified. Furthermore,
+ // entries are only displayed here if they differed in both the image
+ // and the zygote, so they are probably private dirty.
+ CHECK(remotes == RemoteProcesses::kImageAndZygote);
+ os_ << "\n" << " Zygote dirty entries (probably shared dirty): ";
+ DiffDirtyEntries(ProcessType::kZygote, begin_image_ptr, RegionCommon<T>::zygote_contents_);
+ }
+ os_ << "\n";
+ switch (remotes) {
+ case RemoteProcesses::kZygoteOnly:
+ os_ << " Zygote shared dirty entries: ";
+ break;
+ case RemoteProcesses::kImageAndZygote:
+ os_ << " Application dirty entries (private dirty): ";
+ break;
+ case RemoteProcesses::kImageOnly:
+ os_ << " Application dirty entries (unknown whether private or shared dirty): ";
+ break;
+ }
+ DiffDirtyEntries(ProcessType::kRemote, begin_image_ptr, RegionCommon<T>::remote_contents_);
+ RegionSpecializedBase<T>::DumpDirtyEntries();
+ RegionSpecializedBase<T>::DumpFalseDirtyEntries();
+ RegionSpecializedBase<T>::DumpCleanEntries();
+ }
+
+ private:
+ std::ostream& os_;
+
+ void DiffDirtyEntries(ProcessType process_type,
+ const uint8_t* begin_image_ptr,
+ std::vector<uint8_t>* contents)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ os_ << RegionCommon<T>::dirty_entries_.size() << "\n";
+ const std::set<T*>& entries =
+ (process_type == ProcessType::kZygote) ?
+ RegionCommon<T>::zygote_dirty_entries_:
+ RegionCommon<T>::image_dirty_entries_;
+ for (T* entry : entries) {
+ uint8_t* entry_bytes = reinterpret_cast<uint8_t*>(entry);
+ ptrdiff_t offset = entry_bytes - begin_image_ptr;
+ uint8_t* remote_bytes = &(*contents)[offset];
+ RegionSpecializedBase<T>::DiffEntryContents(entry, remote_bytes);
+ }
+ }
+
+ void ComputeEntryDirty(T* entry,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_pages)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Set up pointers in the remote and the zygote for comparison.
+ uint8_t* current = reinterpret_cast<uint8_t*>(entry);
+ ptrdiff_t offset = current - begin_image_ptr;
+ T* entry_remote =
+ reinterpret_cast<T*>(const_cast<uint8_t*>(&(*RegionCommon<T>::remote_contents_)[offset]));
+ const uint8_t* current_zygote =
+ RegionCommon<T>::zygote_contents_->empty() ? nullptr :
+ &(*RegionCommon<T>::zygote_contents_)[offset];
+ T* entry_zygote = reinterpret_cast<T*>(const_cast<uint8_t*>(current_zygote));
+ // Visit and classify entries at the current location.
+ RegionSpecializedBase<T>::VisitEntry(entry);
+ bool different_image_entry = EntriesDiffer(entry, entry_remote);
+ if (different_image_entry) {
+ bool different_zygote_entry = false;
+ if (entry_zygote != nullptr) {
+ different_zygote_entry = EntriesDiffer(entry, entry_zygote);
+ }
+ if (different_zygote_entry) {
+ // Different from zygote.
+ RegionCommon<T>::AddZygoteDirtyEntry(entry);
+ RegionSpecializedBase<T>::AddDirtyEntry(entry, entry_remote);
+ } else {
+ // Just different from image.
+ RegionCommon<T>::AddImageDirtyEntry(entry);
+ RegionSpecializedBase<T>::AddDirtyEntry(entry, entry_remote);
+ }
+ } else {
+ RegionSpecializedBase<T>::AddCleanEntry(entry);
+ }
+ if (!different_image_entry && RegionCommon<T>::IsEntryOnDirtyPage(entry, dirty_pages)) {
+ // This entry was either never mutated or got mutated back to the same value.
+ // TODO: Do I want to distinguish a "different" vs a "dirty" page here?
+ RegionSpecializedBase<T>::AddFalseDirtyEntry(entry);
+ }
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(RegionData);
+};
+
+} // namespace
+
+
class ImgDiagDumper {
public:
explicit ImgDiagDumper(std::ostream* os,
@@ -123,8 +920,6 @@
CHECK(boot_map_.end >= boot_map_.start);
boot_map_size_ = boot_map_.end - boot_map_.start;
- pointer_size_ = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet());
-
// Open /proc/<image_diff_pid_>/mem and read as remote_contents_.
std::string image_file_name =
StringPrintf("/proc/%ld/mem", static_cast<long>(image_diff_pid_)); // NOLINT [runtime/int]
@@ -188,7 +983,7 @@
return false;
}
- // Commit the mappings, etc., to the object state.
+ // Commit the mappings, etc.
proc_maps_ = std::move(tmp_proc_maps);
remote_contents_ = std::move(tmp_remote_contents);
zygote_contents_ = std::move(tmp_zygote_contents);
@@ -228,14 +1023,7 @@
return DumpImageDiffMap();
}
- bool ComputeDirtyBytes(const uint8_t* image_begin,
- size_t* dirty_pages /*out*/,
- size_t* different_pages /*out*/,
- size_t* different_bytes /*out*/,
- size_t* different_int32s /*out*/,
- size_t* private_pages /*out*/,
- size_t* private_dirty_pages /*out*/,
- std::set<size_t>* dirty_page_set_local) {
+ bool ComputeDirtyBytes(const uint8_t* image_begin, MappingData* mapping_data /*out*/) {
std::ostream& os = *os_;
size_t virtual_page_idx = 0; // Virtual page number (for an absolute memory address)
@@ -254,7 +1042,7 @@
uint8_t* remote_ptr = &remote_contents_[offset];
if (memcmp(local_ptr, remote_ptr, kPageSize) != 0) {
- ++*different_pages;
+ mapping_data->different_pages++;
// Count the number of 32-bit integers that are different.
for (size_t i = 0; i < kPageSize / sizeof(uint32_t); ++i) {
@@ -262,7 +1050,7 @@
const uint32_t* local_ptr_int32 = reinterpret_cast<const uint32_t*>(local_ptr);
if (remote_ptr_int32[i] != local_ptr_int32[i]) {
- ++*different_int32s;
+ mapping_data->different_int32s++;
}
}
}
@@ -286,7 +1074,7 @@
page_idx = (offset + page_off_begin) / kPageSize;
if (*local_ptr != *remote_ptr) {
// Track number of bytes that are different
- ++*different_bytes;
+ mapping_data->different_bytes++;
}
// Independently count the # of dirty pages on the remote side
@@ -307,294 +1095,38 @@
os << error_msg;
return false;
} else if (dirtiness > 0) {
- ++*dirty_pages;
- dirty_page_set_local->insert(dirty_page_set_local->end(), virtual_page_idx);
+ mapping_data->dirty_pages++;
+ mapping_data->dirty_page_set.insert(mapping_data->dirty_page_set.end(), virtual_page_idx);
}
bool is_dirty = dirtiness > 0;
bool is_private = page_count == 1;
if (page_count == 1) {
- ++*private_pages;
+ mapping_data->private_pages++;
}
if (is_dirty && is_private) {
- ++*private_dirty_pages;
+ mapping_data->private_dirty_pages++;
}
}
}
+ mapping_data->false_dirty_pages = mapping_data->dirty_pages - mapping_data->different_pages;
+ // Print low-level (bytes, int32s, pages) statistics.
+ os << mapping_data->different_bytes << " differing bytes,\n "
+ << mapping_data->different_int32s << " differing int32s,\n "
+ << mapping_data->different_pages << " differing pages,\n "
+ << mapping_data->dirty_pages << " pages are dirty;\n "
+ << mapping_data->false_dirty_pages << " pages are false dirty;\n "
+ << mapping_data->private_pages << " pages are private;\n "
+ << mapping_data->private_dirty_pages << " pages are Private_Dirty\n ";
+
return true;
}
- bool ObjectIsOnDirtyPage(const uint8_t* item,
- size_t size,
- const std::set<size_t>& dirty_page_set_local) {
- size_t page_off = 0;
- size_t current_page_idx;
- uintptr_t object_address = reinterpret_cast<uintptr_t>(item);
- // Iterate every page this object belongs to
- do {
- current_page_idx = object_address / kPageSize + page_off;
-
- if (dirty_page_set_local.find(current_page_idx) != dirty_page_set_local.end()) {
- // This object is on a dirty page
- return true;
- }
-
- page_off++;
- } while ((current_page_idx * kPageSize) < RoundUp(object_address + size, kObjectAlignment));
-
- return false;
- }
-
- static std::string PrettyFieldValue(ArtField* field, mirror::Object* obj)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::ostringstream oss;
- switch (field->GetTypeAsPrimitiveType()) {
- case Primitive::kPrimNot: {
- oss << obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(
- field->GetOffset());
- break;
- }
- case Primitive::kPrimBoolean: {
- oss << static_cast<bool>(obj->GetFieldBoolean<kVerifyNone>(field->GetOffset()));
- break;
- }
- case Primitive::kPrimByte: {
- oss << static_cast<int32_t>(obj->GetFieldByte<kVerifyNone>(field->GetOffset()));
- break;
- }
- case Primitive::kPrimChar: {
- oss << obj->GetFieldChar<kVerifyNone>(field->GetOffset());
- break;
- }
- case Primitive::kPrimShort: {
- oss << obj->GetFieldShort<kVerifyNone>(field->GetOffset());
- break;
- }
- case Primitive::kPrimInt: {
- oss << obj->GetField32<kVerifyNone>(field->GetOffset());
- break;
- }
- case Primitive::kPrimLong: {
- oss << obj->GetField64<kVerifyNone>(field->GetOffset());
- break;
- }
- case Primitive::kPrimFloat: {
- oss << obj->GetField32<kVerifyNone>(field->GetOffset());
- break;
- }
- case Primitive::kPrimDouble: {
- oss << obj->GetField64<kVerifyNone>(field->GetOffset());
- break;
- }
- case Primitive::kPrimVoid: {
- oss << "void";
- break;
- }
- }
- return oss.str();
- }
-
- // Aggregate and detail class data from an image diff.
- struct ClassData {
- size_t dirty_object_count = 0;
-
- // Track only the byte-per-byte dirtiness (in bytes)
- size_t dirty_object_byte_count = 0;
-
- // Track the object-by-object dirtiness (in bytes)
- size_t dirty_object_size_in_bytes = 0;
-
- size_t clean_object_count = 0;
-
- std::string descriptor;
-
- size_t false_dirty_byte_count = 0;
- size_t false_dirty_object_count = 0;
- std::vector<const uint8_t*> false_dirty_objects;
-
- // Remote pointers to dirty objects
- std::vector<const uint8_t*> dirty_objects;
- };
-
- void DiffObjectContents(mirror::Object* obj,
- uint8_t* remote_bytes,
- std::ostream& os) REQUIRES_SHARED(Locks::mutator_lock_) {
- const char* tabs = " ";
- // Attempt to find fields for all dirty bytes.
- mirror::Class* klass = obj->GetClass();
- if (obj->IsClass()) {
- os << tabs << "Class " << mirror::Class::PrettyClass(obj->AsClass()) << " " << obj << "\n";
- } else {
- os << tabs << "Instance of " << mirror::Class::PrettyClass(klass) << " " << obj << "\n";
- }
-
- std::unordered_set<ArtField*> dirty_instance_fields;
- std::unordered_set<ArtField*> dirty_static_fields;
- const uint8_t* obj_bytes = reinterpret_cast<const uint8_t*>(obj);
- mirror::Object* remote_obj = reinterpret_cast<mirror::Object*>(remote_bytes);
- for (size_t i = 0, count = obj->SizeOf(); i < count; ++i) {
- if (obj_bytes[i] != remote_bytes[i]) {
- ArtField* field = ArtField::FindInstanceFieldWithOffset</*exact*/false>(klass, i);
- if (field != nullptr) {
- dirty_instance_fields.insert(field);
- } else if (obj->IsClass()) {
- field = ArtField::FindStaticFieldWithOffset</*exact*/false>(obj->AsClass(), i);
- if (field != nullptr) {
- dirty_static_fields.insert(field);
- }
- }
- if (field == nullptr) {
- if (klass->IsArrayClass()) {
- mirror::Class* component_type = klass->GetComponentType();
- Primitive::Type primitive_type = component_type->GetPrimitiveType();
- size_t component_size = Primitive::ComponentSize(primitive_type);
- size_t data_offset = mirror::Array::DataOffset(component_size).Uint32Value();
- if (i >= data_offset) {
- os << tabs << "Dirty array element " << (i - data_offset) / component_size << "\n";
- // Skip to next element to prevent spam.
- i += component_size - 1;
- continue;
- }
- }
- os << tabs << "No field for byte offset " << i << "\n";
- }
- }
- }
- // Dump different fields. TODO: Dump field contents.
- if (!dirty_instance_fields.empty()) {
- os << tabs << "Dirty instance fields " << dirty_instance_fields.size() << "\n";
- for (ArtField* field : dirty_instance_fields) {
- os << tabs << ArtField::PrettyField(field)
- << " original=" << PrettyFieldValue(field, obj)
- << " remote=" << PrettyFieldValue(field, remote_obj) << "\n";
- }
- }
- if (!dirty_static_fields.empty()) {
- os << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n";
- for (ArtField* field : dirty_static_fields) {
- os << tabs << ArtField::PrettyField(field)
- << " original=" << PrettyFieldValue(field, obj)
- << " remote=" << PrettyFieldValue(field, remote_obj) << "\n";
- }
- }
- os << "\n";
- }
-
- struct ObjectRegionData {
- // Count of objects that are different.
- size_t different_objects = 0;
-
- // Local objects that are dirty (differ in at least one byte).
- size_t dirty_object_bytes = 0;
- std::vector<const uint8_t*>* dirty_objects;
-
- // Local objects that are clean, but located on dirty pages.
- size_t false_dirty_object_bytes = 0;
- std::vector<const uint8_t*> false_dirty_objects;
-
- // Image dirty objects
- // If zygote_pid_only_ == true, these are shared dirty objects in the zygote.
- // If zygote_pid_only_ == false, these are private dirty objects in the application.
- std::set<const uint8_t*> image_dirty_objects;
-
- // Zygote dirty objects (probably private dirty).
- // We only add objects here if they differed in both the image and the zygote, so
- // they are probably private dirty.
- std::set<const uint8_t*> zygote_dirty_objects;
-
- std::map<off_t /* field offset */, size_t /* count */>* field_dirty_count;
- };
-
- void ComputeObjectDirty(const uint8_t* current,
- const uint8_t* current_remote,
- const uint8_t* current_zygote,
- ClassData* obj_class_data,
- size_t obj_size,
- const std::set<size_t>& dirty_page_set_local,
- ObjectRegionData* region_data /*out*/) {
- bool different_image_object = memcmp(current, current_remote, obj_size) != 0;
- if (different_image_object) {
- bool different_zygote_object = false;
- if (!zygote_contents_.empty()) {
- different_zygote_object = memcmp(current, current_zygote, obj_size) != 0;
- }
- if (different_zygote_object) {
- // Different from zygote.
- region_data->zygote_dirty_objects.insert(current);
- } else {
- // Just different from image.
- region_data->image_dirty_objects.insert(current);
- }
-
- ++region_data->different_objects;
- region_data->dirty_object_bytes += obj_size;
-
- ++obj_class_data->dirty_object_count;
-
- // Go byte-by-byte and figure out what exactly got dirtied
- size_t dirty_byte_count_per_object = 0;
- for (size_t i = 0; i < obj_size; ++i) {
- if (current[i] != current_remote[i]) {
- dirty_byte_count_per_object++;
- }
- }
- obj_class_data->dirty_object_byte_count += dirty_byte_count_per_object;
- obj_class_data->dirty_object_size_in_bytes += obj_size;
- obj_class_data->dirty_objects.push_back(current_remote);
- } else {
- ++obj_class_data->clean_object_count;
- }
-
- if (different_image_object) {
- if (region_data->dirty_objects != nullptr) {
- // print the fields that are dirty
- for (size_t i = 0; i < obj_size; ++i) {
- if (current[i] != current_remote[i]) {
- size_t dirty_count = 0;
- if (region_data->field_dirty_count->find(i) != region_data->field_dirty_count->end()) {
- dirty_count = (*region_data->field_dirty_count)[i];
- }
- (*region_data->field_dirty_count)[i] = dirty_count + 1;
- }
- }
-
- region_data->dirty_objects->push_back(current);
- }
- /*
- * TODO: Resurrect this stuff in the client when we add ArtMethod iterator.
- } else {
- std::string descriptor = GetClassDescriptor(klass);
- if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) {
- // this is an ArtMethod
- ArtMethod* art_method = reinterpret_cast<ArtMethod*>(remote_obj);
-
- // print the fields that are dirty
- for (size_t i = 0; i < obj_size; ++i) {
- if (current[i] != current_remote[i]) {
- art_method_field_dirty_count[i]++;
- }
- }
-
- art_method_dirty_objects.push_back(art_method);
- }
- }
- */
- } else if (ObjectIsOnDirtyPage(current, obj_size, dirty_page_set_local)) {
- // This object was either never mutated or got mutated back to the same value.
- // TODO: Do I want to distinguish a "different" vs a "dirty" page here?
- region_data->false_dirty_objects.push_back(current);
- obj_class_data->false_dirty_objects.push_back(current);
- region_data->false_dirty_object_bytes += obj_size;
- obj_class_data->false_dirty_byte_count += obj_size;
- obj_class_data->false_dirty_object_count += 1;
- }
- }
-
// Look at /proc/$pid/mem and only diff the things from there
bool DumpImageDiffMap()
- REQUIRES_SHARED(Locks::mutator_lock_) {
+ REQUIRES_SHARED(Locks::mutator_lock_) {
std::ostream& os = *os_;
std::string error_msg;
@@ -624,384 +1156,37 @@
// If we wanted even more validation we could map the ImageHeader from the file
}
- size_t dirty_pages = 0;
- size_t different_pages = 0;
- size_t different_bytes = 0;
- size_t different_int32s = 0;
- size_t private_pages = 0;
- size_t private_dirty_pages = 0;
+ MappingData mapping_data;
- // Set of the local virtual page indices that are dirty
- std::set<size_t> dirty_page_set_local;
-
- if (!ComputeDirtyBytes(image_begin,
- &dirty_pages,
- &different_pages,
- &different_bytes,
- &different_int32s,
- &private_pages,
- &private_dirty_pages,
- &dirty_page_set_local)) {
+ os << "Mapping at [" << reinterpret_cast<void*>(boot_map_.start) << ", "
+ << reinterpret_cast<void*>(boot_map_.end) << ") had:\n ";
+ if (!ComputeDirtyBytes(image_begin, &mapping_data)) {
return false;
}
- std::map<mirror::Class*, ClassData> class_data;
+ RegionData<mirror::Object> object_region_data(os_,
+ &remote_contents_,
+ &zygote_contents_,
+ boot_map_,
+ image_header_);
- // Walk each object in the remote image space and compare it against ours
- std::map<off_t /* field offset */, int /* count */> art_method_field_dirty_count;
- std::vector<ArtMethod*> art_method_dirty_objects;
-
- std::map<off_t /* field offset */, size_t /* count */> class_field_dirty_count;
- std::vector<const uint8_t*> class_dirty_objects;
-
-
- // Look up remote classes by their descriptor
- std::map<std::string, mirror::Class*> remote_class_map;
- // Look up local classes by their descriptor
- std::map<std::string, mirror::Class*> local_class_map;
-
- const uint8_t* begin_image_ptr = image_begin_unaligned;
- const uint8_t* end_image_ptr = image_mirror_end_unaligned;
-
- ObjectRegionData region_data;
-
- const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment);
- while (reinterpret_cast<uintptr_t>(current) < reinterpret_cast<uintptr_t>(end_image_ptr)) {
- CHECK_ALIGNED(current, kObjectAlignment);
- mirror::Object* obj = reinterpret_cast<mirror::Object*>(const_cast<uint8_t*>(current));
-
- // Sanity check that we are reading a real object
- CHECK(obj->GetClass() != nullptr) << "Image object at address " << obj << " has null class";
- if (kUseBakerReadBarrier) {
- obj->AssertReadBarrierState();
- }
-
- mirror::Class* klass = obj->GetClass();
- size_t obj_size = obj->SizeOf();
- ClassData& obj_class_data = class_data[klass];
-
- // Check against the other object and see if they are different
- ptrdiff_t offset = current - begin_image_ptr;
- const uint8_t* current_remote = &remote_contents_[offset];
- const uint8_t* current_zygote =
- zygote_contents_.empty() ? nullptr : &zygote_contents_[offset];
-
- if (klass->IsClassClass()) {
- region_data.field_dirty_count = &class_field_dirty_count;
- region_data.dirty_objects = &class_dirty_objects;
- } else {
- region_data.field_dirty_count = nullptr;
- region_data.dirty_objects = nullptr;
- }
-
-
- ComputeObjectDirty(current,
- current_remote,
- current_zygote,
- &obj_class_data,
- obj_size,
- dirty_page_set_local,
- ®ion_data);
-
- // Object specific stuff.
- std::string descriptor = GetClassDescriptor(klass);
- if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) {
- local_class_map[descriptor] = reinterpret_cast<mirror::Class*>(obj);
- mirror::Object* remote_obj = reinterpret_cast<mirror::Object*>(
- const_cast<uint8_t*>(current_remote));
- remote_class_map[descriptor] = reinterpret_cast<mirror::Class*>(remote_obj);
- }
-
- // Unconditionally store the class descriptor in case we need it later
- obj_class_data.descriptor = descriptor;
-
- current += RoundUp(obj_size, kObjectAlignment);
- }
-
- // Looking at only dirty pages, figure out how many of those bytes belong to dirty objects.
- float true_dirtied_percent = region_data.dirty_object_bytes * 1.0f / (dirty_pages * kPageSize);
- size_t false_dirty_pages = dirty_pages - different_pages;
-
- os << "Mapping at [" << reinterpret_cast<void*>(boot_map_.start) << ", "
- << reinterpret_cast<void*>(boot_map_.end) << ") had: \n "
- << different_bytes << " differing bytes, \n "
- << different_int32s << " differing int32s, \n "
- << region_data.different_objects << " different objects, \n "
- << region_data.dirty_object_bytes << " different object [bytes], \n "
- << region_data.false_dirty_objects.size() << " false dirty objects,\n "
- << region_data.false_dirty_object_bytes << " false dirty object [bytes], \n "
- << true_dirtied_percent << " different objects-vs-total in a dirty page;\n "
- << different_pages << " different pages; \n "
- << dirty_pages << " pages are dirty; \n "
- << false_dirty_pages << " pages are false dirty; \n "
- << private_pages << " pages are private; \n "
- << private_dirty_pages << " pages are Private_Dirty\n "
- << "";
-
- // vector of pairs (int count, Class*)
- auto dirty_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>(
- class_data, [](const ClassData& d) { return d.dirty_object_count; });
- auto clean_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>(
- class_data, [](const ClassData& d) { return d.clean_object_count; });
-
- if (!region_data.zygote_dirty_objects.empty()) {
- // We only reach this point if both pids were specified. Furthermore,
- // objects are only displayed here if they differed in both the image
- // and the zygote, so they are probably private dirty.
- CHECK(image_diff_pid_ > 0 && zygote_diff_pid_ > 0);
- os << "\n" << " Zygote dirty objects (probably shared dirty): "
- << region_data.zygote_dirty_objects.size() << "\n";
- for (const uint8_t* obj_bytes : region_data.zygote_dirty_objects) {
- auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes));
- ptrdiff_t offset = obj_bytes - begin_image_ptr;
- uint8_t* remote_bytes = &zygote_contents_[offset];
- DiffObjectContents(obj, remote_bytes, os);
- }
- }
- os << "\n";
+ RemoteProcesses remotes;
if (zygote_pid_only_) {
- // image_diff_pid_ is the zygote process.
- os << " Zygote shared dirty objects: ";
+ remotes = RemoteProcesses::kZygoteOnly;
+ } else if (zygote_diff_pid_ > 0) {
+ remotes = RemoteProcesses::kImageAndZygote;
} else {
- // image_diff_pid_ is actually the image (application) process.
- if (zygote_diff_pid_ > 0) {
- os << " Application dirty objects (private dirty): ";
- } else {
- os << " Application dirty objects (unknown whether private or shared dirty): ";
- }
- }
- os << region_data.image_dirty_objects.size() << "\n";
- for (const uint8_t* obj_bytes : region_data.image_dirty_objects) {
- auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes));
- ptrdiff_t offset = obj_bytes - begin_image_ptr;
- uint8_t* remote_bytes = &remote_contents_[offset];
- DiffObjectContents(obj, remote_bytes, os);
+ remotes = RemoteProcesses::kImageOnly;
}
- os << "\n" << " Dirty object count by class:\n";
- for (const auto& vk_pair : dirty_object_class_values) {
- int dirty_object_count = vk_pair.first;
- mirror::Class* klass = vk_pair.second;
- int object_sizes = class_data[klass].dirty_object_size_in_bytes;
- float avg_dirty_bytes_per_class =
- class_data[klass].dirty_object_byte_count * 1.0f / object_sizes;
- float avg_object_size = object_sizes * 1.0f / dirty_object_count;
- const std::string& descriptor = class_data[klass].descriptor;
- os << " " << mirror::Class::PrettyClass(klass) << " ("
- << "objects: " << dirty_object_count << ", "
- << "avg dirty bytes: " << avg_dirty_bytes_per_class << ", "
- << "avg object size: " << avg_object_size << ", "
- << "class descriptor: '" << descriptor << "'"
- << ")\n";
-
- constexpr size_t kMaxAddressPrint = 5;
- if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) {
- os << " sample object addresses: ";
- for (size_t i = 0; i < art_method_dirty_objects.size() && i < kMaxAddressPrint; ++i) {
- auto art_method = art_method_dirty_objects[i];
-
- os << reinterpret_cast<void*>(art_method) << ", ";
- }
- os << "\n";
-
- os << " dirty byte +offset:count list = ";
- auto art_method_field_dirty_count_sorted =
- SortByValueDesc<off_t, int, int>(art_method_field_dirty_count);
- for (auto pair : art_method_field_dirty_count_sorted) {
- off_t offset = pair.second;
- int count = pair.first;
-
- os << "+" << offset << ":" << count << ", ";
- }
-
- os << "\n";
-
- os << " field contents:\n";
- const auto& dirty_objects_list = class_data[klass].dirty_objects;
- for (const uint8_t* uobj : dirty_objects_list) {
- auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));
- // remote method
- auto art_method = reinterpret_cast<ArtMethod*>(obj);
-
- // remote class
- mirror::Class* remote_declaring_class =
- FixUpRemotePointer(art_method->GetDeclaringClass(), remote_contents_, boot_map_);
-
- // local class
- mirror::Class* declaring_class =
- RemoteContentsPointerToLocal(remote_declaring_class, remote_contents_, image_header_);
-
- os << " " << reinterpret_cast<void*>(obj) << " ";
- os << " entryPointFromJni: "
- << reinterpret_cast<const void*>(
- art_method->GetDataPtrSize(pointer_size_)) << ", ";
- os << " entryPointFromQuickCompiledCode: "
- << reinterpret_cast<const void*>(
- art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_))
- << ", ";
- os << " isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
- os << " class_status (local): " << declaring_class->GetStatus();
- os << " class_status (remote): " << remote_declaring_class->GetStatus();
- os << "\n";
- }
- }
- if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) {
- os << " sample object addresses: ";
- for (size_t i = 0; i < class_dirty_objects.size() && i < kMaxAddressPrint; ++i) {
- auto class_ptr = class_dirty_objects[i];
-
- os << reinterpret_cast<const void*>(class_ptr) << ", ";
- }
- os << "\n";
-
- os << " dirty byte +offset:count list = ";
- auto class_field_dirty_count_sorted =
- SortByValueDesc<off_t, int, size_t>(class_field_dirty_count);
- for (auto pair : class_field_dirty_count_sorted) {
- off_t offset = pair.second;
- int count = pair.first;
-
- os << "+" << offset << ":" << count << ", ";
- }
- os << "\n";
-
- os << " field contents:\n";
- // TODO: templatize this to avoid the awful casts down to uint8_t* and back.
- const auto& dirty_objects_list = class_data[klass].dirty_objects;
- for (const uint8_t* uobj : dirty_objects_list) {
- auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));
- // remote class object
- auto remote_klass = reinterpret_cast<mirror::Class*>(obj);
-
- // local class object
- auto local_klass = RemoteContentsPointerToLocal(remote_klass,
- remote_contents_,
- image_header_);
-
- os << " " << reinterpret_cast<const void*>(obj) << " ";
- os << " class_status (remote): " << remote_klass->GetStatus() << ", ";
- os << " class_status (local): " << local_klass->GetStatus();
- os << "\n";
- }
- }
- }
-
- auto false_dirty_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>(
- class_data, [](const ClassData& d) { return d.false_dirty_object_count; });
-
- os << "\n" << " False-dirty object count by class:\n";
- for (const auto& vk_pair : false_dirty_object_class_values) {
- int object_count = vk_pair.first;
- mirror::Class* klass = vk_pair.second;
- int object_sizes = class_data[klass].false_dirty_byte_count;
- float avg_object_size = object_sizes * 1.0f / object_count;
- const std::string& descriptor = class_data[klass].descriptor;
- os << " " << mirror::Class::PrettyClass(klass) << " ("
- << "objects: " << object_count << ", "
- << "avg object size: " << avg_object_size << ", "
- << "total bytes: " << object_sizes << ", "
- << "class descriptor: '" << descriptor << "'"
- << ")\n";
-
- if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) {
- // TODO: templatize this to avoid the awful casts down to uint8_t* and back.
- auto& art_method_false_dirty_objects = class_data[klass].false_dirty_objects;
-
- os << " field contents:\n";
- for (const uint8_t* uobj : art_method_false_dirty_objects) {
- auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));
- // local method
- auto art_method = reinterpret_cast<ArtMethod*>(obj);
-
- // local class
- mirror::Class* declaring_class = art_method->GetDeclaringClass();
-
- os << " " << reinterpret_cast<const void*>(obj) << " ";
- os << " entryPointFromJni: "
- << reinterpret_cast<const void*>(
- art_method->GetDataPtrSize(pointer_size_)) << ", ";
- os << " entryPointFromQuickCompiledCode: "
- << reinterpret_cast<const void*>(
- art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_))
- << ", ";
- os << " isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
- os << " class_status (local): " << declaring_class->GetStatus();
- os << "\n";
- }
- }
- }
-
- os << "\n" << " Clean object count by class:\n";
- for (const auto& vk_pair : clean_object_class_values) {
- os << " " << mirror::Class::PrettyClass(vk_pair.second) << " (" << vk_pair.first << ")\n";
- }
+ object_region_data.ProcessRegion(mapping_data,
+ remotes,
+ image_begin_unaligned,
+ image_mirror_end_unaligned);
return true;
}
- // Fixup a remote pointer that we read from a foreign boot.art to point to our own memory.
- // Returned pointer will point to inside of remote_contents.
- template <typename T>
- static T* FixUpRemotePointer(T* remote_ptr,
- std::vector<uint8_t>& remote_contents,
- const backtrace_map_t& boot_map) {
- if (remote_ptr == nullptr) {
- return nullptr;
- }
-
- uintptr_t remote = reinterpret_cast<uintptr_t>(remote_ptr);
-
- CHECK_LE(boot_map.start, remote);
- CHECK_GT(boot_map.end, remote);
-
- off_t boot_offset = remote - boot_map.start;
-
- return reinterpret_cast<T*>(&remote_contents[boot_offset]);
- }
-
- template <typename T>
- static T* RemoteContentsPointerToLocal(T* remote_ptr,
- std::vector<uint8_t>& remote_contents,
- const ImageHeader& image_header) {
- if (remote_ptr == nullptr) {
- return nullptr;
- }
-
- uint8_t* remote = reinterpret_cast<uint8_t*>(remote_ptr);
- ptrdiff_t boot_offset = remote - &remote_contents[0];
-
- const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header) + boot_offset;
-
- return reinterpret_cast<T*>(const_cast<uint8_t*>(local_ptr));
- }
-
- static std::string GetClassDescriptor(mirror::Class* klass)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK(klass != nullptr);
-
- std::string descriptor;
- const char* descriptor_str = klass->GetDescriptor(&descriptor);
-
- return std::string(descriptor_str);
- }
-
- template <typename K, typename V, typename D>
- static std::vector<std::pair<V, K>> SortByValueDesc(
- const std::map<K, D> map,
- std::function<V(const D&)> value_mapper = [](const D& d) { return static_cast<V>(d); }) {
- // Store value->key so that we can use the default sort from pair which
- // sorts by value first and then key
- std::vector<std::pair<V, K>> value_key_vector;
-
- for (const auto& kv_pair : map) {
- value_key_vector.push_back(std::make_pair(value_mapper(kv_pair.second), kv_pair.first));
- }
-
- // Sort in reverse (descending order)
- std::sort(value_key_vector.rbegin(), value_key_vector.rend());
- return value_key_vector;
- }
-
static bool GetPageFrameNumber(File* page_map_file,
size_t virtual_page_index,
uint64_t* page_frame_number,
@@ -1142,8 +1327,6 @@
pid_t zygote_diff_pid_; // Dump image diff against zygote boot.art if pid is non-negative
bool zygote_pid_only_; // The user only specified a pid for the zygote.
- // Pointer size constant for object fields, etc.
- PointerSize pointer_size_;
// BacktraceMap used for finding the memory mapping of the image file.
std::unique_ptr<BacktraceMap> proc_maps_;
// Boot image mapping.
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 0a95d49..99168c9 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2233,16 +2233,15 @@
if (num_methods != 0u) {
os << "Methods (size=" << num_methods << "):\n";
ScopedIndentation indent2(&vios_);
- auto* resolved_methods = dex_cache->GetResolvedMethods();
+ mirror::MethodDexCacheType* resolved_methods = dex_cache->GetResolvedMethods();
for (size_t i = 0, length = dex_cache->NumResolvedMethods(); i < length; ++i) {
- auto* elem = mirror::DexCache::GetElementPtrSize(resolved_methods,
- i,
- image_pointer_size);
+ ArtMethod* elem = mirror::DexCache::GetNativePairPtrSize(
+ resolved_methods, i, image_pointer_size).object;
size_t run = 0;
for (size_t j = i + 1;
- j != length && elem == mirror::DexCache::GetElementPtrSize(resolved_methods,
- j,
- image_pointer_size);
+ j != length &&
+ elem == mirror::DexCache::GetNativePairPtrSize(
+ resolved_methods, j, image_pointer_size).object;
++j) {
++run;
}
@@ -2270,7 +2269,7 @@
ScopedIndentation indent2(&vios_);
auto* resolved_fields = dex_cache->GetResolvedFields();
for (size_t i = 0, length = dex_cache->NumResolvedFields(); i < length; ++i) {
- auto* elem = mirror::DexCache::GetNativePairPtrSize(
+ ArtField* elem = mirror::DexCache::GetNativePairPtrSize(
resolved_fields, i, image_pointer_size).object;
size_t run = 0;
for (size_t j = i + 1;
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index a93969f..1ee2fbd 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -535,17 +535,18 @@
orig_dex_cache->FixupResolvedTypes(RelocatedCopyOf(orig_types),
RelocatedPointerVisitor(this));
}
- ArtMethod** orig_methods = orig_dex_cache->GetResolvedMethods();
- ArtMethod** relocated_methods = RelocatedAddressOfPointer(orig_methods);
+ mirror::MethodDexCacheType* orig_methods = orig_dex_cache->GetResolvedMethods();
+ mirror::MethodDexCacheType* relocated_methods = RelocatedAddressOfPointer(orig_methods);
copy_dex_cache->SetField64<false>(
mirror::DexCache::ResolvedMethodsOffset(),
static_cast<int64_t>(reinterpret_cast<uintptr_t>(relocated_methods)));
if (orig_methods != nullptr) {
- ArtMethod** copy_methods = RelocatedCopyOf(orig_methods);
+ mirror::MethodDexCacheType* copy_methods = RelocatedCopyOf(orig_methods);
for (size_t j = 0, num = orig_dex_cache->NumResolvedMethods(); j != num; ++j) {
- ArtMethod* orig = mirror::DexCache::GetElementPtrSize(orig_methods, j, pointer_size);
- ArtMethod* copy = RelocatedAddressOfPointer(orig);
- mirror::DexCache::SetElementPtrSize(copy_methods, j, copy, pointer_size);
+ mirror::MethodDexCachePair orig =
+ mirror::DexCache::GetNativePairPtrSize(orig_methods, j, pointer_size);
+ mirror::MethodDexCachePair copy(RelocatedAddressOfPointer(orig.object), orig.index);
+ mirror::DexCache::SetNativePairPtrSize(copy_methods, j, copy, pointer_size);
}
}
mirror::FieldDexCacheType* orig_fields = orig_dex_cache->GetResolvedFields();
diff --git a/runtime/arch/arch_test.cc b/runtime/arch/arch_test.cc
index 838ae40..dd98f51 100644
--- a/runtime/arch/arch_test.cc
+++ b/runtime/arch/arch_test.cc
@@ -129,6 +129,10 @@
#undef FRAME_SIZE_SAVE_REFS_AND_ARGS
static constexpr size_t kFrameSizeSaveEverything = FRAME_SIZE_SAVE_EVERYTHING;
#undef FRAME_SIZE_SAVE_EVERYTHING
+#undef BAKER_MARK_INTROSPECTION_REGISTER_COUNT
+#undef BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE
+#undef BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET
+#undef BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE
} // namespace mips
namespace mips64 {
@@ -141,6 +145,10 @@
#undef FRAME_SIZE_SAVE_REFS_AND_ARGS
static constexpr size_t kFrameSizeSaveEverything = FRAME_SIZE_SAVE_EVERYTHING;
#undef FRAME_SIZE_SAVE_EVERYTHING
+#undef BAKER_MARK_INTROSPECTION_REGISTER_COUNT
+#undef BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE
+#undef BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET
+#undef BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE
} // namespace mips64
namespace x86 {
diff --git a/runtime/arch/arm/instruction_set_features_arm.cc b/runtime/arch/arm/instruction_set_features_arm.cc
index 8384460..0942356 100644
--- a/runtime/arch/arm/instruction_set_features_arm.cc
+++ b/runtime/arch/arm/instruction_set_features_arm.cc
@@ -279,10 +279,9 @@
return false;
}
const ArmInstructionSetFeatures* other_as_arm = other->AsArmInstructionSetFeatures();
-
- return (has_div_ || (has_div_ == other_as_arm->has_div_))
- && (has_atomic_ldrd_strd_ || (has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_))
- && (has_armv8a_ || (has_armv8a_ == other_as_arm->has_armv8a_));
+ return (has_div_ || !other_as_arm->has_div_)
+ && (has_atomic_ldrd_strd_ || !other_as_arm->has_atomic_ldrd_strd_)
+ && (has_armv8a_ || !other_as_arm->has_armv8a_);
}
uint32_t ArmInstructionSetFeatures::AsBitmap() const {
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 0de5905..375768e 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1585,31 +1585,98 @@
*
* Note that this stub writes to r0, r4, and r12.
*/
+ .extern artLookupResolvedMethod
ENTRY art_quick_imt_conflict_trampoline
- ldr r4, [sp, #0] // Load referrer
- ldr r4, [r4, #ART_METHOD_DEX_CACHE_METHODS_OFFSET_32] // Load dex cache methods array
- ldr r12, [r4, r12, lsl #POINTER_SIZE_SHIFT] // Load interface method
- ldr r0, [r0, #ART_METHOD_JNI_OFFSET_32] // Load ImtConflictTable
- ldr r4, [r0] // Load first entry in ImtConflictTable.
+ push {r1-r2}
+ .cfi_adjust_cfa_offset (2 * 4)
+ .cfi_rel_offset r1, 0
+ .cfi_rel_offset r2, 4
+ ldr r4, [sp, #(2 * 4)] // Load referrer.
+ ubfx r1, r12, #0, #METHOD_DEX_CACHE_HASH_BITS // Calculate DexCache method slot index.
+ ldr r4, [r4, #ART_METHOD_DEX_CACHE_METHODS_OFFSET_32] // Load dex cache methods array
+ add r4, r4, r1, lsl #(POINTER_SIZE_SHIFT + 1) // Load DexCache method slot address.
+ ldr r2, [r0, #ART_METHOD_JNI_OFFSET_32] // Load ImtConflictTable
+
+// FIXME: Configure the build to use the faster code when appropriate.
+// Currently we fall back to the slower version.
+#if HAS_ATOMIC_LDRD
+ ldrd r0, r1, [r4]
+#else
+ push {r3}
+ .cfi_adjust_cfa_offset 4
+ .cfi_rel_offset r3, 0
+.Limt_conflict_trampoline_retry_load:
+ ldrexd r0, r1, [r4]
+ strexd r3, r0, r1, [r4]
+ cmp r3, #0
+ bne .Limt_conflict_trampoline_retry_load
+ pop {r3}
+ .cfi_adjust_cfa_offset -4
+ .cfi_restore r3
+#endif
+
+ ldr r4, [r2] // Load first entry in ImtConflictTable.
+ cmp r1, r12 // Compare method index to see if we had a DexCache method hit.
+ bne .Limt_conflict_trampoline_dex_cache_miss
.Limt_table_iterate:
- cmp r4, r12
+ cmp r4, r0
// Branch if found. Benchmarks have shown doing a branch here is better.
- beq .Limt_table_found
+ beq .Limt_table_found
// If the entry is null, the interface method is not in the ImtConflictTable.
- cbz r4, .Lconflict_trampoline
+ cbz r4, .Lconflict_trampoline
// Iterate over the entries of the ImtConflictTable.
- ldr r4, [r0, #(2 * __SIZEOF_POINTER__)]!
+ ldr r4, [r2, #(2 * __SIZEOF_POINTER__)]!
b .Limt_table_iterate
.Limt_table_found:
// We successfully hit an entry in the table. Load the target method
// and jump to it.
- ldr r0, [r0, #__SIZEOF_POINTER__]
- ldr pc, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32]
+ ldr r0, [r2, #__SIZEOF_POINTER__]
+ .cfi_remember_state
+ pop {r1-r2}
+ .cfi_adjust_cfa_offset -(2 * 4)
+ .cfi_restore r1
+ .cfi_restore r2
+ ldr pc, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32]
+ .cfi_restore_state
.Lconflict_trampoline:
// Call the runtime stub to populate the ImtConflictTable and jump to the
// resolved method.
- mov r0, r12 // Load interface method
+ .cfi_remember_state
+ pop {r1-r2}
+ .cfi_adjust_cfa_offset -(2 * 4)
+ .cfi_restore r1
+ .cfi_restore r2
INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline
+ .cfi_restore_state
+.Limt_conflict_trampoline_dex_cache_miss:
+ // We're not creating a proper runtime method frame here,
+ // artLookupResolvedMethod() is not allowed to walk the stack.
+
+ // Save ImtConflictTable (r2), remaining arg (r3), first entry (r4), return address (lr).
+ push {r2-r4, lr}
+ .cfi_adjust_cfa_offset (4 * 4)
+ .cfi_rel_offset r3, 4
+ .cfi_rel_offset lr, 12
+ // Save FPR args.
+ vpush {d0-d7}
+ .cfi_adjust_cfa_offset (8 * 8)
+
+ mov r0, ip // Pass method index.
+ ldr r1, [sp, #(8 * 8 + 6 * 4)] // Pass referrer.
+ bl artLookupResolvedMethod // (uint32_t method_index, ArtMethod* referrer)
+
+ // Restore FPR args.
+ vpop {d0-d7}
+ .cfi_adjust_cfa_offset -(8 * 8)
+ // Restore ImtConflictTable (r2), remaining arg (r3), first entry (r4), return address (lr).
+ pop {r2-r4, lr}
+ .cfi_adjust_cfa_offset -(4 * 4)
+ .cfi_restore r3
+ .cfi_restore lr
+
+ cmp r0, #0 // If the method wasn't resolved,
+ beq .Lconflict_trampoline // skip the lookup and go to artInvokeInterfaceTrampoline().
+ b .Limt_table_iterate
END art_quick_imt_conflict_trampoline
.extern artQuickResolutionTrampoline
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index e097a33..d15f5b8 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2052,17 +2052,28 @@
* x0 is the conflict ArtMethod.
* xIP1 is a hidden argument that holds the target interface method's dex method index.
*
- * Note that this stub writes to xIP0, xIP1, and x0.
+ * Note that this stub writes to xIP0, xIP1, x13-x15, and x0.
*/
- .extern artInvokeInterfaceTrampoline
+ .extern artLookupResolvedMethod
ENTRY art_quick_imt_conflict_trampoline
ldr xIP0, [sp, #0] // Load referrer
+ ubfx x15, xIP1, #0, #METHOD_DEX_CACHE_HASH_BITS // Calculate DexCache method slot index.
ldr xIP0, [xIP0, #ART_METHOD_DEX_CACHE_METHODS_OFFSET_64] // Load dex cache methods array
- ldr xIP0, [xIP0, xIP1, lsl #POINTER_SIZE_SHIFT] // Load interface method
+ add xIP0, xIP0, x15, lsl #(POINTER_SIZE_SHIFT + 1) // Load DexCache method slot address.
+
+ // Relaxed atomic load x14:x15 from the dex cache slot.
+.Limt_conflict_trampoline_retry_load:
+ ldxp x14, x15, [xIP0]
+ stxp w13, x14, x15, [xIP0]
+ cbnz w13, .Limt_conflict_trampoline_retry_load
+
+ cmp x15, xIP1 // Compare method index to see if we had a DexCache method hit.
+ bne .Limt_conflict_trampoline_dex_cache_miss
+.Limt_conflict_trampoline_have_interface_method:
ldr xIP1, [x0, #ART_METHOD_JNI_OFFSET_64] // Load ImtConflictTable
ldr x0, [xIP1] // Load first entry in ImtConflictTable.
.Limt_table_iterate:
- cmp x0, xIP0
+ cmp x0, x14
// Branch if found. Benchmarks have shown doing a branch here is better.
beq .Limt_table_found
// If the entry is null, the interface method is not in the ImtConflictTable.
@@ -2079,8 +2090,46 @@
.Lconflict_trampoline:
// Call the runtime stub to populate the ImtConflictTable and jump to the
// resolved method.
- mov x0, xIP0 // Load interface method
+ mov x0, x14 // Load interface method
INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline
+.Limt_conflict_trampoline_dex_cache_miss:
+ // We're not creating a proper runtime method frame here,
+ // artLookupResolvedMethod() is not allowed to walk the stack.
+
+ // Save GPR args and return address, allocate space for FPR args, align stack.
+ SAVE_TWO_REGS_INCREASE_FRAME x0, x1, (8 * 8 + 8 * 8 + 8 + 8)
+ SAVE_TWO_REGS x2, x3, 16
+ SAVE_TWO_REGS x4, x5, 32
+ SAVE_TWO_REGS x6, x7, 48
+ SAVE_REG xLR, (8 * 8 + 8 * 8 + 8)
+
+ // Save FPR args.
+ stp d0, d1, [sp, #64]
+ stp d2, d3, [sp, #80]
+ stp d4, d5, [sp, #96]
+ stp d6, d7, [sp, #112]
+
+ mov x0, xIP1 // Pass method index.
+ ldr x1, [sp, #(8 * 8 + 8 * 8 + 8 + 8)] // Pass referrer.
+ bl artLookupResolvedMethod // (uint32_t method_index, ArtMethod* referrer)
+ mov x14, x0 // Move the interface method to x14 where the loop above expects it.
+
+ // Restore FPR args.
+ ldp d0, d1, [sp, #64]
+ ldp d2, d3, [sp, #80]
+ ldp d4, d5, [sp, #96]
+ ldp d6, d7, [sp, #112]
+
+ // Restore GPR args and return address.
+ RESTORE_REG xLR, (8 * 8 + 8 * 8 + 8)
+ RESTORE_TWO_REGS x2, x3, 16
+ RESTORE_TWO_REGS x4, x5, 32
+ RESTORE_TWO_REGS x6, x7, 48
+ RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, (8 * 8 + 8 * 8 + 8 + 8)
+
+ // If the method wasn't resolved, skip the lookup and go to artInvokeInterfaceTrampoline().
+ cbz x14, .Lconflict_trampoline
+ b .Limt_conflict_trampoline_have_interface_method
END art_quick_imt_conflict_trampoline
ENTRY art_quick_resolution_trampoline
diff --git a/runtime/arch/mips/asm_support_mips.S b/runtime/arch/mips/asm_support_mips.S
index 948b06c..50095ae 100644
--- a/runtime/arch/mips/asm_support_mips.S
+++ b/runtime/arch/mips/asm_support_mips.S
@@ -127,6 +127,13 @@
#endif // USE_HEAP_POISONING
.endm
+// Byte size of the instructions (un)poisoning heap references.
+#ifdef USE_HEAP_POISONING
+#define HEAP_POISON_INSTR_SIZE 4
+#else
+#define HEAP_POISON_INSTR_SIZE 0
+#endif // USE_HEAP_POISONING
+
// Based on contents of creg select the minimum integer
// At the end of the macro the original value of creg is lost
.macro MINint dreg,rreg,sreg,creg
diff --git a/runtime/arch/mips/asm_support_mips.h b/runtime/arch/mips/asm_support_mips.h
index 7437774..9d8572f 100644
--- a/runtime/arch/mips/asm_support_mips.h
+++ b/runtime/arch/mips/asm_support_mips.h
@@ -24,4 +24,24 @@
#define FRAME_SIZE_SAVE_REFS_AND_ARGS 112
#define FRAME_SIZE_SAVE_EVERYTHING 256
+// &art_quick_read_barrier_mark_introspection is the first of many entry points:
+// 21 entry points for long field offsets, large array indices and variable array indices
+// (see macro BRB_FIELD_LONG_OFFSET_ENTRY)
+// 21 entry points for short field offsets and small array indices
+// (see macro BRB_FIELD_SHORT_OFFSET_ENTRY)
+// 21 entry points for GC roots
+// (see macro BRB_GC_ROOT_ENTRY)
+
+// There are as many entry points of each kind as there are registers that
+// can hold a reference: V0-V1, A0-A3, T0-T7, S2-S8.
+#define BAKER_MARK_INTROSPECTION_REGISTER_COUNT 21
+
+#define BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE (8 * 4) // 8 instructions in
+ // BRB_FIELD_*_OFFSET_ENTRY.
+
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET \
+ (2 * BAKER_MARK_INTROSPECTION_REGISTER_COUNT * BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE)
+
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE (4 * 4) // 4 instructions in BRB_GC_ROOT_ENTRY.
+
#endif // ART_RUNTIME_ARCH_MIPS_ASM_SUPPORT_MIPS_H_
diff --git a/runtime/arch/mips/entrypoints_init_mips.cc b/runtime/arch/mips/entrypoints_init_mips.cc
index 9978da5..3010246 100644
--- a/runtime/arch/mips/entrypoints_init_mips.cc
+++ b/runtime/arch/mips/entrypoints_init_mips.cc
@@ -16,6 +16,7 @@
#include <string.h>
+#include "arch/mips/asm_support_mips.h"
#include "atomic.h"
#include "entrypoints/jni/jni_entrypoints.h"
#include "entrypoints/quick/quick_alloc_entrypoints.h"
@@ -59,6 +60,10 @@
extern "C" mirror::Object* art_quick_read_barrier_mark_reg22(mirror::Object*);
extern "C" mirror::Object* art_quick_read_barrier_mark_reg29(mirror::Object*);
+extern "C" mirror::Object* art_quick_read_barrier_mark_introspection(mirror::Object*);
+extern "C" mirror::Object* art_quick_read_barrier_mark_introspection_gc_roots(mirror::Object*);
+extern "C" void art_quick_read_barrier_mark_introspection_end_of_entries(void);
+
// Math entrypoints.
extern int32_t CmpgDouble(double a, double b);
extern int32_t CmplDouble(double a, double b);
@@ -87,6 +92,23 @@
extern "C" int64_t __moddi3(int64_t, int64_t);
void UpdateReadBarrierEntrypoints(QuickEntryPoints* qpoints, bool is_active) {
+ intptr_t introspection_field_array_entries_size =
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection_gc_roots) -
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection);
+ static_assert(
+ BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET == 2 *
+ BAKER_MARK_INTROSPECTION_REGISTER_COUNT * BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE,
+ "Expecting equal");
+ DCHECK_EQ(introspection_field_array_entries_size,
+ BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET);
+ intptr_t introspection_gc_root_entries_size =
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection_end_of_entries) -
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection_gc_roots);
+ DCHECK_EQ(introspection_gc_root_entries_size,
+ BAKER_MARK_INTROSPECTION_REGISTER_COUNT * BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE);
+ qpoints->pReadBarrierMarkReg00 = is_active ? art_quick_read_barrier_mark_introspection : nullptr;
+ static_assert(!IsDirectEntrypoint(kQuickReadBarrierMarkReg00),
+ "Non-direct C stub marked direct.");
qpoints->pReadBarrierMarkReg01 = is_active ? art_quick_read_barrier_mark_reg01 : nullptr;
static_assert(!IsDirectEntrypoint(kQuickReadBarrierMarkReg01),
"Non-direct C stub marked direct.");
@@ -416,9 +438,6 @@
// Cannot use the following registers to pass arguments:
// 0(ZERO), 1(AT), 16(S0), 17(S1), 24(T8), 25(T9), 26(K0), 27(K1), 28(GP), 29(SP), 31(RA).
// Note that there are 30 entry points only: 00 for register 1(AT), ..., 29 for register 30(S8).
- qpoints->pReadBarrierMarkReg00 = nullptr;
- static_assert(!IsDirectEntrypoint(kQuickReadBarrierMarkReg00),
- "Non-direct C stub marked direct.");
qpoints->pReadBarrierMarkReg15 = nullptr;
static_assert(!IsDirectEntrypoint(kQuickReadBarrierMarkReg15),
"Non-direct C stub marked direct.");
diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S
index 00e3d67..59a2c10 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -165,13 +165,29 @@
.endm
/*
+ * Individually usable part of macro SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY.
+ */
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_S4_THRU_S8
+ sw $s8, 104($sp)
+ .cfi_rel_offset 30, 104
+ sw $s7, 96($sp)
+ .cfi_rel_offset 23, 96
+ sw $s6, 92($sp)
+ .cfi_rel_offset 22, 92
+ sw $s5, 88($sp)
+ .cfi_rel_offset 21, 88
+ sw $s4, 84($sp)
+ .cfi_rel_offset 20, 84
+.endm
+
+ /*
* Macro that sets up the callee save frame to conform with
* Runtime::CreateCalleeSaveMethod(kSaveRefsAndArgs).
* callee-save: $a1-$a3, $t0-$t1, $s2-$s8, $gp, $ra, $f8-$f19
* (26 total + 1 word padding + method*)
*/
-.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY
- addiu $sp, $sp, -112
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY save_s4_thru_s8=1
+ addiu $sp, $sp, -112
.cfi_adjust_cfa_offset 112
// Ugly compile-time check, but we only have the preprocessor.
@@ -179,40 +195,33 @@
#error "FRAME_SIZE_SAVE_REFS_AND_ARGS(MIPS) size not as expected."
#endif
- sw $ra, 108($sp)
+ sw $ra, 108($sp)
.cfi_rel_offset 31, 108
- sw $s8, 104($sp)
- .cfi_rel_offset 30, 104
- sw $gp, 100($sp)
+ sw $gp, 100($sp)
.cfi_rel_offset 28, 100
- sw $s7, 96($sp)
- .cfi_rel_offset 23, 96
- sw $s6, 92($sp)
- .cfi_rel_offset 22, 92
- sw $s5, 88($sp)
- .cfi_rel_offset 21, 88
- sw $s4, 84($sp)
- .cfi_rel_offset 20, 84
- sw $s3, 80($sp)
+ .if \save_s4_thru_s8
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_S4_THRU_S8
+ .endif
+ sw $s3, 80($sp)
.cfi_rel_offset 19, 80
- sw $s2, 76($sp)
+ sw $s2, 76($sp)
.cfi_rel_offset 18, 76
- sw $t1, 72($sp)
+ sw $t1, 72($sp)
.cfi_rel_offset 9, 72
- sw $t0, 68($sp)
+ sw $t0, 68($sp)
.cfi_rel_offset 8, 68
- sw $a3, 64($sp)
+ sw $a3, 64($sp)
.cfi_rel_offset 7, 64
- sw $a2, 60($sp)
+ sw $a2, 60($sp)
.cfi_rel_offset 6, 60
- sw $a1, 56($sp)
+ sw $a1, 56($sp)
.cfi_rel_offset 5, 56
- SDu $f18, $f19, 48, $sp, $t8
- SDu $f16, $f17, 40, $sp, $t8
- SDu $f14, $f15, 32, $sp, $t8
- SDu $f12, $f13, 24, $sp, $t8
- SDu $f10, $f11, 16, $sp, $t8
- SDu $f8, $f9, 8, $sp, $t8
+ SDu $f18, $f19, 48, $sp, $t8
+ SDu $f16, $f17, 40, $sp, $t8
+ SDu $f14, $f15, 32, $sp, $t8
+ SDu $f12, $f13, 24, $sp, $t8
+ SDu $f10, $f11, 16, $sp, $t8
+ SDu $f8, $f9, 8, $sp, $t8
# bottom will hold Method*
.endm
@@ -225,8 +234,14 @@
* Allocates ARG_SLOT_SIZE bytes at the bottom of the stack for arg slots.
* Reserves FRAME_SIZE_SAVE_REFS_AND_ARGS + ARG_SLOT_SIZE bytes on the stack
*/
-.macro SETUP_SAVE_REFS_AND_ARGS_FRAME
- SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME save_s4_thru_s8_only=0
+ .if \save_s4_thru_s8_only
+ // It is expected that `SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY /* save_s4_thru_s8 */ 0`
+ // has been done prior to `SETUP_SAVE_REFS_AND_ARGS_FRAME /* save_s4_thru_s8_only */ 1`.
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_S4_THRU_S8
+ .else
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY
+ .endif
lw $t0, %got(_ZN3art7Runtime9instance_E)($gp)
lw $t0, 0($t0)
lw $t0, RUNTIME_SAVE_REFS_AND_ARGS_METHOD_OFFSET($t0)
@@ -254,44 +269,64 @@
.cfi_adjust_cfa_offset ARG_SLOT_SIZE
.endm
-.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME
- addiu $sp, $sp, ARG_SLOT_SIZE # remove argument slots on the stack
- .cfi_adjust_cfa_offset -ARG_SLOT_SIZE
- lw $ra, 108($sp)
- .cfi_restore 31
- lw $s8, 104($sp)
- .cfi_restore 30
- lw $gp, 100($sp)
+ /*
+ * Individually usable part of macro RESTORE_SAVE_REFS_AND_ARGS_FRAME.
+ */
+.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME_GP
+ lw $gp, 100($sp)
.cfi_restore 28
- lw $s7, 96($sp)
- .cfi_restore 23
- lw $s6, 92($sp)
- .cfi_restore 22
- lw $s5, 88($sp)
- .cfi_restore 21
- lw $s4, 84($sp)
- .cfi_restore 20
- lw $s3, 80($sp)
- .cfi_restore 19
- lw $s2, 76($sp)
- .cfi_restore 18
- lw $t1, 72($sp)
- .cfi_restore 9
- lw $t0, 68($sp)
- .cfi_restore 8
- lw $a3, 64($sp)
- .cfi_restore 7
- lw $a2, 60($sp)
- .cfi_restore 6
- lw $a1, 56($sp)
+.endm
+
+ /*
+ * Individually usable part of macro RESTORE_SAVE_REFS_AND_ARGS_FRAME.
+ */
+.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME_A1
+ lw $a1, 56($sp)
.cfi_restore 5
- LDu $f18, $f19, 48, $sp, $t8
- LDu $f16, $f17, 40, $sp, $t8
- LDu $f14, $f15, 32, $sp, $t8
- LDu $f12, $f13, 24, $sp, $t8
- LDu $f10, $f11, 16, $sp, $t8
- LDu $f8, $f9, 8, $sp, $t8
- addiu $sp, $sp, 112 # pop frame
+.endm
+
+.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME restore_s4_thru_s8=1, remove_arg_slots=1
+ .if \remove_arg_slots
+ addiu $sp, $sp, ARG_SLOT_SIZE # Remove argument slots from the stack.
+ .cfi_adjust_cfa_offset -ARG_SLOT_SIZE
+ .endif
+ lw $ra, 108($sp)
+ .cfi_restore 31
+ .if \restore_s4_thru_s8
+ lw $s8, 104($sp)
+ .cfi_restore 30
+ .endif
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME_GP
+ .if \restore_s4_thru_s8
+ lw $s7, 96($sp)
+ .cfi_restore 23
+ lw $s6, 92($sp)
+ .cfi_restore 22
+ lw $s5, 88($sp)
+ .cfi_restore 21
+ lw $s4, 84($sp)
+ .cfi_restore 20
+ .endif
+ lw $s3, 80($sp)
+ .cfi_restore 19
+ lw $s2, 76($sp)
+ .cfi_restore 18
+ lw $t1, 72($sp)
+ .cfi_restore 9
+ lw $t0, 68($sp)
+ .cfi_restore 8
+ lw $a3, 64($sp)
+ .cfi_restore 7
+ lw $a2, 60($sp)
+ .cfi_restore 6
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME_A1
+ LDu $f18, $f19, 48, $sp, $t8
+ LDu $f16, $f17, 40, $sp, $t8
+ LDu $f14, $f15, 32, $sp, $t8
+ LDu $f12, $f13, 24, $sp, $t8
+ LDu $f10, $f11, 16, $sp, $t8
+ LDu $f8, $f9, 8, $sp, $t8
+ addiu $sp, $sp, 112 # Pop frame.
.cfi_adjust_cfa_offset -112
.endm
@@ -826,9 +861,10 @@
* On success this wrapper will restore arguments and *jump* to the target, leaving the lr
* pointing back to the original caller.
*/
-.macro INVOKE_TRAMPOLINE_BODY cxx_name
+.macro INVOKE_TRAMPOLINE_BODY cxx_name, save_s4_thru_s8_only=0
.extern \cxx_name
- SETUP_SAVE_REFS_AND_ARGS_FRAME # save callee saves in case allocation triggers GC
+ SETUP_SAVE_REFS_AND_ARGS_FRAME \save_s4_thru_s8_only # save callee saves in case
+ # allocation triggers GC
move $a2, rSELF # pass Thread::Current
la $t9, \cxx_name
jalr $t9 # (method_idx, this, Thread*, $sp)
@@ -2063,39 +2099,83 @@
* a0 is the conflict ArtMethod.
* t7 is a hidden argument that holds the target interface method's dex method index.
*
- * Note that this stub writes to a0, t7 and t8.
+ * Note that this stub writes to v0-v1, a0, t2-t9, f0-f7.
*/
+ .extern artLookupResolvedMethod
+ .extern __atomic_load_8 # For int64_t std::atomic::load(std::memory_order).
ENTRY art_quick_imt_conflict_trampoline
- lw $t8, 0($sp) # Load referrer.
- lw $t8, ART_METHOD_DEX_CACHE_METHODS_OFFSET_32($t8) # Load dex cache methods array.
- sll $t7, $t7, POINTER_SIZE_SHIFT # Calculate offset.
- addu $t7, $t8, $t7 # Add offset to base.
- lw $t7, 0($t7) # Load interface method.
- lw $a0, ART_METHOD_JNI_OFFSET_32($a0) # Load ImtConflictTable.
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY /* save_s4_thru_s8 */ 0
+
+ lw $t8, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $t8 = referrer.
+ la $t9, __atomic_load_8
+ addiu $sp, $sp, -ARG_SLOT_SIZE # Reserve argument slots on the stack.
+ .cfi_adjust_cfa_offset ARG_SLOT_SIZE
+ lw $t8, ART_METHOD_DEX_CACHE_METHODS_OFFSET_32($t8) # $t8 = dex cache methods array.
+
+ move $s2, $t7 # $s2 = method index (callee-saved).
+ lw $s3, ART_METHOD_JNI_OFFSET_32($a0) # $s3 = ImtConflictTable (callee-saved).
+
+ sll $t7, $t7, 32 - METHOD_DEX_CACHE_HASH_BITS # $t7 = slot index in top bits, zeroes below.
+ srl $t7, $t7, 32 - METHOD_DEX_CACHE_HASH_BITS - (POINTER_SIZE_SHIFT + 1)
+ # $t7 = slot offset.
+
+ li $a1, STD_MEMORY_ORDER_RELAXED # $a1 = std::memory_order_relaxed.
+ jalr $t9 # [$v0, $v1] = __atomic_load_8($a0, $a1).
+ addu $a0, $t8, $t7 # $a0 = DexCache method slot address.
+
+ bne $v1, $s2, .Limt_conflict_trampoline_dex_cache_miss # Branch if method index miss.
+ addiu $sp, $sp, ARG_SLOT_SIZE # Remove argument slots from the stack.
+ .cfi_adjust_cfa_offset -ARG_SLOT_SIZE
.Limt_table_iterate:
- lw $t8, 0($a0) # Load next entry in ImtConflictTable.
+ lw $t8, 0($s3) # Load next entry in ImtConflictTable.
# Branch if found.
- beq $t8, $t7, .Limt_table_found
+ beq $t8, $v0, .Limt_table_found
nop
# If the entry is null, the interface method is not in the ImtConflictTable.
beqz $t8, .Lconflict_trampoline
nop
# Iterate over the entries of the ImtConflictTable.
b .Limt_table_iterate
- addiu $a0, $a0, 2 * __SIZEOF_POINTER__ # Iterate to the next entry.
+ addiu $s3, $s3, 2 * __SIZEOF_POINTER__ # Iterate to the next entry.
.Limt_table_found:
# We successfully hit an entry in the table. Load the target method and jump to it.
- lw $a0, __SIZEOF_POINTER__($a0)
+ .cfi_remember_state
+ lw $a0, __SIZEOF_POINTER__($s3)
lw $t9, ART_METHOD_QUICK_CODE_OFFSET_32($a0)
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME /* restore_s4_thru_s8 */ 0, /* remove_arg_slots */ 0
jalr $zero, $t9
nop
+ .cfi_restore_state
.Lconflict_trampoline:
# Call the runtime stub to populate the ImtConflictTable and jump to the resolved method.
- move $a0, $t7 # Load interface method.
- INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline
+ .cfi_remember_state
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME_GP # Restore clobbered $gp.
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME_A1 # Restore this.
+ move $a0, $v0 # Load interface method.
+ INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline, /* save_s4_thru_s8_only */ 1
+ .cfi_restore_state
+
+.Limt_conflict_trampoline_dex_cache_miss:
+ # We're not creating a proper runtime method frame here,
+ # artLookupResolvedMethod() is not allowed to walk the stack.
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME_GP # Restore clobbered $gp.
+ lw $a1, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $a1 = referrer.
+ la $t9, artLookupResolvedMethod
+ addiu $sp, $sp, -ARG_SLOT_SIZE # Reserve argument slots on the stack.
+ .cfi_adjust_cfa_offset ARG_SLOT_SIZE
+ jalr $t9 # (uint32_t method_index, ArtMethod* referrer).
+ move $a0, $s2 # $a0 = method index.
+
+ # If the method wasn't resolved, skip the lookup and go to artInvokeInterfaceTrampoline().
+ beqz $v0, .Lconflict_trampoline
+ addiu $sp, $sp, ARG_SLOT_SIZE # Remove argument slots from the stack.
+ .cfi_adjust_cfa_offset -ARG_SLOT_SIZE
+
+ b .Limt_table_iterate
+ nop
END art_quick_imt_conflict_trampoline
.extern artQuickResolutionTrampoline
@@ -2721,6 +2801,385 @@
READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg29, $s8
// RA (register 31) is reserved.
+// Caller code:
+// Short constant offset/index:
+// R2: | R6:
+// lw $t9, pReadBarrierMarkReg00
+// beqz $t9, skip_call | beqzc $t9, skip_call
+// addiu $t9, $t9, thunk_disp | nop
+// jalr $t9 | jialc $t9, thunk_disp
+// nop |
+// skip_call: | skip_call:
+// lw `out`, ofs(`obj`) | lw `out`, ofs(`obj`)
+// [subu `out`, $zero, `out`] | [subu `out`, $zero, `out`] # Unpoison reference.
+.macro BRB_FIELD_SHORT_OFFSET_ENTRY obj
+1:
+ # Explicit null check. May be redundant (for array elements or when the field
+ # offset is larger than the page size, 4KB).
+ # $ra will be adjusted to point to lw's stack map when throwing NPE.
+ beqz \obj, .Lintrospection_throw_npe
+#if defined(_MIPS_ARCH_MIPS32R6)
+ lapc $gp, .Lintrospection_exits # $gp = address of .Lintrospection_exits.
+#else
+ addiu $gp, $t9, (.Lintrospection_exits - 1b) # $gp = address of .Lintrospection_exits.
+#endif
+ .set push
+ .set noat
+ lw $at, MIRROR_OBJECT_LOCK_WORD_OFFSET(\obj)
+ sll $at, $at, 31 - LOCK_WORD_READ_BARRIER_STATE_SHIFT # Move barrier state bit
+ # to sign bit.
+ bltz $at, .Lintrospection_field_array # If gray, load reference, mark.
+ move $t8, \obj # Move `obj` to $t8 for common code.
+ .set pop
+ jalr $zero, $ra # Otherwise, load-load barrier and return.
+ sync
+.endm
+
+// Caller code (R2):
+// Long constant offset/index: | Variable index:
+// lw $t9, pReadBarrierMarkReg00
+// lui $t8, ofs_hi | sll $t8, `index`, 2
+// beqz $t9, skip_call | beqz $t9, skip_call
+// addiu $t9, $t9, thunk_disp | addiu $t9, $t9, thunk_disp
+// jalr $t9 | jalr $t9
+// skip_call: | skip_call:
+// addu $t8, $t8, `obj` | addu $t8, $t8, `obj`
+// lw `out`, ofs_lo($t8) | lw `out`, ofs($t8)
+// [subu `out`, $zero, `out`] | [subu `out`, $zero, `out`] # Unpoison reference.
+//
+// Caller code (R6):
+// Long constant offset/index: | Variable index:
+// lw $t9, pReadBarrierMarkReg00
+// beqz $t9, skip_call | beqz $t9, skip_call
+// aui $t8, `obj`, ofs_hi | lsa $t8, `index`, `obj`, 2
+// jialc $t9, thunk_disp | jialc $t9, thunk_disp
+// skip_call: | skip_call:
+// lw `out`, ofs_lo($t8) | lw `out`, ofs($t8)
+// [subu `out`, $zero, `out`] | [subu `out`, $zero, `out`] # Unpoison reference.
+.macro BRB_FIELD_LONG_OFFSET_ENTRY obj
+1:
+ # No explicit null check for variable indices or large constant indices/offsets
+ # as it must have been done earlier.
+#if defined(_MIPS_ARCH_MIPS32R6)
+ lapc $gp, .Lintrospection_exits # $gp = address of .Lintrospection_exits.
+#else
+ addiu $gp, $t9, (.Lintrospection_exits - 1b) # $gp = address of .Lintrospection_exits.
+#endif
+ .set push
+ .set noat
+ lw $at, MIRROR_OBJECT_LOCK_WORD_OFFSET(\obj)
+ sll $at, $at, 31 - LOCK_WORD_READ_BARRIER_STATE_SHIFT # Move barrier state bit
+ # to sign bit.
+ bltz $at, .Lintrospection_field_array # If gray, load reference, mark.
+ nop
+ .set pop
+ jalr $zero, $ra # Otherwise, load-load barrier and return.
+ sync
+ break # Padding to 8 instructions.
+.endm
+
+.macro BRB_GC_ROOT_ENTRY root
+1:
+#if defined(_MIPS_ARCH_MIPS32R6)
+ lapc $gp, .Lintrospection_exit_\root # $gp = exit point address.
+#else
+ addiu $gp, $t9, (.Lintrospection_exit_\root - 1b) # $gp = exit point address.
+#endif
+ bnez \root, .Lintrospection_common
+ move $t8, \root # Move reference to $t8 for common code.
+ jalr $zero, $ra # Return if null.
+ # The next instruction (from the following BRB_GC_ROOT_ENTRY) fills the delay slot.
+ # This instruction has no effect (actual NOP for the last entry; otherwise changes $gp,
+ # which is unused after that anyway).
+.endm
+
+.macro BRB_FIELD_EXIT out
+.Lintrospection_exit_\out:
+ jalr $zero, $ra
+ move \out, $t8 # Return reference in expected register.
+.endm
+
+.macro BRB_FIELD_EXIT_BREAK
+ break
+ break
+.endm
+
+ENTRY_NO_GP art_quick_read_barrier_mark_introspection
+ # Entry points for offsets/indices not fitting into int16_t and for variable indices.
+ BRB_FIELD_LONG_OFFSET_ENTRY $v0
+ BRB_FIELD_LONG_OFFSET_ENTRY $v1
+ BRB_FIELD_LONG_OFFSET_ENTRY $a0
+ BRB_FIELD_LONG_OFFSET_ENTRY $a1
+ BRB_FIELD_LONG_OFFSET_ENTRY $a2
+ BRB_FIELD_LONG_OFFSET_ENTRY $a3
+ BRB_FIELD_LONG_OFFSET_ENTRY $t0
+ BRB_FIELD_LONG_OFFSET_ENTRY $t1
+ BRB_FIELD_LONG_OFFSET_ENTRY $t2
+ BRB_FIELD_LONG_OFFSET_ENTRY $t3
+ BRB_FIELD_LONG_OFFSET_ENTRY $t4
+ BRB_FIELD_LONG_OFFSET_ENTRY $t5
+ BRB_FIELD_LONG_OFFSET_ENTRY $t6
+ BRB_FIELD_LONG_OFFSET_ENTRY $t7
+ BRB_FIELD_LONG_OFFSET_ENTRY $s2
+ BRB_FIELD_LONG_OFFSET_ENTRY $s3
+ BRB_FIELD_LONG_OFFSET_ENTRY $s4
+ BRB_FIELD_LONG_OFFSET_ENTRY $s5
+ BRB_FIELD_LONG_OFFSET_ENTRY $s6
+ BRB_FIELD_LONG_OFFSET_ENTRY $s7
+ BRB_FIELD_LONG_OFFSET_ENTRY $s8
+
+ # Entry points for offsets/indices fitting into int16_t.
+ BRB_FIELD_SHORT_OFFSET_ENTRY $v0
+ BRB_FIELD_SHORT_OFFSET_ENTRY $v1
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a0
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a1
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a2
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a3
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t0
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t1
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t2
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t3
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t4
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t5
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t6
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t7
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s2
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s3
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s4
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s5
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s6
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s7
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s8
+
+ .global art_quick_read_barrier_mark_introspection_gc_roots
+art_quick_read_barrier_mark_introspection_gc_roots:
+ # Entry points for GC roots.
+ BRB_GC_ROOT_ENTRY $v0
+ BRB_GC_ROOT_ENTRY $v1
+ BRB_GC_ROOT_ENTRY $a0
+ BRB_GC_ROOT_ENTRY $a1
+ BRB_GC_ROOT_ENTRY $a2
+ BRB_GC_ROOT_ENTRY $a3
+ BRB_GC_ROOT_ENTRY $t0
+ BRB_GC_ROOT_ENTRY $t1
+ BRB_GC_ROOT_ENTRY $t2
+ BRB_GC_ROOT_ENTRY $t3
+ BRB_GC_ROOT_ENTRY $t4
+ BRB_GC_ROOT_ENTRY $t5
+ BRB_GC_ROOT_ENTRY $t6
+ BRB_GC_ROOT_ENTRY $t7
+ BRB_GC_ROOT_ENTRY $s2
+ BRB_GC_ROOT_ENTRY $s3
+ BRB_GC_ROOT_ENTRY $s4
+ BRB_GC_ROOT_ENTRY $s5
+ BRB_GC_ROOT_ENTRY $s6
+ BRB_GC_ROOT_ENTRY $s7
+ BRB_GC_ROOT_ENTRY $s8
+ .global art_quick_read_barrier_mark_introspection_end_of_entries
+art_quick_read_barrier_mark_introspection_end_of_entries:
+ nop # Fill the delay slot of the last BRB_GC_ROOT_ENTRY.
+
+.Lintrospection_throw_npe:
+ b art_quick_throw_null_pointer_exception
+ addiu $ra, $ra, 4 # Skip lw, make $ra point to lw's stack map.
+
+ .set push
+ .set noat
+
+ // Fields and array elements.
+
+.Lintrospection_field_array:
+ // Get the field/element address using $t8 and the offset from the lw instruction.
+ lh $at, 0($ra) # $ra points to lw: $at = field/element offset.
+ addiu $ra, $ra, 4 + HEAP_POISON_INSTR_SIZE # Skip lw(+subu).
+ addu $t8, $t8, $at # $t8 = field/element address.
+
+ // Calculate the address of the exit point, store it in $gp and load the reference into $t8.
+ lb $at, (-HEAP_POISON_INSTR_SIZE - 2)($ra) # $ra-HEAP_POISON_INSTR_SIZE-4 points to
+ # "lw `out`, ...".
+ andi $at, $at, 31 # Extract `out` from lw.
+ sll $at, $at, 3 # Multiply `out` by the exit point size (BRB_FIELD_EXIT* macros).
+
+ lw $t8, 0($t8) # $t8 = reference.
+ UNPOISON_HEAP_REF $t8
+
+ // Return if null reference.
+ bnez $t8, .Lintrospection_common
+ addu $gp, $gp, $at # $gp = address of the exit point.
+
+ // Early return through the exit point.
+.Lintrospection_return_early:
+ jalr $zero, $gp # Move $t8 to `out` and return.
+ nop
+
+ // Code common for GC roots, fields and array elements.
+
+.Lintrospection_common:
+ // Check lock word for mark bit, if marked return.
+ lw $t9, MIRROR_OBJECT_LOCK_WORD_OFFSET($t8)
+ sll $at, $t9, 31 - LOCK_WORD_MARK_BIT_SHIFT # Move mark bit to sign bit.
+ bltz $at, .Lintrospection_return_early
+#if (LOCK_WORD_STATE_SHIFT != 30) || (LOCK_WORD_STATE_FORWARDING_ADDRESS != 3)
+ // The below code depends on the lock word state being in the highest bits
+ // and the "forwarding address" state having all bits set.
+#error "Unexpected lock word state shift or forwarding address state value."
+#endif
+ // Test that both the forwarding state bits are 1.
+ sll $at, $t9, 1
+ and $at, $at, $t9 # Sign bit = 1 IFF both bits are 1.
+ bgez $at, .Lintrospection_mark
+ nop
+
+ .set pop
+
+ // Shift left by the forwarding address shift. This clears out the state bits since they are
+ // in the top 2 bits of the lock word.
+ jalr $zero, $gp # Move $t8 to `out` and return.
+ sll $t8, $t9, LOCK_WORD_STATE_FORWARDING_ADDRESS_SHIFT
+
+.Lintrospection_mark:
+ // Partially set up the stack frame preserving only $ra.
+ addiu $sp, $sp, -160 # Includes 16 bytes of space for argument registers $a0-$a3.
+ .cfi_adjust_cfa_offset 160
+ sw $ra, 156($sp)
+ .cfi_rel_offset 31, 156
+
+ // Set up $gp, clobbering $ra and using the branch delay slot for a useful instruction.
+ bal 1f
+ sw $gp, 152($sp) # Preserve the exit point address.
+1:
+ .cpload $ra
+
+ // Finalize the stack frame and call.
+ sw $t7, 148($sp)
+ .cfi_rel_offset 15, 148
+ sw $t6, 144($sp)
+ .cfi_rel_offset 14, 144
+ sw $t5, 140($sp)
+ .cfi_rel_offset 13, 140
+ sw $t4, 136($sp)
+ .cfi_rel_offset 12, 136
+ sw $t3, 132($sp)
+ .cfi_rel_offset 11, 132
+ sw $t2, 128($sp)
+ .cfi_rel_offset 10, 128
+ sw $t1, 124($sp)
+ .cfi_rel_offset 9, 124
+ sw $t0, 120($sp)
+ .cfi_rel_offset 8, 120
+ sw $a3, 116($sp)
+ .cfi_rel_offset 7, 116
+ sw $a2, 112($sp)
+ .cfi_rel_offset 6, 112
+ sw $a1, 108($sp)
+ .cfi_rel_offset 5, 108
+ sw $a0, 104($sp)
+ .cfi_rel_offset 4, 104
+ sw $v1, 100($sp)
+ .cfi_rel_offset 3, 100
+ sw $v0, 96($sp)
+ .cfi_rel_offset 2, 96
+
+ la $t9, artReadBarrierMark
+
+ sdc1 $f18, 88($sp)
+ sdc1 $f16, 80($sp)
+ sdc1 $f14, 72($sp)
+ sdc1 $f12, 64($sp)
+ sdc1 $f10, 56($sp)
+ sdc1 $f8, 48($sp)
+ sdc1 $f6, 40($sp)
+ sdc1 $f4, 32($sp)
+ sdc1 $f2, 24($sp)
+ sdc1 $f0, 16($sp)
+
+ jalr $t9 # $v0 <- artReadBarrierMark(reference)
+ move $a0, $t8 # Pass reference in $a0.
+ move $t8, $v0
+
+ lw $ra, 156($sp)
+ .cfi_restore 31
+ lw $gp, 152($sp) # $gp = address of the exit point.
+ lw $t7, 148($sp)
+ .cfi_restore 15
+ lw $t6, 144($sp)
+ .cfi_restore 14
+ lw $t5, 140($sp)
+ .cfi_restore 13
+ lw $t4, 136($sp)
+ .cfi_restore 12
+ lw $t3, 132($sp)
+ .cfi_restore 11
+ lw $t2, 128($sp)
+ .cfi_restore 10
+ lw $t1, 124($sp)
+ .cfi_restore 9
+ lw $t0, 120($sp)
+ .cfi_restore 8
+ lw $a3, 116($sp)
+ .cfi_restore 7
+ lw $a2, 112($sp)
+ .cfi_restore 6
+ lw $a1, 108($sp)
+ .cfi_restore 5
+ lw $a0, 104($sp)
+ .cfi_restore 4
+ lw $v1, 100($sp)
+ .cfi_restore 3
+ lw $v0, 96($sp)
+ .cfi_restore 2
+
+ ldc1 $f18, 88($sp)
+ ldc1 $f16, 80($sp)
+ ldc1 $f14, 72($sp)
+ ldc1 $f12, 64($sp)
+ ldc1 $f10, 56($sp)
+ ldc1 $f8, 48($sp)
+ ldc1 $f6, 40($sp)
+ ldc1 $f4, 32($sp)
+ ldc1 $f2, 24($sp)
+ ldc1 $f0, 16($sp)
+
+ // Return through the exit point.
+ jalr $zero, $gp # Move $t8 to `out` and return.
+ addiu $sp, $sp, 160
+ .cfi_adjust_cfa_offset -160
+
+.Lintrospection_exits:
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT $v0
+ BRB_FIELD_EXIT $v1
+ BRB_FIELD_EXIT $a0
+ BRB_FIELD_EXIT $a1
+ BRB_FIELD_EXIT $a2
+ BRB_FIELD_EXIT $a3
+ BRB_FIELD_EXIT $t0
+ BRB_FIELD_EXIT $t1
+ BRB_FIELD_EXIT $t2
+ BRB_FIELD_EXIT $t3
+ BRB_FIELD_EXIT $t4
+ BRB_FIELD_EXIT $t5
+ BRB_FIELD_EXIT $t6
+ BRB_FIELD_EXIT $t7
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT $s2
+ BRB_FIELD_EXIT $s3
+ BRB_FIELD_EXIT $s4
+ BRB_FIELD_EXIT $s5
+ BRB_FIELD_EXIT $s6
+ BRB_FIELD_EXIT $s7
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT $s8
+ BRB_FIELD_EXIT_BREAK
+END art_quick_read_barrier_mark_introspection
+
.extern artInvokePolymorphic
ENTRY art_quick_invoke_polymorphic
SETUP_SAVE_REFS_AND_ARGS_FRAME
diff --git a/runtime/arch/mips64/asm_support_mips64.S b/runtime/arch/mips64/asm_support_mips64.S
index ef82bd2..a6b249a 100644
--- a/runtime/arch/mips64/asm_support_mips64.S
+++ b/runtime/arch/mips64/asm_support_mips64.S
@@ -83,6 +83,13 @@
#endif // USE_HEAP_POISONING
.endm
+// Byte size of the instructions (un)poisoning heap references.
+#ifdef USE_HEAP_POISONING
+#define HEAP_POISON_INSTR_SIZE 8
+#else
+#define HEAP_POISON_INSTR_SIZE 0
+#endif // USE_HEAP_POISONING
+
// Based on contents of creg select the minimum integer
// At the end of the macro the original value of creg is lost
.macro MINint dreg,rreg,sreg,creg
diff --git a/runtime/arch/mips64/asm_support_mips64.h b/runtime/arch/mips64/asm_support_mips64.h
index 9063d20..7185da5 100644
--- a/runtime/arch/mips64/asm_support_mips64.h
+++ b/runtime/arch/mips64/asm_support_mips64.h
@@ -28,4 +28,24 @@
// $f0-$f31, $at, $v0-$v1, $a0-$a7, $t0-$t3, $s0-$s7, $t8-$t9, $gp, $s8, $ra + padding + method*
#define FRAME_SIZE_SAVE_EVERYTHING 496
+// &art_quick_read_barrier_mark_introspection is the first of many entry points:
+// 20 entry points for long field offsets, large array indices and variable array indices
+// (see macro BRB_FIELD_LONG_OFFSET_ENTRY)
+// 20 entry points for short field offsets and small array indices
+// (see macro BRB_FIELD_SHORT_OFFSET_ENTRY)
+// 20 entry points for GC roots
+// (see macro BRB_GC_ROOT_ENTRY)
+
+// There are as many entry points of each kind as there are registers that
+// can hold a reference: V0-V1, A0-A7, T0-T2, S2-S8.
+#define BAKER_MARK_INTROSPECTION_REGISTER_COUNT 20
+
+#define BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE (8 * 4) // 8 instructions in
+ // BRB_FIELD_*_OFFSET_ENTRY.
+
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET \
+ (2 * BAKER_MARK_INTROSPECTION_REGISTER_COUNT * BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE)
+
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE (4 * 4) // 4 instructions in BRB_GC_ROOT_ENTRY.
+
#endif // ART_RUNTIME_ARCH_MIPS64_ASM_SUPPORT_MIPS64_H_
diff --git a/runtime/arch/mips64/entrypoints_init_mips64.cc b/runtime/arch/mips64/entrypoints_init_mips64.cc
index 007f7b3..5e58827 100644
--- a/runtime/arch/mips64/entrypoints_init_mips64.cc
+++ b/runtime/arch/mips64/entrypoints_init_mips64.cc
@@ -17,6 +17,7 @@
#include <math.h>
#include <string.h>
+#include "arch/mips64/asm_support_mips64.h"
#include "atomic.h"
#include "entrypoints/jni/jni_entrypoints.h"
#include "entrypoints/quick/quick_alloc_entrypoints.h"
@@ -59,6 +60,10 @@
extern "C" mirror::Object* art_quick_read_barrier_mark_reg22(mirror::Object*);
extern "C" mirror::Object* art_quick_read_barrier_mark_reg29(mirror::Object*);
+extern "C" mirror::Object* art_quick_read_barrier_mark_introspection(mirror::Object*);
+extern "C" mirror::Object* art_quick_read_barrier_mark_introspection_gc_roots(mirror::Object*);
+extern "C" void art_quick_read_barrier_mark_introspection_end_of_entries(void);
+
// Math entrypoints.
extern int32_t CmpgDouble(double a, double b);
extern int32_t CmplDouble(double a, double b);
@@ -88,6 +93,21 @@
// No read barrier entrypoints for marking registers.
void UpdateReadBarrierEntrypoints(QuickEntryPoints* qpoints, bool is_active) {
+ intptr_t introspection_field_array_entries_size =
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection_gc_roots) -
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection);
+ static_assert(
+ BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET == 2 *
+ BAKER_MARK_INTROSPECTION_REGISTER_COUNT * BAKER_MARK_INTROSPECTION_FIELD_ARRAY_ENTRY_SIZE,
+ "Expecting equal");
+ DCHECK_EQ(introspection_field_array_entries_size,
+ BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRIES_OFFSET);
+ intptr_t introspection_gc_root_entries_size =
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection_end_of_entries) -
+ reinterpret_cast<intptr_t>(&art_quick_read_barrier_mark_introspection_gc_roots);
+ DCHECK_EQ(introspection_gc_root_entries_size,
+ BAKER_MARK_INTROSPECTION_REGISTER_COUNT * BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRY_SIZE);
+ qpoints->pReadBarrierMarkReg00 = is_active ? art_quick_read_barrier_mark_introspection : nullptr;
qpoints->pReadBarrierMarkReg01 = is_active ? art_quick_read_barrier_mark_reg01 : nullptr;
qpoints->pReadBarrierMarkReg02 = is_active ? art_quick_read_barrier_mark_reg02 : nullptr;
qpoints->pReadBarrierMarkReg03 = is_active ? art_quick_read_barrier_mark_reg03 : nullptr;
@@ -173,7 +193,6 @@
// Cannot use the following registers to pass arguments:
// 0(ZERO), 1(AT), 15(T3), 16(S0), 17(S1), 24(T8), 25(T9), 26(K0), 27(K1), 28(GP), 29(SP), 31(RA).
// Note that there are 30 entry points only: 00 for register 1(AT), ..., 29 for register 30(S8).
- qpoints->pReadBarrierMarkReg00 = nullptr;
qpoints->pReadBarrierMarkReg14 = nullptr;
qpoints->pReadBarrierMarkReg15 = nullptr;
qpoints->pReadBarrierMarkReg16 = nullptr;
diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S
index d427fe3..3b92daa 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -188,7 +188,23 @@
// This assumes the top part of these stack frame types are identical.
#define REFS_AND_ARGS_MINUS_REFS_SIZE (FRAME_SIZE_SAVE_REFS_AND_ARGS - FRAME_SIZE_SAVE_REFS_ONLY)
-.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
+ /*
+ * Individually usable part of macro SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL.
+ */
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_S4_THRU_S8
+ sd $s8, 192($sp)
+ .cfi_rel_offset 30, 192
+ sd $s7, 176($sp)
+ .cfi_rel_offset 23, 176
+ sd $s6, 168($sp)
+ .cfi_rel_offset 22, 168
+ sd $s5, 160($sp)
+ .cfi_rel_offset 21, 160
+ sd $s4, 152($sp)
+ .cfi_rel_offset 20, 152
+.endm
+
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL save_s4_thru_s8=1
daddiu $sp, $sp, -208
.cfi_adjust_cfa_offset 208
@@ -197,48 +213,40 @@
#error "FRAME_SIZE_SAVE_REFS_AND_ARGS(MIPS64) size not as expected."
#endif
- sd $ra, 200($sp) # = kQuickCalleeSaveFrame_RefAndArgs_LrOffset
+ sd $ra, 200($sp) # = kQuickCalleeSaveFrame_RefAndArgs_LrOffset
.cfi_rel_offset 31, 200
- sd $s8, 192($sp)
- .cfi_rel_offset 30, 192
- sd $t8, 184($sp) # t8 holds caller's gp, now save it to the stack.
- .cfi_rel_offset 28, 184 # Value from gp is pushed, so set the cfi offset accordingly.
- sd $s7, 176($sp)
- .cfi_rel_offset 23, 176
- sd $s6, 168($sp)
- .cfi_rel_offset 22, 168
- sd $s5, 160($sp)
- .cfi_rel_offset 21, 160
- sd $s4, 152($sp)
- .cfi_rel_offset 20, 152
- sd $s3, 144($sp)
+ sd $t8, 184($sp) # t8 holds caller's gp, now save it to the stack.
+ .cfi_rel_offset 28, 184 # Value from gp is pushed, so set the cfi offset accordingly.
+ .if \save_s4_thru_s8
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_S4_THRU_S8
+ .endif
+ sd $s3, 144($sp)
.cfi_rel_offset 19, 144
- sd $s2, 136($sp)
+ sd $s2, 136($sp)
.cfi_rel_offset 18, 136
-
- sd $a7, 128($sp)
+ sd $a7, 128($sp)
.cfi_rel_offset 11, 128
- sd $a6, 120($sp)
+ sd $a6, 120($sp)
.cfi_rel_offset 10, 120
- sd $a5, 112($sp)
+ sd $a5, 112($sp)
.cfi_rel_offset 9, 112
- sd $a4, 104($sp)
+ sd $a4, 104($sp)
.cfi_rel_offset 8, 104
- sd $a3, 96($sp)
+ sd $a3, 96($sp)
.cfi_rel_offset 7, 96
- sd $a2, 88($sp)
+ sd $a2, 88($sp)
.cfi_rel_offset 6, 88
- sd $a1, 80($sp) # = kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset
+ sd $a1, 80($sp) # = kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset
.cfi_rel_offset 5, 80
- s.d $f19, 72($sp)
- s.d $f18, 64($sp)
- s.d $f17, 56($sp)
- s.d $f16, 48($sp)
- s.d $f15, 40($sp)
- s.d $f14, 32($sp)
- s.d $f13, 24($sp) # = kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset
- s.d $f12, 16($sp) # This isn't necessary to store.
+ s.d $f19, 72($sp)
+ s.d $f18, 64($sp)
+ s.d $f17, 56($sp)
+ s.d $f16, 48($sp)
+ s.d $f15, 40($sp)
+ s.d $f14, 32($sp)
+ s.d $f13, 24($sp) # = kQuickCalleeSaveFrame_RefAndArgs_Fpr1Offset
+ s.d $f12, 16($sp) # This isn't necessary to store.
# 1x8 bytes padding + Method*
.endm
@@ -248,8 +256,14 @@
* non-moving GC.
* callee-save: padding + $f12-$f19 + $a1-$a7 + $s2-$s7 + $gp + $ra + $s8 = 24 total + 1 words padding + Method*
*/
-.macro SETUP_SAVE_REFS_AND_ARGS_FRAME
- SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
+.macro SETUP_SAVE_REFS_AND_ARGS_FRAME save_s4_thru_s8_only=0
+ .if \save_s4_thru_s8_only
+ // It is expected that `SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL /* save_s4_thru_s8 */ 0`
+ // has been done prior to `SETUP_SAVE_REFS_AND_ARGS_FRAME /* save_s4_thru_s8_only */ 1`.
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_S4_THRU_S8
+ .else
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
+ .endif
# load appropriate callee-save-method
ld $t1, %got(_ZN3art7Runtime9instance_E)($gp)
ld $t1, 0($t1)
@@ -264,52 +278,62 @@
sd $sp, THREAD_TOP_QUICK_FRAME_OFFSET(rSELF) # Place sp in Thread::Current()->top_quick_frame.
.endm
-.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME
- ld $ra, 200($sp)
- .cfi_restore 31
- ld $s8, 192($sp)
- .cfi_restore 30
- ld $t8, 184($sp) # Restore gp back to it's temp storage.
- .cfi_restore 28
- ld $s7, 176($sp)
- .cfi_restore 23
- ld $s6, 168($sp)
- .cfi_restore 22
- ld $s5, 160($sp)
- .cfi_restore 21
- ld $s4, 152($sp)
- .cfi_restore 20
- ld $s3, 144($sp)
- .cfi_restore 19
- ld $s2, 136($sp)
- .cfi_restore 18
-
- ld $a7, 128($sp)
- .cfi_restore 11
- ld $a6, 120($sp)
- .cfi_restore 10
- ld $a5, 112($sp)
- .cfi_restore 9
- ld $a4, 104($sp)
- .cfi_restore 8
- ld $a3, 96($sp)
- .cfi_restore 7
- ld $a2, 88($sp)
- .cfi_restore 6
- ld $a1, 80($sp)
+ /*
+ * Individually usable part of macro RESTORE_SAVE_REFS_AND_ARGS_FRAME.
+ */
+.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME_A1
+ ld $a1, 80($sp)
.cfi_restore 5
+.endm
- l.d $f19, 72($sp)
- l.d $f18, 64($sp)
- l.d $f17, 56($sp)
- l.d $f16, 48($sp)
- l.d $f15, 40($sp)
- l.d $f14, 32($sp)
- l.d $f13, 24($sp)
- l.d $f12, 16($sp)
+.macro RESTORE_SAVE_REFS_AND_ARGS_FRAME restore_s4_thru_s8=1
+ ld $ra, 200($sp)
+ .cfi_restore 31
+ .if \restore_s4_thru_s8
+ ld $s8, 192($sp)
+ .cfi_restore 30
+ .endif
+ ld $t8, 184($sp) # Restore gp back to it's temp storage.
+ .cfi_restore 28
+ .if \restore_s4_thru_s8
+ ld $s7, 176($sp)
+ .cfi_restore 23
+ ld $s6, 168($sp)
+ .cfi_restore 22
+ ld $s5, 160($sp)
+ .cfi_restore 21
+ ld $s4, 152($sp)
+ .cfi_restore 20
+ .endif
+ ld $s3, 144($sp)
+ .cfi_restore 19
+ ld $s2, 136($sp)
+ .cfi_restore 18
+ ld $a7, 128($sp)
+ .cfi_restore 11
+ ld $a6, 120($sp)
+ .cfi_restore 10
+ ld $a5, 112($sp)
+ .cfi_restore 9
+ ld $a4, 104($sp)
+ .cfi_restore 8
+ ld $a3, 96($sp)
+ .cfi_restore 7
+ ld $a2, 88($sp)
+ .cfi_restore 6
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME_A1
+
+ l.d $f19, 72($sp)
+ l.d $f18, 64($sp)
+ l.d $f17, 56($sp)
+ l.d $f16, 48($sp)
+ l.d $f15, 40($sp)
+ l.d $f14, 32($sp)
+ l.d $f13, 24($sp)
+ l.d $f12, 16($sp)
.cpreturn
- daddiu $sp, $sp, 208
+ daddiu $sp, $sp, 208
.cfi_adjust_cfa_offset -208
.endm
@@ -847,7 +871,7 @@
dla $t9, artThrowNullPointerExceptionFromSignal
jalr $zero, $t9 # artThrowNullPointerExceptionFromSignal(uinptr_t, Thread*)
move $a1, rSELF # pass Thread::Current
-END art_quick_throw_null_pointer_exception
+END art_quick_throw_null_pointer_exception_from_signal
/*
* Called by managed code to create and deliver an ArithmeticException
@@ -913,9 +937,10 @@
* On success this wrapper will restore arguments and *jump* to the target, leaving the ra
* pointing back to the original caller.
*/
-.macro INVOKE_TRAMPOLINE_BODY cxx_name
+.macro INVOKE_TRAMPOLINE_BODY cxx_name, save_s4_thru_s8_only=0
.extern \cxx_name
- SETUP_SAVE_REFS_AND_ARGS_FRAME # save callee saves in case allocation triggers GC
+ SETUP_SAVE_REFS_AND_ARGS_FRAME \save_s4_thru_s8_only # save callee saves in case
+ # allocation triggers GC
move $a2, rSELF # pass Thread::Current
jal \cxx_name # (method_idx, this, Thread*, $sp)
move $a3, $sp # pass $sp
@@ -1986,38 +2011,69 @@
* a0 is the conflict ArtMethod.
* t0 is a hidden argument that holds the target interface method's dex method index.
*
- * Mote that this stub writes to a0, t0 and t1.
+ * Mote that this stub writes to v0-v1, a0, t0-t3, t8-t9, f0-f11, f20-f23.
*/
+ .extern artLookupResolvedMethod
+ .extern __atomic_load_16 # For __int128_t std::atomic::load(std::memory_order).
ENTRY art_quick_imt_conflict_trampoline
- ld $t1, 0($sp) # Load referrer.
- ld $t1, ART_METHOD_DEX_CACHE_METHODS_OFFSET_64($t1) # Load dex cache methods array.
- dsll $t0, $t0, POINTER_SIZE_SHIFT # Calculate offset.
- daddu $t0, $t1, $t0 # Add offset to base.
- ld $t0, 0($t0) # Load interface method.
- ld $a0, ART_METHOD_JNI_OFFSET_64($a0) # Load ImtConflictTable.
+ SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL /* save_s4_thru_s8 */ 0
+
+ ld $t1, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $t1 = referrer.
+ dla $t9, __atomic_load_16
+ ld $t1, ART_METHOD_DEX_CACHE_METHODS_OFFSET_64($t1) # $t1 = dex cache methods array.
+
+ dext $s2, $t0, 0, 32 # $s2 = zero-extended method index
+ # (callee-saved).
+ ld $s3, ART_METHOD_JNI_OFFSET_64($a0) # $s3 = ImtConflictTable (callee-saved).
+
+ dext $t0, $t0, 0, METHOD_DEX_CACHE_HASH_BITS # $t0 = slot index.
+
+ li $a1, STD_MEMORY_ORDER_RELAXED # $a1 = std::memory_order_relaxed.
+ jalr $t9 # [$v0, $v1] = __atomic_load_16($a0, $a1).
+ dlsa $a0, $t0, $t1, POINTER_SIZE_SHIFT + 1 # $a0 = DexCache method slot address.
+
+ bnec $v1, $s2, .Limt_conflict_trampoline_dex_cache_miss # Branch if method index miss.
.Limt_table_iterate:
- ld $t1, 0($a0) # Load next entry in ImtConflictTable.
+ ld $t1, 0($s3) # Load next entry in ImtConflictTable.
# Branch if found.
- beq $t1, $t0, .Limt_table_found
+ beq $t1, $v0, .Limt_table_found
nop
# If the entry is null, the interface method is not in the ImtConflictTable.
beqzc $t1, .Lconflict_trampoline
# Iterate over the entries of the ImtConflictTable.
- daddiu $a0, $a0, 2 * __SIZEOF_POINTER__ # Iterate to the next entry.
- bc .Limt_table_iterate
+ daddiu $s3, $s3, 2 * __SIZEOF_POINTER__ # Iterate to the next entry.
+ bc .Limt_table_iterate
.Limt_table_found:
# We successfully hit an entry in the table. Load the target method and jump to it.
- ld $a0, __SIZEOF_POINTER__($a0)
+ .cfi_remember_state
+ ld $a0, __SIZEOF_POINTER__($s3)
ld $t9, ART_METHOD_QUICK_CODE_OFFSET_64($a0)
- jr $t9
- .cpreturn # Restore gp from t8 in branch delay slot.
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME /* restore_s4_thru_s8 */ 0
+ jic $t9, 0
+ .cfi_restore_state
.Lconflict_trampoline:
# Call the runtime stub to populate the ImtConflictTable and jump to the resolved method.
- move $a0, $t0 # Load interface method.
- INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline
+ .cfi_remember_state
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME_A1 # Restore this.
+ move $a0, $v0 # Load interface method.
+ INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline, /* save_s4_thru_s8_only */ 1
+ .cfi_restore_state
+
+.Limt_conflict_trampoline_dex_cache_miss:
+ # We're not creating a proper runtime method frame here,
+ # artLookupResolvedMethod() is not allowed to walk the stack.
+ dla $t9, artLookupResolvedMethod
+ ld $a1, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $a1 = referrer.
+ jalr $t9 # (uint32_t method_index, ArtMethod* referrer).
+ sll $a0, $s2, 0 # $a0 = sign-extended method index.
+
+ # If the method wasn't resolved, skip the lookup and go to artInvokeInterfaceTrampoline().
+ beqzc $v0, .Lconflict_trampoline
+ nop
+ bc .Limt_table_iterate
END art_quick_imt_conflict_trampoline
.extern artQuickResolutionTrampoline
@@ -2567,6 +2623,375 @@
READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg29, $s8
// RA (register 31) is reserved.
+// Caller code:
+// Short constant offset/index:
+// ld $t9, pReadBarrierMarkReg00
+// beqzc $t9, skip_call
+// nop
+// jialc $t9, thunk_disp
+// skip_call:
+// lwu `out`, ofs(`obj`)
+// [dsubu `out`, $zero, `out`
+// dext `out`, `out`, 0, 32] # Unpoison reference.
+.macro BRB_FIELD_SHORT_OFFSET_ENTRY obj
+ # Explicit null check. May be redundant (for array elements or when the field
+ # offset is larger than the page size, 4KB).
+ # $ra will be adjusted to point to lwu's stack map when throwing NPE.
+ beqzc \obj, .Lintrospection_throw_npe
+ lapc $t3, .Lintrospection_exits # $t3 = address of .Lintrospection_exits.
+ .set push
+ .set noat
+ lw $at, MIRROR_OBJECT_LOCK_WORD_OFFSET(\obj)
+ sll $at, $at, 31 - LOCK_WORD_READ_BARRIER_STATE_SHIFT # Move barrier state bit
+ # to sign bit.
+ bltz $at, .Lintrospection_field_array # If gray, load reference, mark.
+ move $t8, \obj # Move `obj` to $t8 for common code.
+ .set pop
+ jalr $zero, $ra # Otherwise, load-load barrier and return.
+ sync
+.endm
+
+// Caller code:
+// Long constant offset/index: | Variable index:
+// ld $t9, pReadBarrierMarkReg00
+// beqz $t9, skip_call | beqz $t9, skip_call
+// daui $t8, `obj`, ofs_hi | dlsa $t8, `index`, `obj`, 2
+// jialc $t9, thunk_disp | jialc $t9, thunk_disp
+// skip_call: | skip_call:
+// lwu `out`, ofs_lo($t8) | lwu `out`, ofs($t8)
+// [dsubu `out`, $zero, `out` | [dsubu `out`, $zero, `out`
+// dext `out`, `out`, 0, 32] | dext `out`, `out`, 0, 32] # Unpoison reference.
+.macro BRB_FIELD_LONG_OFFSET_ENTRY obj
+ # No explicit null check for variable indices or large constant indices/offsets
+ # as it must have been done earlier.
+ lapc $t3, .Lintrospection_exits # $t3 = address of .Lintrospection_exits.
+ .set push
+ .set noat
+ lw $at, MIRROR_OBJECT_LOCK_WORD_OFFSET(\obj)
+ sll $at, $at, 31 - LOCK_WORD_READ_BARRIER_STATE_SHIFT # Move barrier state bit
+ # to sign bit.
+ bltzc $at, .Lintrospection_field_array # If gray, load reference, mark.
+ .set pop
+ sync # Otherwise, load-load barrier and return.
+ jic $ra, 0
+ break # Padding to 8 instructions.
+ break
+.endm
+
+.macro BRB_GC_ROOT_ENTRY root
+ lapc $t3, .Lintrospection_exit_\root # $t3 = exit point address.
+ bnez \root, .Lintrospection_common
+ move $t8, \root # Move reference to $t8 for common code.
+ jic $ra, 0 # Return if null.
+.endm
+
+.macro BRB_FIELD_EXIT out
+.Lintrospection_exit_\out:
+ jalr $zero, $ra
+ move \out, $t8 # Return reference in expected register.
+.endm
+
+.macro BRB_FIELD_EXIT_BREAK
+ break
+ break
+.endm
+
+ENTRY_NO_GP art_quick_read_barrier_mark_introspection
+ # Entry points for offsets/indices not fitting into int16_t and for variable indices.
+ BRB_FIELD_LONG_OFFSET_ENTRY $v0
+ BRB_FIELD_LONG_OFFSET_ENTRY $v1
+ BRB_FIELD_LONG_OFFSET_ENTRY $a0
+ BRB_FIELD_LONG_OFFSET_ENTRY $a1
+ BRB_FIELD_LONG_OFFSET_ENTRY $a2
+ BRB_FIELD_LONG_OFFSET_ENTRY $a3
+ BRB_FIELD_LONG_OFFSET_ENTRY $a4
+ BRB_FIELD_LONG_OFFSET_ENTRY $a5
+ BRB_FIELD_LONG_OFFSET_ENTRY $a6
+ BRB_FIELD_LONG_OFFSET_ENTRY $a7
+ BRB_FIELD_LONG_OFFSET_ENTRY $t0
+ BRB_FIELD_LONG_OFFSET_ENTRY $t1
+ BRB_FIELD_LONG_OFFSET_ENTRY $t2
+ BRB_FIELD_LONG_OFFSET_ENTRY $s2
+ BRB_FIELD_LONG_OFFSET_ENTRY $s3
+ BRB_FIELD_LONG_OFFSET_ENTRY $s4
+ BRB_FIELD_LONG_OFFSET_ENTRY $s5
+ BRB_FIELD_LONG_OFFSET_ENTRY $s6
+ BRB_FIELD_LONG_OFFSET_ENTRY $s7
+ BRB_FIELD_LONG_OFFSET_ENTRY $s8
+
+ # Entry points for offsets/indices fitting into int16_t.
+ BRB_FIELD_SHORT_OFFSET_ENTRY $v0
+ BRB_FIELD_SHORT_OFFSET_ENTRY $v1
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a0
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a1
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a2
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a3
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a4
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a5
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a6
+ BRB_FIELD_SHORT_OFFSET_ENTRY $a7
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t0
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t1
+ BRB_FIELD_SHORT_OFFSET_ENTRY $t2
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s2
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s3
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s4
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s5
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s6
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s7
+ BRB_FIELD_SHORT_OFFSET_ENTRY $s8
+
+ .global art_quick_read_barrier_mark_introspection_gc_roots
+art_quick_read_barrier_mark_introspection_gc_roots:
+ # Entry points for GC roots.
+ BRB_GC_ROOT_ENTRY $v0
+ BRB_GC_ROOT_ENTRY $v1
+ BRB_GC_ROOT_ENTRY $a0
+ BRB_GC_ROOT_ENTRY $a1
+ BRB_GC_ROOT_ENTRY $a2
+ BRB_GC_ROOT_ENTRY $a3
+ BRB_GC_ROOT_ENTRY $a4
+ BRB_GC_ROOT_ENTRY $a5
+ BRB_GC_ROOT_ENTRY $a6
+ BRB_GC_ROOT_ENTRY $a7
+ BRB_GC_ROOT_ENTRY $t0
+ BRB_GC_ROOT_ENTRY $t1
+ BRB_GC_ROOT_ENTRY $t2
+ BRB_GC_ROOT_ENTRY $s2
+ BRB_GC_ROOT_ENTRY $s3
+ BRB_GC_ROOT_ENTRY $s4
+ BRB_GC_ROOT_ENTRY $s5
+ BRB_GC_ROOT_ENTRY $s6
+ BRB_GC_ROOT_ENTRY $s7
+ BRB_GC_ROOT_ENTRY $s8
+ .global art_quick_read_barrier_mark_introspection_end_of_entries
+art_quick_read_barrier_mark_introspection_end_of_entries:
+
+.Lintrospection_throw_npe:
+ b art_quick_throw_null_pointer_exception
+ daddiu $ra, $ra, 4 # Skip lwu, make $ra point to lwu's stack map.
+
+ .set push
+ .set noat
+
+ // Fields and array elements.
+
+.Lintrospection_field_array:
+ // Get the field/element address using $t8 and the offset from the lwu instruction.
+ lh $at, 0($ra) # $ra points to lwu: $at = low 16 bits of field/element offset.
+ daddiu $ra, $ra, 4 + HEAP_POISON_INSTR_SIZE # Skip lwu(+dsubu+dext).
+ daddu $t8, $t8, $at # $t8 = field/element address.
+
+ // Calculate the address of the exit point, store it in $t3 and load the reference into $t8.
+ lb $at, (-HEAP_POISON_INSTR_SIZE - 2)($ra) # $ra-HEAP_POISON_INSTR_SIZE-4 points to
+ # "lwu `out`, ...".
+ andi $at, $at, 31 # Extract `out` from lwu.
+
+ lwu $t8, 0($t8) # $t8 = reference.
+ UNPOISON_HEAP_REF $t8
+
+ // Return if null reference.
+ bnez $t8, .Lintrospection_common
+ dlsa $t3, $at, $t3, 3 # $t3 = address of the exit point
+ # (BRB_FIELD_EXIT* macro is 8 bytes).
+
+ // Early return through the exit point.
+.Lintrospection_return_early:
+ jic $t3, 0 # Move $t8 to `out` and return.
+
+ // Code common for GC roots, fields and array elements.
+
+.Lintrospection_common:
+ // Check lock word for mark bit, if marked return.
+ lw $t9, MIRROR_OBJECT_LOCK_WORD_OFFSET($t8)
+ sll $at, $t9, 31 - LOCK_WORD_MARK_BIT_SHIFT # Move mark bit to sign bit.
+ bltzc $at, .Lintrospection_return_early
+#if (LOCK_WORD_STATE_SHIFT != 30) || (LOCK_WORD_STATE_FORWARDING_ADDRESS != 3)
+ // The below code depends on the lock word state being in the highest bits
+ // and the "forwarding address" state having all bits set.
+#error "Unexpected lock word state shift or forwarding address state value."
+#endif
+ // Test that both the forwarding state bits are 1.
+ sll $at, $t9, 1
+ and $at, $at, $t9 # Sign bit = 1 IFF both bits are 1.
+ bgezc $at, .Lintrospection_mark
+
+ .set pop
+
+ // Shift left by the forwarding address shift. This clears out the state bits since they are
+ // in the top 2 bits of the lock word.
+ sll $t8, $t9, LOCK_WORD_STATE_FORWARDING_ADDRESS_SHIFT
+ jalr $zero, $t3 # Move $t8 to `out` and return.
+ dext $t8, $t8, 0, 32 # Make sure the address is zero-extended.
+
+.Lintrospection_mark:
+ // Partially set up the stack frame preserving only $ra.
+ daddiu $sp, $sp, -320
+ .cfi_adjust_cfa_offset 320
+ sd $ra, 312($sp)
+ .cfi_rel_offset 31, 312
+
+ // Set up $gp, clobbering $ra.
+ lapc $ra, 1f
+1:
+ .cpsetup $ra, 304, 1b # Save old $gp in 304($sp).
+
+ // Finalize the stack frame and call.
+ sd $t3, 296($sp) # Preserve the exit point address.
+ sd $t2, 288($sp)
+ .cfi_rel_offset 14, 288
+ sd $t1, 280($sp)
+ .cfi_rel_offset 13, 280
+ sd $t0, 272($sp)
+ .cfi_rel_offset 12, 272
+ sd $a7, 264($sp)
+ .cfi_rel_offset 11, 264
+ sd $a6, 256($sp)
+ .cfi_rel_offset 10, 256
+ sd $a5, 248($sp)
+ .cfi_rel_offset 9, 248
+ sd $a4, 240($sp)
+ .cfi_rel_offset 8, 240
+ sd $a3, 232($sp)
+ .cfi_rel_offset 7, 232
+ sd $a2, 224($sp)
+ .cfi_rel_offset 6, 224
+ sd $a1, 216($sp)
+ .cfi_rel_offset 5, 216
+ sd $a0, 208($sp)
+ .cfi_rel_offset 4, 208
+ sd $v1, 200($sp)
+ .cfi_rel_offset 3, 200
+ sd $v0, 192($sp)
+ .cfi_rel_offset 2, 192
+
+ dla $t9, artReadBarrierMark
+
+ sdc1 $f23, 184($sp)
+ sdc1 $f22, 176($sp)
+ sdc1 $f21, 168($sp)
+ sdc1 $f20, 160($sp)
+ sdc1 $f19, 152($sp)
+ sdc1 $f18, 144($sp)
+ sdc1 $f17, 136($sp)
+ sdc1 $f16, 128($sp)
+ sdc1 $f15, 120($sp)
+ sdc1 $f14, 112($sp)
+ sdc1 $f13, 104($sp)
+ sdc1 $f12, 96($sp)
+ sdc1 $f11, 88($sp)
+ sdc1 $f10, 80($sp)
+ sdc1 $f9, 72($sp)
+ sdc1 $f8, 64($sp)
+ sdc1 $f7, 56($sp)
+ sdc1 $f6, 48($sp)
+ sdc1 $f5, 40($sp)
+ sdc1 $f4, 32($sp)
+ sdc1 $f3, 24($sp)
+ sdc1 $f2, 16($sp)
+ sdc1 $f1, 8($sp)
+ sdc1 $f0, 0($sp)
+
+ jalr $t9 # $v0 <- artReadBarrierMark(reference)
+ move $a0, $t8 # Pass reference in $a0.
+ move $t8, $v0
+
+ ld $ra, 312($sp)
+ .cfi_restore 31
+ .cpreturn # Restore old $gp from 304($sp).
+ ld $t3, 296($sp) # $t3 = address of the exit point.
+ ld $t2, 288($sp)
+ .cfi_restore 14
+ ld $t1, 280($sp)
+ .cfi_restore 13
+ ld $t0, 272($sp)
+ .cfi_restore 12
+ ld $a7, 264($sp)
+ .cfi_restore 11
+ ld $a6, 256($sp)
+ .cfi_restore 10
+ ld $a5, 248($sp)
+ .cfi_restore 9
+ ld $a4, 240($sp)
+ .cfi_restore 8
+ ld $a3, 232($sp)
+ .cfi_restore 7
+ ld $a2, 224($sp)
+ .cfi_restore 6
+ ld $a1, 216($sp)
+ .cfi_restore 5
+ ld $a0, 208($sp)
+ .cfi_restore 4
+ ld $v1, 200($sp)
+ .cfi_restore 3
+ ld $v0, 192($sp)
+ .cfi_restore 2
+
+ ldc1 $f23, 184($sp)
+ ldc1 $f22, 176($sp)
+ ldc1 $f21, 168($sp)
+ ldc1 $f20, 160($sp)
+ ldc1 $f19, 152($sp)
+ ldc1 $f18, 144($sp)
+ ldc1 $f17, 136($sp)
+ ldc1 $f16, 128($sp)
+ ldc1 $f15, 120($sp)
+ ldc1 $f14, 112($sp)
+ ldc1 $f13, 104($sp)
+ ldc1 $f12, 96($sp)
+ ldc1 $f11, 88($sp)
+ ldc1 $f10, 80($sp)
+ ldc1 $f9, 72($sp)
+ ldc1 $f8, 64($sp)
+ ldc1 $f7, 56($sp)
+ ldc1 $f6, 48($sp)
+ ldc1 $f5, 40($sp)
+ ldc1 $f4, 32($sp)
+ ldc1 $f3, 24($sp)
+ ldc1 $f2, 16($sp)
+ ldc1 $f1, 8($sp)
+ ldc1 $f0, 0($sp)
+
+ // Return through the exit point.
+ jalr $zero, $t3 # Move $t8 to `out` and return.
+ daddiu $sp, $sp, 320
+ .cfi_adjust_cfa_offset -320
+
+.Lintrospection_exits:
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT $v0
+ BRB_FIELD_EXIT $v1
+ BRB_FIELD_EXIT $a0
+ BRB_FIELD_EXIT $a1
+ BRB_FIELD_EXIT $a2
+ BRB_FIELD_EXIT $a3
+ BRB_FIELD_EXIT $a4
+ BRB_FIELD_EXIT $a5
+ BRB_FIELD_EXIT $a6
+ BRB_FIELD_EXIT $a7
+ BRB_FIELD_EXIT $t0
+ BRB_FIELD_EXIT $t1
+ BRB_FIELD_EXIT $t2
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT $s2
+ BRB_FIELD_EXIT $s3
+ BRB_FIELD_EXIT $s4
+ BRB_FIELD_EXIT $s5
+ BRB_FIELD_EXIT $s6
+ BRB_FIELD_EXIT $s7
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT_BREAK
+ BRB_FIELD_EXIT $s8
+ BRB_FIELD_EXIT_BREAK
+END art_quick_read_barrier_mark_introspection
+
.extern artInvokePolymorphic
ENTRY art_quick_invoke_polymorphic
SETUP_SAVE_REFS_AND_ARGS_FRAME
diff --git a/runtime/arch/x86/instruction_set_features_x86.cc b/runtime/arch/x86/instruction_set_features_x86.cc
index cc0bdf2..ea5a90d 100644
--- a/runtime/arch/x86/instruction_set_features_x86.cc
+++ b/runtime/arch/x86/instruction_set_features_x86.cc
@@ -230,6 +230,19 @@
(has_POPCNT_ == other_as_x86->has_POPCNT_);
}
+bool X86InstructionSetFeatures::HasAtLeast(const InstructionSetFeatures* other) const {
+ if (GetInstructionSet() != other->GetInstructionSet()) {
+ return false;
+ }
+ const X86InstructionSetFeatures* other_as_x86 = other->AsX86InstructionSetFeatures();
+ return (has_SSSE3_ || !other_as_x86->has_SSSE3_) &&
+ (has_SSE4_1_ || !other_as_x86->has_SSE4_1_) &&
+ (has_SSE4_2_ || !other_as_x86->has_SSE4_2_) &&
+ (has_AVX_ || !other_as_x86->has_AVX_) &&
+ (has_AVX2_ || !other_as_x86->has_AVX2_) &&
+ (has_POPCNT_ || !other_as_x86->has_POPCNT_);
+}
+
uint32_t X86InstructionSetFeatures::AsBitmap() const {
return (has_SSSE3_ ? kSsse3Bitfield : 0) |
(has_SSE4_1_ ? kSse4_1Bitfield : 0) |
diff --git a/runtime/arch/x86/instruction_set_features_x86.h b/runtime/arch/x86/instruction_set_features_x86.h
index eb8a710..56cb07e 100644
--- a/runtime/arch/x86/instruction_set_features_x86.h
+++ b/runtime/arch/x86/instruction_set_features_x86.h
@@ -29,12 +29,11 @@
public:
// Process a CPU variant string like "atom" or "nehalem" and create InstructionSetFeatures.
static X86FeaturesUniquePtr FromVariant(const std::string& variant,
- std::string* error_msg,
- bool x86_64 = false);
+ std::string* error_msg,
+ bool x86_64 = false);
// Parse a bitmap and create an InstructionSetFeatures.
- static X86FeaturesUniquePtr FromBitmap(uint32_t bitmap,
- bool x86_64 = false);
+ static X86FeaturesUniquePtr FromBitmap(uint32_t bitmap, bool x86_64 = false);
// Turn C pre-processor #defines into the equivalent instruction set features.
static X86FeaturesUniquePtr FromCppDefines(bool x86_64 = false);
@@ -52,6 +51,8 @@
bool Equals(const InstructionSetFeatures* other) const OVERRIDE;
+ bool HasAtLeast(const InstructionSetFeatures* other) const OVERRIDE;
+
virtual InstructionSet GetInstructionSet() const OVERRIDE {
return kX86;
}
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 031b36b..48d2de9 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -1780,35 +1780,90 @@
*/
DEFINE_FUNCTION art_quick_imt_conflict_trampoline
PUSH EDI
- movl 8(%esp), %edi // Load referrer
- movl ART_METHOD_DEX_CACHE_METHODS_OFFSET_32(%edi), %edi // Load dex cache methods array
+ PUSH ESI
+ PUSH EDX
+ movl 16(%esp), %edi // Load referrer.
+ movl ART_METHOD_DEX_CACHE_METHODS_OFFSET_32(%edi), %edi // Load dex cache methods array.
pushl ART_METHOD_JNI_OFFSET_32(%eax) // Push ImtConflictTable.
CFI_ADJUST_CFA_OFFSET(4)
- movd %xmm7, %eax // get target method index stored in xmm7
- movl 0(%edi, %eax, __SIZEOF_POINTER__), %edi // Load interface method
- popl %eax // Pop ImtConflictTable.
+ movd %xmm7, %eax // Get target method index stored in xmm7.
+ movl %eax, %esi // Remember method index in ESI.
+ andl LITERAL(METHOD_DEX_CACHE_SIZE_MINUS_ONE), %eax // Calculate DexCache method slot index.
+ leal 0(%edi, %eax, 2 * __SIZEOF_POINTER__), %edi // Load DexCache method slot address.
+ mov %ecx, %edx // Make EDX:EAX == ECX:EBX so that LOCK CMPXCHG8B makes no changes.
+ mov %ebx, %eax // (The actual value does not matter.)
+ lock cmpxchg8b (%edi) // Relaxed atomic load EDX:EAX from the dex cache slot.
+ popl %edi // Pop ImtConflictTable.
CFI_ADJUST_CFA_OFFSET(-4)
+ cmp %edx, %esi // Compare method index to see if we had a DexCache method hit.
+ jne .Limt_conflict_trampoline_dex_cache_miss
.Limt_table_iterate:
- cmpl %edi, 0(%eax)
+ cmpl %eax, 0(%edi)
jne .Limt_table_next_entry
// We successfully hit an entry in the table. Load the target method
// and jump to it.
+ movl __SIZEOF_POINTER__(%edi), %eax
+ CFI_REMEMBER_STATE
+ POP EDX
+ POP ESI
POP EDI
- movl __SIZEOF_POINTER__(%eax), %eax
jmp *ART_METHOD_QUICK_CODE_OFFSET_32(%eax)
+ CFI_RESTORE_STATE
.Limt_table_next_entry:
// If the entry is null, the interface method is not in the ImtConflictTable.
- cmpl LITERAL(0), 0(%eax)
+ cmpl LITERAL(0), 0(%edi)
jz .Lconflict_trampoline
// Iterate over the entries of the ImtConflictTable.
- addl LITERAL(2 * __SIZEOF_POINTER__), %eax
+ addl LITERAL(2 * __SIZEOF_POINTER__), %edi
jmp .Limt_table_iterate
.Lconflict_trampoline:
// Call the runtime stub to populate the ImtConflictTable and jump to the
// resolved method.
- movl %edi, %eax // Load interface method
+ CFI_REMEMBER_STATE
+ POP EDX
+ POP ESI
POP EDI
INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline
+ CFI_RESTORE_STATE
+.Limt_conflict_trampoline_dex_cache_miss:
+ // We're not creating a proper runtime method frame here,
+ // artLookupResolvedMethod() is not allowed to walk the stack.
+
+ // Save core register args; EDX is already saved.
+ PUSH ebx
+ PUSH ecx
+
+ // Save FPR args.
+ subl MACRO_LITERAL(32), %esp
+ CFI_ADJUST_CFA_OFFSET(32)
+ movsd %xmm0, 0(%esp)
+ movsd %xmm1, 8(%esp)
+ movsd %xmm2, 16(%esp)
+ movsd %xmm3, 24(%esp)
+
+ pushl 32+8+16(%esp) // Pass referrer.
+ CFI_ADJUST_CFA_OFFSET(4)
+ pushl %esi // Pass method index.
+ CFI_ADJUST_CFA_OFFSET(4)
+ call SYMBOL(artLookupResolvedMethod) // (uint32_t method_index, ArtMethod* referrer)
+ addl LITERAL(8), %esp // Pop arguments.
+ CFI_ADJUST_CFA_OFFSET(-8)
+
+ // Restore FPR args.
+ movsd 0(%esp), %xmm0
+ movsd 8(%esp), %xmm1
+ movsd 16(%esp), %xmm2
+ movsd 24(%esp), %xmm3
+ addl MACRO_LITERAL(32), %esp
+ CFI_ADJUST_CFA_OFFSET(-32)
+
+ // Restore core register args.
+ POP ecx
+ POP ebx
+
+ cmp LITERAL(0), %eax // If the method wasn't resolved,
+ je .Lconflict_trampoline // skip the lookup and go to artInvokeInterfaceTrampoline().
+ jmp .Limt_table_iterate
END_FUNCTION art_quick_imt_conflict_trampoline
DEFINE_FUNCTION art_quick_resolution_trampoline
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index ad06873..0a9199e 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -1641,17 +1641,29 @@
int3
int3
#else
- movq __SIZEOF_POINTER__(%rsp), %r10 // Load referrer
- movq ART_METHOD_DEX_CACHE_METHODS_OFFSET_64(%r10), %r10 // Load dex cache methods array
- movq 0(%r10, %rax, __SIZEOF_POINTER__), %r10 // Load interface method
+ movq __SIZEOF_POINTER__(%rsp), %r10 // Load referrer.
+ movq ART_METHOD_DEX_CACHE_METHODS_OFFSET_64(%r10), %r10 // Load dex cache methods array.
+ mov %eax, %r11d // Remember method index in R11.
+ andl LITERAL(METHOD_DEX_CACHE_SIZE_MINUS_ONE), %eax // Calculate DexCache method slot index.
+ shll LITERAL(1), %eax // Multiply by 2 as entries have size 2 * __SIZEOF_POINTER__.
+ leaq 0(%r10, %rax, __SIZEOF_POINTER__), %r10 // Load DexCache method slot address.
+ PUSH rdx // Preserve RDX as we need to clobber it by LOCK CMPXCHG16B.
+ mov %rcx, %rdx // Make RDX:RAX == RCX:RBX so that LOCK CMPXCHG16B makes no changes.
+ mov %rbx, %rax // (The actual value does not matter.)
+ lock cmpxchg16b (%r10) // Relaxed atomic load RDX:RAX from the dex cache slot.
movq ART_METHOD_JNI_OFFSET_64(%rdi), %rdi // Load ImtConflictTable
+ cmp %rdx, %r11 // Compare method index to see if we had a DexCache method hit.
+ jne .Limt_conflict_trampoline_dex_cache_miss
.Limt_table_iterate:
- cmpq %r10, 0(%rdi)
+ cmpq %rax, 0(%rdi)
jne .Limt_table_next_entry
// We successfully hit an entry in the table. Load the target method
// and jump to it.
movq __SIZEOF_POINTER__(%rdi), %rdi
+ CFI_REMEMBER_STATE
+ POP rdx
jmp *ART_METHOD_QUICK_CODE_OFFSET_64(%rdi)
+ CFI_RESTORE_STATE
.Limt_table_next_entry:
// If the entry is null, the interface method is not in the ImtConflictTable.
cmpq LITERAL(0), 0(%rdi)
@@ -1662,8 +1674,66 @@
.Lconflict_trampoline:
// Call the runtime stub to populate the ImtConflictTable and jump to the
// resolved method.
- movq %r10, %rdi // Load interface method
+ CFI_REMEMBER_STATE
+ POP rdx
+ movq %rax, %rdi // Load interface method
INVOKE_TRAMPOLINE_BODY artInvokeInterfaceTrampoline
+ CFI_RESTORE_STATE
+.Limt_conflict_trampoline_dex_cache_miss:
+ // We're not creating a proper runtime method frame here,
+ // artLookupResolvedMethod() is not allowed to walk the stack.
+
+ // Save GPR args and ImtConflictTable; RDX is already saved.
+ PUSH r9 // Quick arg 5.
+ PUSH r8 // Quick arg 4.
+ PUSH rsi // Quick arg 1.
+ PUSH rcx // Quick arg 3.
+ PUSH rdi // ImtConflictTable
+ // Save FPR args and callee-saves, align stack to 16B.
+ subq MACRO_LITERAL(12 * 8 + 8), %rsp
+ CFI_ADJUST_CFA_OFFSET(12 * 8 + 8)
+ movq %xmm0, 0(%rsp)
+ movq %xmm1, 8(%rsp)
+ movq %xmm2, 16(%rsp)
+ movq %xmm3, 24(%rsp)
+ movq %xmm4, 32(%rsp)
+ movq %xmm5, 40(%rsp)
+ movq %xmm6, 48(%rsp)
+ movq %xmm7, 56(%rsp)
+ movq %xmm12, 64(%rsp) // XMM12-15 are callee-save in ART compiled code ABI
+ movq %xmm13, 72(%rsp) // but caller-save in native ABI.
+ movq %xmm14, 80(%rsp)
+ movq %xmm15, 88(%rsp)
+
+ movq %r11, %rdi // Pass method index.
+ movq 12 * 8 + 8 + 6 * 8 + 8(%rsp), %rsi // Pass referrer.
+ call SYMBOL(artLookupResolvedMethod) // (uint32_t method_index, ArtMethod* referrer)
+
+ // Restore FPRs.
+ movq 0(%rsp), %xmm0
+ movq 8(%rsp), %xmm1
+ movq 16(%rsp), %xmm2
+ movq 24(%rsp), %xmm3
+ movq 32(%rsp), %xmm4
+ movq 40(%rsp), %xmm5
+ movq 48(%rsp), %xmm6
+ movq 56(%rsp), %xmm7
+ movq 64(%rsp), %xmm12
+ movq 72(%rsp), %xmm13
+ movq 80(%rsp), %xmm14
+ movq 88(%rsp), %xmm15
+ addq MACRO_LITERAL(12 * 8 + 8), %rsp
+ CFI_ADJUST_CFA_OFFSET(-(12 * 8 + 8))
+ // Restore ImtConflictTable and GPR args.
+ POP rdi
+ POP rcx
+ POP rsi
+ POP r8
+ POP r9
+
+ cmp LITERAL(0), %rax // If the method wasn't resolved,
+ je .Lconflict_trampoline // skip the lookup and go to artInvokeInterfaceTrampoline().
+ jmp .Limt_table_iterate
#endif // __APPLE__
END_FUNCTION art_quick_imt_conflict_trampoline
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 40d7e5c..4300544 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -102,20 +102,21 @@
return GetDexMethodIndexUnchecked();
}
-inline ArtMethod** ArtMethod::GetDexCacheResolvedMethods(PointerSize pointer_size) {
- return GetNativePointer<ArtMethod**>(DexCacheResolvedMethodsOffset(pointer_size),
- pointer_size);
+inline mirror::MethodDexCacheType* ArtMethod::GetDexCacheResolvedMethods(PointerSize pointer_size) {
+ return GetNativePointer<mirror::MethodDexCacheType*>(DexCacheResolvedMethodsOffset(pointer_size),
+ pointer_size);
}
inline ArtMethod* ArtMethod::GetDexCacheResolvedMethod(uint16_t method_index,
PointerSize pointer_size) {
// NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
// without accessing the DexCache and we don't want to do that in release build.
- DCHECK_LT(method_index,
- GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
- ArtMethod* method = mirror::DexCache::GetElementPtrSize(GetDexCacheResolvedMethods(pointer_size),
- method_index,
- pointer_size);
+ DCHECK_LT(method_index, GetInterfaceMethodIfProxy(pointer_size)->GetDexFile()->NumMethodIds());
+ uint32_t slot_idx = method_index % mirror::DexCache::kDexCacheMethodCacheSize;
+ DCHECK_LT(slot_idx, GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
+ mirror::MethodDexCachePair pair = mirror::DexCache::GetNativePairPtrSize(
+ GetDexCacheResolvedMethods(pointer_size), slot_idx, pointer_size);
+ ArtMethod* method = pair.GetObjectForIndex(method_index);
if (LIKELY(method != nullptr)) {
auto* declaring_class = method->GetDeclaringClass();
if (LIKELY(declaring_class == nullptr || !declaring_class->IsErroneous())) {
@@ -130,29 +131,29 @@
PointerSize pointer_size) {
// NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
// without accessing the DexCache and we don't want to do that in release build.
- DCHECK_LT(method_index,
- GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
+ DCHECK_LT(method_index, GetInterfaceMethodIfProxy(pointer_size)->GetDexFile()->NumMethodIds());
DCHECK(new_method == nullptr || new_method->GetDeclaringClass() != nullptr);
- mirror::DexCache::SetElementPtrSize(GetDexCacheResolvedMethods(pointer_size),
- method_index,
- new_method,
- pointer_size);
+ uint32_t slot_idx = method_index % mirror::DexCache::kDexCacheMethodCacheSize;
+ DCHECK_LT(slot_idx, GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
+ mirror::MethodDexCachePair pair(new_method, method_index);
+ mirror::DexCache::SetNativePairPtrSize(
+ GetDexCacheResolvedMethods(pointer_size), slot_idx, pair, pointer_size);
}
inline bool ArtMethod::HasDexCacheResolvedMethods(PointerSize pointer_size) {
return GetDexCacheResolvedMethods(pointer_size) != nullptr;
}
-inline bool ArtMethod::HasSameDexCacheResolvedMethods(ArtMethod** other_cache,
- PointerSize pointer_size) {
- return GetDexCacheResolvedMethods(pointer_size) == other_cache;
-}
-
inline bool ArtMethod::HasSameDexCacheResolvedMethods(ArtMethod* other, PointerSize pointer_size) {
return GetDexCacheResolvedMethods(pointer_size) ==
other->GetDexCacheResolvedMethods(pointer_size);
}
+inline bool ArtMethod::HasSameDexCacheResolvedMethods(mirror::MethodDexCacheType* other_cache,
+ PointerSize pointer_size) {
+ return GetDexCacheResolvedMethods(pointer_size) == other_cache;
+}
+
inline mirror::Class* ArtMethod::GetClassFromTypeIndex(dex::TypeIndex type_idx, bool resolve) {
// TODO: Refactor this function into two functions, Resolve...() and Lookup...()
// so that we can properly annotate it with no-suspension possible / suspension possible.
@@ -381,17 +382,21 @@
if (LIKELY(!IsProxyMethod())) {
return this;
}
- ArtMethod* interface_method = mirror::DexCache::GetElementPtrSize(
- GetDexCacheResolvedMethods(pointer_size),
- GetDexMethodIndex(),
- pointer_size);
- DCHECK(interface_method != nullptr);
- DCHECK_EQ(interface_method,
- Runtime::Current()->GetClassLinker()->FindMethodForProxy(GetDeclaringClass(), this));
+ uint32_t method_index = GetDexMethodIndex();
+ uint32_t slot_idx = method_index % mirror::DexCache::kDexCacheMethodCacheSize;
+ mirror::MethodDexCachePair pair = mirror::DexCache::GetNativePairPtrSize(
+ GetDexCacheResolvedMethods(pointer_size), slot_idx, pointer_size);
+ ArtMethod* interface_method = pair.GetObjectForIndex(method_index);
+ if (LIKELY(interface_method != nullptr)) {
+ DCHECK_EQ(interface_method, Runtime::Current()->GetClassLinker()->FindMethodForProxy(this));
+ } else {
+ interface_method = Runtime::Current()->GetClassLinker()->FindMethodForProxy(this);
+ DCHECK(interface_method != nullptr);
+ }
return interface_method;
}
-inline void ArtMethod::SetDexCacheResolvedMethods(ArtMethod** new_dex_cache_methods,
+inline void ArtMethod::SetDexCacheResolvedMethods(mirror::MethodDexCacheType* new_dex_cache_methods,
PointerSize pointer_size) {
SetNativePointer(DexCacheResolvedMethodsOffset(pointer_size),
new_dex_cache_methods,
@@ -462,14 +467,8 @@
if (UNLIKELY(klass->IsProxyClass())) {
// For normal methods, dex cache shortcuts will be visited through the declaring class.
// However, for proxies we need to keep the interface method alive, so we visit its roots.
- ArtMethod* interface_method = mirror::DexCache::GetElementPtrSize(
- GetDexCacheResolvedMethods(pointer_size),
- GetDexMethodIndex(),
- pointer_size);
+ ArtMethod* interface_method = GetInterfaceMethodIfProxy(pointer_size);
DCHECK(interface_method != nullptr);
- DCHECK_EQ(interface_method,
- Runtime::Current()->GetClassLinker()->FindMethodForProxy<kReadBarrierOption>(
- klass, this));
interface_method->VisitRoots(visitor, pointer_size);
}
}
@@ -483,8 +482,8 @@
if (old_class != new_class) {
SetDeclaringClass(new_class);
}
- ArtMethod** old_methods = GetDexCacheResolvedMethods(pointer_size);
- ArtMethod** new_methods = visitor(old_methods);
+ mirror::MethodDexCacheType* old_methods = GetDexCacheResolvedMethods(pointer_size);
+ mirror::MethodDexCacheType* new_methods = visitor(old_methods);
if (old_methods != new_methods) {
SetDexCacheResolvedMethods(new_methods, pointer_size);
}
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 45dd596..d8984e8 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -67,9 +67,10 @@
return this;
} else {
mirror::Class* declaring_class = GetDeclaringClass();
- ArtMethod* ret = declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(),
- GetDexMethodIndex(),
- pointer_size);
+ DCHECK(declaring_class->IsInterface());
+ ArtMethod* ret = declaring_class->FindInterfaceMethod(declaring_class->GetDexCache(),
+ GetDexMethodIndex(),
+ pointer_size);
DCHECK(ret != nullptr);
return ret;
}
@@ -215,11 +216,8 @@
} else {
// Method didn't override superclass method so search interfaces
if (IsProxyMethod()) {
- result = mirror::DexCache::GetElementPtrSize(GetDexCacheResolvedMethods(pointer_size),
- GetDexMethodIndex(),
- pointer_size);
- CHECK_EQ(result,
- Runtime::Current()->GetClassLinker()->FindMethodForProxy(GetDeclaringClass(), this));
+ result = GetInterfaceMethodIfProxy(pointer_size);
+ DCHECK(result != nullptr);
} else {
mirror::IfTable* iftable = GetDeclaringClass()->GetIfTable();
for (size_t i = 0; i < iftable->Count() && result == nullptr; i++) {
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 4b3e8ef..511ac83 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -53,6 +53,10 @@
template <typename MirrorType> class ObjectArray;
class PointerArray;
class String;
+
+template <typename T> struct NativeDexCachePair;
+using MethodDexCachePair = NativeDexCachePair<ArtMethod>;
+using MethodDexCacheType = std::atomic<MethodDexCachePair>;
} // namespace mirror
class ArtMethod FINAL {
@@ -352,7 +356,7 @@
dex_method_index_ = new_idx;
}
- ALWAYS_INLINE ArtMethod** GetDexCacheResolvedMethods(PointerSize pointer_size)
+ ALWAYS_INLINE mirror::MethodDexCacheType* GetDexCacheResolvedMethods(PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
ALWAYS_INLINE ArtMethod* GetDexCacheResolvedMethod(uint16_t method_index,
PointerSize pointer_size)
@@ -362,13 +366,14 @@
ArtMethod* new_method,
PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
- ALWAYS_INLINE void SetDexCacheResolvedMethods(ArtMethod** new_dex_cache_methods,
+ ALWAYS_INLINE void SetDexCacheResolvedMethods(mirror::MethodDexCacheType* new_dex_cache_methods,
PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
bool HasDexCacheResolvedMethods(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_);
bool HasSameDexCacheResolvedMethods(ArtMethod* other, PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
- bool HasSameDexCacheResolvedMethods(ArtMethod** other_cache, PointerSize pointer_size)
+ bool HasSameDexCacheResolvedMethods(mirror::MethodDexCacheType* other_cache,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
// Get the Class* from the type index into this method's dex cache.
@@ -714,7 +719,7 @@
// Must be the last fields in the method.
struct PtrSizedFields {
// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
- ArtMethod** dex_cache_resolved_methods_;
+ mirror::MethodDexCacheType* 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
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 3c51f52..9a73697 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -90,33 +90,129 @@
return resolved_type.Ptr();
}
-inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod* referrer) {
- ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_);
- if (resolved_method == nullptr || resolved_method->IsRuntimeMethod()) {
- return nullptr;
+template <bool kThrowOnError, typename ClassGetter>
+inline bool ClassLinker::CheckInvokeClassMismatch(ObjPtr<mirror::DexCache> dex_cache,
+ InvokeType type,
+ ClassGetter class_getter) {
+ switch (type) {
+ case kStatic:
+ case kSuper:
+ break;
+ case kInterface: {
+ // We have to check whether the method id really belongs to an interface (dex static bytecode
+ // constraints A15, A16). Otherwise you must not invoke-interface on it.
+ ObjPtr<mirror::Class> klass = class_getter();
+ if (UNLIKELY(!klass->IsInterface())) {
+ if (kThrowOnError) {
+ ThrowIncompatibleClassChangeError(klass,
+ "Found class %s, but interface was expected",
+ klass->PrettyDescriptor().c_str());
+ }
+ return true;
+ }
+ break;
+ }
+ case kDirect:
+ if (dex_cache->GetDexFile()->GetVersion() >= DexFile::kDefaultMethodsVersion) {
+ break;
+ }
+ FALLTHROUGH_INTENDED;
+ case kVirtual: {
+ // Similarly, invoke-virtual (and invoke-direct without default methods) must reference
+ // a non-interface class (dex static bytecode constraint A24, A25).
+ ObjPtr<mirror::Class> klass = class_getter();
+ if (UNLIKELY(klass->IsInterface())) {
+ if (kThrowOnError) {
+ ThrowIncompatibleClassChangeError(klass,
+ "Found interface %s, but class was expected",
+ klass->PrettyDescriptor().c_str());
+ }
+ return true;
+ }
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unreachable - invocation type: " << type;
+ UNREACHABLE();
}
- return resolved_method;
+ return false;
}
-inline mirror::Class* ClassLinker::ResolveReferencedClassOfMethod(
- uint32_t method_idx,
- Handle<mirror::DexCache> dex_cache,
- Handle<mirror::ClassLoader> class_loader) {
- // NB: We cannot simply use `GetResolvedMethod(method_idx, ...)->GetDeclaringClass()`. This is
- // because if we did so than an invoke-super could be incorrectly dispatched in cases where
- // GetMethodId(method_idx).class_idx_ refers to a non-interface, non-direct-superclass
- // (super*-class?) of the referrer and the direct superclass of the referrer contains a concrete
- // implementation of the method. If this class's implementation of the method is copied from an
- // interface (either miranda, default or conflict) we would incorrectly assume that is what we
- // want to invoke on, instead of the 'concrete' implementation that the direct superclass
- // contains.
- const DexFile* dex_file = dex_cache->GetDexFile();
- const DexFile::MethodId& method = dex_file->GetMethodId(method_idx);
- ObjPtr<mirror::Class> resolved_type = dex_cache->GetResolvedType(method.class_idx_);
- if (UNLIKELY(resolved_type == nullptr)) {
- resolved_type = ResolveType(*dex_file, method.class_idx_, dex_cache, class_loader);
+template <bool kThrow>
+inline bool ClassLinker::CheckInvokeClassMismatch(ObjPtr<mirror::DexCache> dex_cache,
+ InvokeType type,
+ uint32_t method_idx,
+ ObjPtr<mirror::ClassLoader> class_loader) {
+ return CheckInvokeClassMismatch<kThrow>(
+ dex_cache,
+ type,
+ [this, dex_cache, method_idx, class_loader]() REQUIRES_SHARED(Locks::mutator_lock_) {
+ const DexFile& dex_file = *dex_cache->GetDexFile();
+ const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx);
+ ObjPtr<mirror::Class> klass =
+ LookupResolvedType(dex_file, method_id.class_idx_, dex_cache, class_loader);
+ DCHECK(klass != nullptr);
+ return klass;
+ });
+}
+
+inline ArtMethod* ClassLinker::LookupResolvedMethod(uint32_t method_idx,
+ ObjPtr<mirror::DexCache> dex_cache,
+ ObjPtr<mirror::ClassLoader> class_loader) {
+ PointerSize pointer_size = image_pointer_size_;
+ ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx, pointer_size);
+ if (resolved == nullptr) {
+ const DexFile& dex_file = *dex_cache->GetDexFile();
+ const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx);
+ ObjPtr<mirror::Class> klass = LookupResolvedType(method_id.class_idx_, dex_cache, class_loader);
+ if (klass != nullptr) {
+ if (klass->IsInterface()) {
+ resolved = klass->FindInterfaceMethod(dex_cache, method_idx, pointer_size);
+ } else {
+ resolved = klass->FindClassMethod(dex_cache, method_idx, pointer_size);
+ }
+ if (resolved != nullptr) {
+ dex_cache->SetResolvedMethod(method_idx, resolved, pointer_size);
+ }
+ }
}
- return resolved_type.Ptr();
+ return resolved;
+}
+
+template <InvokeType type, ClassLinker::ResolveMode kResolveMode>
+inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod* referrer) {
+ DCHECK(referrer != nullptr);
+ // Note: The referrer can be a Proxy constructor. In that case, we need to do the
+ // lookup in the context of the original method from where it steals the code.
+ // However, we delay the GetInterfaceMethodIfProxy() until needed.
+ DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor());
+ ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_);
+ if (resolved_method == nullptr) {
+ return nullptr;
+ }
+ DCHECK(!resolved_method->IsRuntimeMethod());
+ if (kResolveMode == ResolveMode::kCheckICCEAndIAE) {
+ referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
+ // Check if the invoke type matches the class type.
+ ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+ ObjPtr<mirror::ClassLoader> class_loader = referrer->GetClassLoader();
+ if (CheckInvokeClassMismatch</* kThrow */ false>(dex_cache, type, method_idx, class_loader)) {
+ return nullptr;
+ }
+ // Check access.
+ ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
+ if (!referring_class->CanAccessResolvedMethod(resolved_method->GetDeclaringClass(),
+ resolved_method,
+ dex_cache,
+ method_idx)) {
+ return nullptr;
+ }
+ // Check if the invoke type matches the method type.
+ if (UNLIKELY(resolved_method->CheckIncompatibleClassChange(type))) {
+ return nullptr;
+ }
+ }
+ return resolved_method;
}
template <ClassLinker::ResolveMode kResolveMode>
@@ -124,9 +220,16 @@
uint32_t method_idx,
ArtMethod* referrer,
InvokeType type) {
- ArtMethod* resolved_method = GetResolvedMethod(method_idx, referrer);
+ DCHECK(referrer != nullptr);
+ // Note: The referrer can be a Proxy constructor. In that case, we need to do the
+ // lookup in the context of the original method from where it steals the code.
+ // However, we delay the GetInterfaceMethodIfProxy() until needed.
+ DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor());
Thread::PoisonObjectPointersIfDebug();
+ ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_);
+ DCHECK(resolved_method == nullptr || !resolved_method->IsRuntimeMethod());
if (UNLIKELY(resolved_method == nullptr)) {
+ referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass();
StackHandleScope<2> hs(self);
Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(referrer->GetDexCache()));
@@ -138,6 +241,33 @@
h_class_loader,
referrer,
type);
+ } else if (kResolveMode == ResolveMode::kCheckICCEAndIAE) {
+ referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
+ // Check if the invoke type matches the class type.
+ ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+ ObjPtr<mirror::ClassLoader> class_loader = referrer->GetClassLoader();
+ if (CheckInvokeClassMismatch</* kThrow */ true>(dex_cache, type, method_idx, class_loader)) {
+ DCHECK(Thread::Current()->IsExceptionPending());
+ return nullptr;
+ }
+ // Check access.
+ ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
+ if (!referring_class->CheckResolvedMethodAccess(resolved_method->GetDeclaringClass(),
+ resolved_method,
+ dex_cache,
+ method_idx,
+ type)) {
+ DCHECK(Thread::Current()->IsExceptionPending());
+ return nullptr;
+ }
+ // Check if the invoke type matches the method type.
+ if (UNLIKELY(resolved_method->CheckIncompatibleClassChange(type))) {
+ ThrowIncompatibleClassChangeError(type,
+ resolved_method->GetInvokeType(),
+ resolved_method,
+ referrer);
+ return nullptr;
+ }
}
// Note: We cannot check here to see whether we added the method to the cache. It
// might be an erroneous class, which results in it being hidden from us.
@@ -182,35 +312,6 @@
return klass.Ptr();
}
-template<ReadBarrierOption kReadBarrierOption>
-ArtMethod* ClassLinker::FindMethodForProxy(ObjPtr<mirror::Class> proxy_class,
- ArtMethod* proxy_method) {
- DCHECK(proxy_class->IsProxyClass());
- DCHECK(proxy_method->IsProxyMethod());
- {
- Thread* const self = Thread::Current();
- ReaderMutexLock mu(self, *Locks::dex_lock_);
- // Locate the dex cache of the original interface/Object
- for (const DexCacheData& data : dex_caches_) {
- if (!self->IsJWeakCleared(data.weak_root) &&
- proxy_method->HasSameDexCacheResolvedMethods(data.resolved_methods,
- image_pointer_size_)) {
- ObjPtr<mirror::DexCache> dex_cache =
- ObjPtr<mirror::DexCache>::DownCast(self->DecodeJObject(data.weak_root));
- if (dex_cache != nullptr) {
- ArtMethod* resolved_method = dex_cache->GetResolvedMethod(
- proxy_method->GetDexMethodIndex(), image_pointer_size_);
- CHECK(resolved_method != nullptr);
- return resolved_method;
- }
- }
- }
- }
- LOG(FATAL) << "Didn't find dex cache for " << proxy_class->PrettyClass() << " "
- << proxy_method->PrettyMethod();
- UNREACHABLE();
-}
-
} // namespace art
#endif // ART_RUNTIME_CLASS_LINKER_INL_H_
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index a9237ef3..9e3e76c 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -150,8 +150,8 @@
return false;
}
- ArtMethod* exception_init_method = exception_class->FindDeclaredDirectMethod(
- "<init>", "(Ljava/lang/String;)V", class_linker->GetImagePointerSize());
+ ArtMethod* exception_init_method = exception_class->FindConstructor(
+ "(Ljava/lang/String;)V", class_linker->GetImagePointerSize());
return exception_init_method != nullptr;
}
@@ -1114,7 +1114,8 @@
virtual void Visit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
const bool is_copied = method->IsCopied();
- ArtMethod** resolved_methods = method->GetDexCacheResolvedMethods(kRuntimePointerSize);
+ mirror::MethodDexCacheType* resolved_methods =
+ method->GetDexCacheResolvedMethods(kRuntimePointerSize);
if (resolved_methods != nullptr) {
bool in_image_space = false;
if (kIsDebugBuild || is_copied) {
@@ -1284,6 +1285,25 @@
}
}
+template <typename T>
+static void CopyNativeDexCachePairs(std::atomic<mirror::NativeDexCachePair<T>>* src,
+ size_t count,
+ std::atomic<mirror::NativeDexCachePair<T>>* dst,
+ PointerSize pointer_size) {
+ DCHECK_NE(count, 0u);
+ DCHECK(mirror::DexCache::GetNativePairPtrSize(src, 0, pointer_size).object != nullptr ||
+ mirror::DexCache::GetNativePairPtrSize(src, 0, pointer_size).index != 0u);
+ for (size_t i = 0; i < count; ++i) {
+ DCHECK_EQ(mirror::DexCache::GetNativePairPtrSize(dst, i, pointer_size).index, 0u);
+ DCHECK(mirror::DexCache::GetNativePairPtrSize(dst, i, pointer_size).object == nullptr);
+ mirror::NativeDexCachePair<T> source =
+ mirror::DexCache::GetNativePairPtrSize(src, i, pointer_size);
+ if (source.index != 0u || source.object != nullptr) {
+ mirror::DexCache::SetNativePairPtrSize(dst, i, source, pointer_size);
+ }
+ }
+}
+
// new_class_set is the set of classes that were read from the class table section in the image.
// If there was no class table section, it is null.
// Note: using a class here to avoid having to make ClassLinker internals public.
@@ -1363,7 +1383,10 @@
if (dex_file->NumTypeIds() < num_types) {
num_types = dex_file->NumTypeIds();
}
- const size_t num_methods = dex_file->NumMethodIds();
+ size_t num_methods = mirror::DexCache::kDexCacheMethodCacheSize;
+ if (dex_file->NumMethodIds() < num_methods) {
+ num_methods = dex_file->NumMethodIds();
+ }
size_t num_fields = mirror::DexCache::kDexCacheFieldCacheSize;
if (dex_file->NumFieldIds() < num_fields) {
num_fields = dex_file->NumFieldIds();
@@ -1396,37 +1419,18 @@
dex_cache->SetResolvedTypes(types);
}
if (num_methods != 0u) {
- ArtMethod** const methods = reinterpret_cast<ArtMethod**>(
- raw_arrays + layout.MethodsOffset());
- ArtMethod** const image_resolved_methods = dex_cache->GetResolvedMethods();
- for (size_t j = 0; kIsDebugBuild && j < num_methods; ++j) {
- DCHECK(methods[j] == nullptr);
- }
- CopyNonNull(image_resolved_methods,
- num_methods,
- methods,
- [] (const ArtMethod* method) {
- return method == nullptr;
- });
+ mirror::MethodDexCacheType* const image_resolved_methods =
+ dex_cache->GetResolvedMethods();
+ mirror::MethodDexCacheType* const methods =
+ reinterpret_cast<mirror::MethodDexCacheType*>(raw_arrays + layout.MethodsOffset());
+ CopyNativeDexCachePairs(image_resolved_methods, num_methods, methods, image_pointer_size);
dex_cache->SetResolvedMethods(methods);
}
if (num_fields != 0u) {
mirror::FieldDexCacheType* const image_resolved_fields = dex_cache->GetResolvedFields();
mirror::FieldDexCacheType* const fields =
reinterpret_cast<mirror::FieldDexCacheType*>(raw_arrays + layout.FieldsOffset());
- for (size_t j = 0; j < num_fields; ++j) {
- DCHECK_EQ(mirror::DexCache::GetNativePairPtrSize(fields, j, image_pointer_size).index,
- 0u);
- DCHECK(mirror::DexCache::GetNativePairPtrSize(fields, j, image_pointer_size).object ==
- nullptr);
- mirror::DexCache::SetNativePairPtrSize(
- fields,
- j,
- mirror::DexCache::GetNativePairPtrSize(image_resolved_fields,
- j,
- image_pointer_size),
- image_pointer_size);
- }
+ CopyNativeDexCachePairs(image_resolved_fields, num_fields, fields, image_pointer_size);
dex_cache->SetResolvedFields(fields);
}
if (num_method_types != 0u) {
@@ -1663,13 +1667,13 @@
heap->VisitObjects(visitor);
}
- static void CheckPointerArray(gc::Heap* heap,
- ClassLinker* class_linker,
- ArtMethod** arr,
- size_t size)
+ static void CheckArtMethodDexCacheArray(gc::Heap* heap,
+ ClassLinker* class_linker,
+ mirror::MethodDexCacheType* arr,
+ size_t size)
REQUIRES_SHARED(Locks::mutator_lock_) {
ImageSanityChecks isc(heap, class_linker);
- isc.SanityCheckArtMethodPointerArray(arr, size);
+ isc.SanityCheckArtMethodDexCacheArray(arr, size);
}
private:
@@ -1724,7 +1728,7 @@
}
}
- void SanityCheckArtMethodPointerArray(ArtMethod** arr, size_t size)
+ void SanityCheckArtMethodDexCacheArray(mirror::MethodDexCacheType* arr, size_t size)
REQUIRES_SHARED(Locks::mutator_lock_) {
CHECK_EQ(arr != nullptr, size != 0u);
if (arr != nullptr) {
@@ -1740,7 +1744,8 @@
CHECK(contains);
}
for (size_t j = 0; j < size; ++j) {
- ArtMethod* method = mirror::DexCache::GetElementPtrSize(arr, j, pointer_size_);
+ auto pair = mirror::DexCache::GetNativePairPtrSize(arr, j, pointer_size_);
+ ArtMethod* method = pair.object;
// expected_class == null means we are a dex cache.
if (method != nullptr) {
SanityCheckArtMethod(method, nullptr);
@@ -1851,10 +1856,10 @@
}
} else {
if (kSanityCheckObjects) {
- ImageSanityChecks::CheckPointerArray(heap,
- this,
- dex_cache->GetResolvedMethods(),
- dex_cache->NumResolvedMethods());
+ ImageSanityChecks::CheckArtMethodDexCacheArray(heap,
+ this,
+ dex_cache->GetResolvedMethods(),
+ dex_cache->NumResolvedMethods());
}
// Register dex files, keep track of existing ones that are conflicts.
AppendToBootClassPath(*dex_file.get(), dex_cache);
@@ -3722,20 +3727,6 @@
return DexCacheData();
}
-void ClassLinker::FixupDexCaches(ArtMethod* resolution_method) {
- Thread* const self = Thread::Current();
- ReaderMutexLock mu(self, *Locks::dex_lock_);
- for (const DexCacheData& data : dex_caches_) {
- if (!self->IsJWeakCleared(data.weak_root)) {
- ObjPtr<mirror::DexCache> dex_cache = ObjPtr<mirror::DexCache>::DownCast(
- self->DecodeJObject(data.weak_root));
- if (dex_cache != nullptr) {
- dex_cache->Fixup(resolution_method, image_pointer_size_);
- }
- }
- }
-}
-
mirror::Class* ClassLinker::CreatePrimitiveClass(Thread* self, Primitive::Type type) {
ObjPtr<mirror::Class> klass =
AllocClass(self, mirror::Class::PrimitiveClassSize(image_pointer_size_));
@@ -4638,10 +4629,8 @@
// Find the <init>(InvocationHandler)V method. The exact method offset varies depending
// on which front-end compiler was used to build the libcore DEX files.
- ArtMethod* proxy_constructor = GetClassRoot(kJavaLangReflectProxy)->
- FindDeclaredDirectMethod("<init>",
- "(Ljava/lang/reflect/InvocationHandler;)V",
- image_pointer_size_);
+ ArtMethod* proxy_constructor = GetClassRoot(kJavaLangReflectProxy)->FindConstructor(
+ "(Ljava/lang/reflect/InvocationHandler;)V", image_pointer_size_);
DCHECK(proxy_constructor != nullptr)
<< "Could not find <init> method in java.lang.reflect.Proxy";
@@ -4653,8 +4642,9 @@
// code_ too)
DCHECK(out != nullptr);
out->CopyFrom(proxy_constructor, image_pointer_size_);
- // Make this constructor public and fix the class to be our Proxy version
+ // Make this constructor public and fix the class to be our Proxy version.
// Mark kAccCompileDontBother so that we don't take JIT samples for the method. b/62349349
+ // Note that the compiler calls a ResolveMethod() overload that does not handle a Proxy referrer.
out->SetAccessFlags((out->GetAccessFlags() & ~kAccProtected) |
kAccPublic |
kAccCompileDontBother);
@@ -5629,12 +5619,18 @@
return false;
}
// Verify
- if (super->IsFinal() || super->IsInterface()) {
+ if (super->IsFinal()) {
+ ThrowVerifyError(klass.Get(),
+ "Superclass %s of %s is declared final",
+ super->PrettyDescriptor().c_str(),
+ klass->PrettyDescriptor().c_str());
+ return false;
+ }
+ if (super->IsInterface()) {
ThrowIncompatibleClassChangeError(klass.Get(),
- "Superclass %s of %s is %s",
+ "Superclass %s of %s is an interface",
super->PrettyDescriptor().c_str(),
- klass->PrettyDescriptor().c_str(),
- super->IsFinal() ? "declared final" : "an interface");
+ klass->PrettyDescriptor().c_str());
return false;
}
if (!klass->CanAccess(super)) {
@@ -6887,7 +6883,8 @@
// Check that there are no stale methods are in the dex cache array.
auto* resolved_methods = klass_->GetDexCache()->GetResolvedMethods();
for (size_t i = 0, count = klass_->GetDexCache()->NumResolvedMethods(); i < count; ++i) {
- auto* m = mirror::DexCache::GetElementPtrSize(resolved_methods, i, pointer_size);
+ auto pair = mirror::DexCache::GetNativePairPtrSize(resolved_methods, i, pointer_size);
+ ArtMethod* m = pair.object;
CHECK(move_table_.find(m) == move_table_.end() ||
// The original versions of copied methods will still be present so allow those too.
// Note that if the first check passes this might fail to GetDeclaringClass().
@@ -7363,10 +7360,8 @@
// defaults. This means we don't need to do any trickery when creating the Miranda methods, since
// they will already be null. This has the additional benefit that the declarer of a miranda
// method will actually declare an abstract method.
- for (size_t i = ifcount; i != 0; ) {
+ for (size_t i = ifcount; i != 0u; ) {
--i;
-
- DCHECK_GE(i, 0u);
DCHECK_LT(i, ifcount);
size_t num_methods = iftable->GetInterface(i)->NumDeclaredVirtualMethods();
@@ -7947,201 +7942,96 @@
ArtMethod* referrer,
InvokeType type) {
DCHECK(dex_cache != nullptr);
+ DCHECK(referrer == nullptr || !referrer->IsProxyMethod());
// Check for hit in the dex cache.
- ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx, image_pointer_size_);
+ PointerSize pointer_size = image_pointer_size_;
+ ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx, pointer_size);
Thread::PoisonObjectPointersIfDebug();
- if (resolved != nullptr && !resolved->IsRuntimeMethod()) {
+ DCHECK(resolved == nullptr || !resolved->IsRuntimeMethod());
+ bool valid_dex_cache_method = resolved != nullptr;
+ if (kResolveMode == ResolveMode::kNoChecks && valid_dex_cache_method) {
+ // We have a valid method from the DexCache and no checks to perform.
DCHECK(resolved->GetDeclaringClassUnchecked() != nullptr) << resolved->GetDexMethodIndex();
- if (kResolveMode == ClassLinker::kForceICCECheck) {
- if (resolved->CheckIncompatibleClassChange(type)) {
- ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer);
- return nullptr;
- }
- }
return resolved;
}
- // Fail, get the declaring class.
const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx);
- ObjPtr<mirror::Class> klass = ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader);
- if (klass == nullptr) {
+ ObjPtr<mirror::Class> klass = nullptr;
+ if (valid_dex_cache_method) {
+ // We have a valid method from the DexCache but we need to perform ICCE and IAE checks.
+ DCHECK(resolved->GetDeclaringClassUnchecked() != nullptr) << resolved->GetDexMethodIndex();
+ klass = LookupResolvedType(dex_file, method_id.class_idx_, dex_cache.Get(), class_loader.Get());
+ DCHECK(klass != nullptr);
+ } else {
+ // The method was not in the DexCache, resolve the declaring class.
+ klass = ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader);
+ if (klass == nullptr) {
+ DCHECK(Thread::Current()->IsExceptionPending());
+ return nullptr;
+ }
+ }
+
+ // Check if the invoke type matches the class type.
+ if (kResolveMode == ResolveMode::kCheckICCEAndIAE &&
+ CheckInvokeClassMismatch</* kThrow */ true>(
+ dex_cache.Get(), type, [klass]() { return klass; })) {
DCHECK(Thread::Current()->IsExceptionPending());
return nullptr;
}
- // Scan using method_idx, this saves string compares but will only hit for matching dex
- // caches/files.
- switch (type) {
- case kDirect: // Fall-through.
- case kStatic:
- resolved = klass->FindDirectMethod(dex_cache.Get(), method_idx, image_pointer_size_);
- DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr);
- break;
- case kInterface:
- // We have to check whether the method id really belongs to an interface (dex static bytecode
- // constraint A15). Otherwise you must not invoke-interface on it.
- //
- // This is not symmetric to A12-A14 (direct, static, virtual), as using FindInterfaceMethod
- // assumes that the given type is an interface, and will check the interface table if the
- // method isn't declared in the class. So it may find an interface method (usually by name
- // in the handling below, but we do the constraint check early). In that case,
- // CheckIncompatibleClassChange will succeed (as it is called on an interface method)
- // unexpectedly.
- // Example:
- // interface I {
- // foo()
- // }
- // class A implements I {
- // ...
- // }
- // class B extends A {
- // ...
- // }
- // invoke-interface B.foo
- // -> FindInterfaceMethod finds I.foo (interface method), not A.foo (miranda method)
- if (UNLIKELY(!klass->IsInterface())) {
- ThrowIncompatibleClassChangeError(klass,
- "Found class %s, but interface was expected",
- klass->PrettyDescriptor().c_str());
- return nullptr;
- } else {
- resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, image_pointer_size_);
- DCHECK(resolved == nullptr || resolved->GetDeclaringClass()->IsInterface());
- }
- break;
- case kSuper:
- if (klass->IsInterface()) {
- resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, image_pointer_size_);
- } else {
- resolved = klass->FindVirtualMethod(dex_cache.Get(), method_idx, image_pointer_size_);
- }
- break;
- case kVirtual:
- resolved = klass->FindVirtualMethod(dex_cache.Get(), method_idx, image_pointer_size_);
- break;
- default:
- LOG(FATAL) << "Unreachable - invocation type: " << type;
- UNREACHABLE();
- }
- if (resolved == nullptr) {
- // Search by name, which works across dex files.
- const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
- const Signature signature = dex_file.GetMethodSignature(method_id);
- switch (type) {
- case kDirect: // Fall-through.
- case kStatic:
- resolved = klass->FindDirectMethod(name, signature, image_pointer_size_);
- DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr);
- break;
- case kInterface:
- resolved = klass->FindInterfaceMethod(name, signature, image_pointer_size_);
- DCHECK(resolved == nullptr || resolved->GetDeclaringClass()->IsInterface());
- break;
- case kSuper:
- if (klass->IsInterface()) {
- resolved = klass->FindInterfaceMethod(name, signature, image_pointer_size_);
- } else {
- resolved = klass->FindVirtualMethod(name, signature, image_pointer_size_);
- }
- break;
- case kVirtual:
- resolved = klass->FindVirtualMethod(name, signature, image_pointer_size_);
- break;
+
+ if (!valid_dex_cache_method) {
+ // Search for the method using dex_cache and method_idx. The Class::Find*Method()
+ // functions can optimize the search if the dex_cache is the same as the DexCache
+ // of the class, with fall-back to name and signature search otherwise.
+ if (klass->IsInterface()) {
+ resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, pointer_size);
+ } else {
+ resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, pointer_size);
+ }
+ DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr);
+ if (resolved != nullptr) {
+ // Be a good citizen and update the dex cache to speed subsequent calls.
+ dex_cache->SetResolvedMethod(method_idx, resolved, pointer_size);
}
}
+
+ // Note: We can check for IllegalAccessError only if we have a referrer.
+ if (kResolveMode == ResolveMode::kCheckICCEAndIAE && resolved != nullptr && referrer != nullptr) {
+ ObjPtr<mirror::Class> methods_class = resolved->GetDeclaringClass();
+ ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
+ if (!referring_class->CheckResolvedMethodAccess(methods_class,
+ resolved,
+ dex_cache.Get(),
+ method_idx,
+ type)) {
+ DCHECK(Thread::Current()->IsExceptionPending());
+ return nullptr;
+ }
+ }
+
// If we found a method, check for incompatible class changes.
- if (LIKELY(resolved != nullptr && !resolved->CheckIncompatibleClassChange(type))) {
- // Be a good citizen and update the dex cache to speed subsequent calls.
- dex_cache->SetResolvedMethod(method_idx, resolved, image_pointer_size_);
+ if (LIKELY(resolved != nullptr) &&
+ LIKELY(kResolveMode == ResolveMode::kNoChecks ||
+ !resolved->CheckIncompatibleClassChange(type))) {
return resolved;
} else {
- // If we had a method, it's an incompatible-class-change error.
+ // If we had a method, or if we can find one with another lookup type,
+ // it's an incompatible-class-change error.
+ if (resolved == nullptr) {
+ if (klass->IsInterface()) {
+ resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, pointer_size);
+ } else {
+ // If there was an interface method with the same signature,
+ // we would have found it also in the "copied" methods.
+ DCHECK(klass->FindInterfaceMethod(dex_cache.Get(), method_idx, pointer_size) == nullptr);
+ }
+ }
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer);
} else {
- // We failed to find the method which means either an access error, an incompatible class
- // change, or no such method. First try to find the method among direct and virtual methods.
+ // We failed to find the method (using all lookup types), so throw a NoSuchMethodError.
const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
const Signature signature = dex_file.GetMethodSignature(method_id);
- switch (type) {
- case kDirect:
- case kStatic:
- resolved = klass->FindVirtualMethod(name, signature, image_pointer_size_);
- // Note: kDirect and kStatic are also mutually exclusive, but in that case we would
- // have had a resolved method before, which triggers the "true" branch above.
- break;
- case kInterface:
- case kVirtual:
- case kSuper:
- resolved = klass->FindDirectMethod(name, signature, image_pointer_size_);
- break;
- }
-
- // If we found something, check that it can be accessed by the referrer.
- bool exception_generated = false;
- if (resolved != nullptr && referrer != nullptr) {
- ObjPtr<mirror::Class> methods_class = resolved->GetDeclaringClass();
- ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
- if (!referring_class->CanAccess(methods_class)) {
- ThrowIllegalAccessErrorClassForMethodDispatch(referring_class,
- methods_class,
- resolved,
- type);
- exception_generated = true;
- } else if (!referring_class->CanAccessMember(methods_class, resolved->GetAccessFlags())) {
- ThrowIllegalAccessErrorMethod(referring_class, resolved);
- exception_generated = true;
- }
- }
- if (!exception_generated) {
- // Otherwise, throw an IncompatibleClassChangeError if we found something, and check
- // interface methods and throw if we find the method there. If we find nothing, throw a
- // NoSuchMethodError.
- switch (type) {
- case kDirect:
- case kStatic:
- if (resolved != nullptr) {
- ThrowIncompatibleClassChangeError(type, kVirtual, resolved, referrer);
- } else {
- resolved = klass->FindInterfaceMethod(name, signature, image_pointer_size_);
- if (resolved != nullptr) {
- ThrowIncompatibleClassChangeError(type, kInterface, resolved, referrer);
- } else {
- ThrowNoSuchMethodError(type, klass, name, signature);
- }
- }
- break;
- case kInterface:
- if (resolved != nullptr) {
- ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer);
- } else {
- resolved = klass->FindVirtualMethod(name, signature, image_pointer_size_);
- if (resolved != nullptr) {
- ThrowIncompatibleClassChangeError(type, kVirtual, resolved, referrer);
- } else {
- ThrowNoSuchMethodError(type, klass, name, signature);
- }
- }
- break;
- case kSuper:
- if (resolved != nullptr) {
- ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer);
- } else {
- ThrowNoSuchMethodError(type, klass, name, signature);
- }
- break;
- case kVirtual:
- if (resolved != nullptr) {
- ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer);
- } else {
- resolved = klass->FindInterfaceMethod(name, signature, image_pointer_size_);
- if (resolved != nullptr) {
- ThrowIncompatibleClassChangeError(type, kInterface, resolved, referrer);
- } else {
- ThrowNoSuchMethodError(type, klass, name, signature);
- }
- }
- break;
- }
- }
+ ThrowNoSuchMethodError(type, klass, name, signature);
}
Thread::Current()->AssertPendingException();
return nullptr;
@@ -8154,27 +8044,23 @@
Handle<mirror::ClassLoader> class_loader) {
ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx, image_pointer_size_);
Thread::PoisonObjectPointersIfDebug();
- if (resolved != nullptr && !resolved->IsRuntimeMethod()) {
+ if (resolved != nullptr) {
+ DCHECK(!resolved->IsRuntimeMethod());
DCHECK(resolved->GetDeclaringClassUnchecked() != nullptr) << resolved->GetDexMethodIndex();
return resolved;
}
// Fail, get the declaring class.
const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx);
- ObjPtr<mirror::Class> klass = ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader);
+ ObjPtr<mirror::Class> klass =
+ ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader);
if (klass == nullptr) {
Thread::Current()->AssertPendingException();
return nullptr;
}
if (klass->IsInterface()) {
- LOG(FATAL) << "ResolveAmbiguousMethod: unexpected method in interface: "
- << klass->PrettyClass();
- return nullptr;
- }
-
- // Search both direct and virtual methods
- resolved = klass->FindDirectMethod(dex_cache.Get(), method_idx, image_pointer_size_);
- if (resolved == nullptr) {
- resolved = klass->FindVirtualMethod(dex_cache.Get(), method_idx, image_pointer_size_);
+ resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, image_pointer_size_);
+ } else {
+ resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, image_pointer_size_);
}
return resolved;
@@ -8489,19 +8375,19 @@
case DexFile::MethodHandleType::kInvokeStatic: {
kind = mirror::MethodHandle::Kind::kInvokeStatic;
receiver_count = 0;
- target_method = ResolveMethod<kNoICCECheckForCache>(self,
- method_handle.field_or_method_idx_,
- referrer,
- InvokeType::kStatic);
+ target_method = ResolveMethod<ResolveMode::kNoChecks>(self,
+ method_handle.field_or_method_idx_,
+ referrer,
+ InvokeType::kStatic);
break;
}
case DexFile::MethodHandleType::kInvokeInstance: {
kind = mirror::MethodHandle::Kind::kInvokeVirtual;
receiver_count = 1;
- target_method = ResolveMethod<kNoICCECheckForCache>(self,
- method_handle.field_or_method_idx_,
- referrer,
- InvokeType::kVirtual);
+ target_method = ResolveMethod<ResolveMode::kNoChecks>(self,
+ method_handle.field_or_method_idx_,
+ referrer,
+ InvokeType::kVirtual);
break;
}
case DexFile::MethodHandleType::kInvokeConstructor: {
@@ -8509,10 +8395,10 @@
// are special cased later in this method.
kind = mirror::MethodHandle::Kind::kInvokeTransform;
receiver_count = 0;
- target_method = ResolveMethod<kNoICCECheckForCache>(self,
- method_handle.field_or_method_idx_,
- referrer,
- InvokeType::kDirect);
+ target_method = ResolveMethod<ResolveMode::kNoChecks>(self,
+ method_handle.field_or_method_idx_,
+ referrer,
+ InvokeType::kDirect);
break;
}
case DexFile::MethodHandleType::kInvokeDirect: {
@@ -8535,16 +8421,16 @@
if (target_method->IsPrivate()) {
kind = mirror::MethodHandle::Kind::kInvokeDirect;
- target_method = ResolveMethod<kNoICCECheckForCache>(self,
- method_handle.field_or_method_idx_,
- referrer,
- InvokeType::kDirect);
+ target_method = ResolveMethod<ResolveMode::kNoChecks>(self,
+ method_handle.field_or_method_idx_,
+ referrer,
+ InvokeType::kDirect);
} else {
kind = mirror::MethodHandle::Kind::kInvokeSuper;
- target_method = ResolveMethod<kNoICCECheckForCache>(self,
- method_handle.field_or_method_idx_,
- referrer,
- InvokeType::kSuper);
+ target_method = ResolveMethod<ResolveMode::kNoChecks>(self,
+ method_handle.field_or_method_idx_,
+ referrer,
+ InvokeType::kSuper);
if (UNLIKELY(target_method == nullptr)) {
break;
}
@@ -8560,10 +8446,10 @@
case DexFile::MethodHandleType::kInvokeInterface: {
kind = mirror::MethodHandle::Kind::kInvokeInterface;
receiver_count = 1;
- target_method = ResolveMethod<kNoICCECheckForCache>(self,
- method_handle.field_or_method_idx_,
- referrer,
- InvokeType::kInterface);
+ target_method = ResolveMethod<ResolveMode::kNoChecks>(self,
+ method_handle.field_or_method_idx_,
+ referrer,
+ InvokeType::kInterface);
break;
}
}
@@ -9100,51 +8986,6 @@
return ret;
}
-std::unordered_set<std::string> ClassLinker::GetClassDescriptorsForResolvedClasses(
- const std::set<DexCacheResolvedClasses>& classes) {
- ScopedTrace trace(__PRETTY_FUNCTION__);
- std::unordered_set<std::string> ret;
- Thread* const self = Thread::Current();
- std::unordered_map<std::string, const DexFile*> location_to_dex_file;
- ScopedObjectAccess soa(self);
- ScopedAssertNoThreadSuspension ants(__FUNCTION__);
- ReaderMutexLock mu(self, *Locks::dex_lock_);
- for (const ClassLinker::DexCacheData& data : GetDexCachesData()) {
- if (!self->IsJWeakCleared(data.weak_root)) {
- ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::DexCache>(data.weak_root);
- if (dex_cache != nullptr) {
- const DexFile* dex_file = dex_cache->GetDexFile();
- // There could be duplicates if two dex files with the same location are mapped.
- location_to_dex_file.emplace(dex_file->GetLocation(), dex_file);
- }
- }
- }
- for (const DexCacheResolvedClasses& info : classes) {
- const std::string& location = info.GetDexLocation();
- auto found = location_to_dex_file.find(location);
- if (found != location_to_dex_file.end()) {
- const DexFile* dex_file = found->second;
- VLOG(profiler) << "Found opened dex file for " << dex_file->GetLocation() << " with "
- << info.GetClasses().size() << " classes";
- DCHECK_EQ(dex_file->GetLocationChecksum(), info.GetLocationChecksum());
- for (dex::TypeIndex type_idx : info.GetClasses()) {
- if (!dex_file->IsTypeIndexValid(type_idx)) {
- // Something went bad. The profile is probably corrupted. Abort and return an emtpy set.
- LOG(WARNING) << "Corrupted profile: invalid type index "
- << type_idx.index_ << " in dex " << location;
- return std::unordered_set<std::string>();
- }
- const DexFile::TypeId& type_id = dex_file->GetTypeId(type_idx);
- const char* descriptor = dex_file->GetTypeDescriptor(type_id);
- ret.insert(descriptor);
- }
- } else {
- VLOG(class_linker) << "Failed to find opened dex file for location " << location;
- }
- }
- return ret;
-}
-
class ClassLinker::FindVirtualMethodHolderVisitor : public ClassVisitor {
public:
FindVirtualMethodHolderVisitor(const ArtMethod* method, PointerSize pointer_size)
@@ -9179,15 +9020,62 @@
ifcount * mirror::IfTable::kMax));
}
+ArtMethod* ClassLinker::FindMethodForProxy(ArtMethod* proxy_method) {
+ DCHECK(proxy_method->IsProxyMethod());
+ {
+ uint32_t method_index = proxy_method->GetDexMethodIndex();
+ PointerSize pointer_size = image_pointer_size_;
+ Thread* const self = Thread::Current();
+ ReaderMutexLock mu(self, *Locks::dex_lock_);
+ // Locate the dex cache of the original interface/Object
+ for (const DexCacheData& data : dex_caches_) {
+ if (!self->IsJWeakCleared(data.weak_root) &&
+ proxy_method->HasSameDexCacheResolvedMethods(data.resolved_methods, pointer_size)) {
+ ObjPtr<mirror::DexCache> dex_cache =
+ ObjPtr<mirror::DexCache>::DownCast(self->DecodeJObject(data.weak_root));
+ if (dex_cache != nullptr) {
+ // Lookup up the method. Instead of going through LookupResolvedMethod()
+ // and thus LookupResolvedType(), use the ClassTable from the DexCacheData.
+ ArtMethod* resolved_method = dex_cache->GetResolvedMethod(method_index, pointer_size);
+ if (resolved_method == nullptr) {
+ const DexFile::MethodId& method_id = data.dex_file->GetMethodId(method_index);
+ ObjPtr<mirror::Class> klass = dex_cache->GetResolvedType(method_id.class_idx_);
+ if (klass == nullptr) {
+ const char* descriptor = data.dex_file->StringByTypeIdx(method_id.class_idx_);
+ klass = data.class_table->Lookup(descriptor, ComputeModifiedUtf8Hash(descriptor));
+ DCHECK(klass != nullptr);
+ dex_cache->SetResolvedType(method_id.class_idx_, klass);
+ }
+ if (klass->IsInterface()) {
+ resolved_method = klass->FindInterfaceMethod(dex_cache, method_index, pointer_size);
+ } else {
+ DCHECK(
+ klass == WellKnownClasses::ToClass(WellKnownClasses::java_lang_reflect_Proxy) ||
+ klass == WellKnownClasses::ToClass(WellKnownClasses::java_lang_Object));
+ resolved_method = klass->FindClassMethod(dex_cache, method_index, pointer_size);
+ }
+ CHECK(resolved_method != nullptr);
+ dex_cache->SetResolvedMethod(method_index, resolved_method, pointer_size);
+ }
+ return resolved_method;
+ }
+ }
+ }
+ }
+ // Note: Do not use proxy_method->PrettyMethod() as it can call back here.
+ LOG(FATAL) << "Didn't find dex cache for " << proxy_method->GetDeclaringClass()->PrettyClass();
+ UNREACHABLE();
+}
+
// Instantiate ResolveMethod.
-template ArtMethod* ClassLinker::ResolveMethod<ClassLinker::kForceICCECheck>(
+template ArtMethod* ClassLinker::ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
const DexFile& dex_file,
uint32_t method_idx,
Handle<mirror::DexCache> dex_cache,
Handle<mirror::ClassLoader> class_loader,
ArtMethod* referrer,
InvokeType type);
-template ArtMethod* ClassLinker::ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+template ArtMethod* ClassLinker::ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
const DexFile& dex_file,
uint32_t method_idx,
Handle<mirror::DexCache> dex_cache,
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 864d37f..cb28187 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -55,6 +55,9 @@
class MethodType;
template<class T> class ObjectArray;
class StackTraceElement;
+ template <typename T> struct NativeDexCachePair;
+ using MethodDexCachePair = NativeDexCachePair<ArtMethod>;
+ using MethodDexCacheType = std::atomic<MethodDexCachePair>;
} // namespace mirror
class ClassTable;
@@ -281,12 +284,18 @@
REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
// Determine whether a dex cache result should be trusted, or an IncompatibleClassChangeError
- // check should be performed even after a hit.
- enum ResolveMode { // private.
- kNoICCECheckForCache,
- kForceICCECheck
+ // check and IllegalAccessError check should be performed even after a hit.
+ enum class ResolveMode { // private.
+ kNoChecks,
+ kCheckICCEAndIAE
};
+ // Look up a previously resolved method with the given index.
+ ArtMethod* LookupResolvedMethod(uint32_t method_idx,
+ ObjPtr<mirror::DexCache> dex_cache,
+ ObjPtr<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Resolve a method with a given ID from the DexFile, storing the
// result in DexCache. The ClassLinker and ClassLoader are used as
// in ResolveType. What is unique is the method type argument which
@@ -302,17 +311,10 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
+ template <InvokeType type, ResolveMode kResolveMode>
ArtMethod* GetResolvedMethod(uint32_t method_idx, ArtMethod* referrer)
REQUIRES_SHARED(Locks::mutator_lock_);
- // This returns the class referred to by GetMethodId(method_idx).class_idx_. This might be
- // different then the declaring class of the resolved method due to copied
- // miranda/default/conflict methods.
- mirror::Class* ResolveReferencedClassOfMethod(uint32_t method_idx,
- Handle<mirror::DexCache> dex_cache,
- Handle<mirror::ClassLoader> class_loader)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
template <ResolveMode kResolveMode>
ArtMethod* ResolveMethod(Thread* self, uint32_t method_idx, ArtMethod* referrer, InvokeType type)
REQUIRES_SHARED(Locks::mutator_lock_)
@@ -430,9 +432,6 @@
ClassTable* FindClassTable(Thread* self, ObjPtr<mirror::DexCache> dex_cache)
REQUIRES(!Locks::dex_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- void FixupDexCaches(ArtMethod* resolution_method)
- REQUIRES(!Locks::dex_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
LengthPrefixedArray<ArtField>* AllocArtFieldArray(Thread* self,
LinearAlloc* allocator,
@@ -482,8 +481,7 @@
REQUIRES_SHARED(Locks::mutator_lock_);
std::string GetDescriptorForProxy(ObjPtr<mirror::Class> proxy_class)
REQUIRES_SHARED(Locks::mutator_lock_);
- template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
- ArtMethod* FindMethodForProxy(ObjPtr<mirror::Class> proxy_class, ArtMethod* proxy_method)
+ ArtMethod* FindMethodForProxy(ArtMethod* proxy_method)
REQUIRES(!Locks::dex_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -616,11 +614,6 @@
std::set<DexCacheResolvedClasses> GetResolvedClasses(bool ignore_boot_classes)
REQUIRES(!Locks::dex_lock_);
- // Returns the class descriptors for loaded dex files.
- std::unordered_set<std::string> GetClassDescriptorsForResolvedClasses(
- const std::set<DexCacheResolvedClasses>& classes)
- REQUIRES(!Locks::dex_lock_);
-
static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
ObjPtr<mirror::ClassLoader> class_loader)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -699,7 +692,7 @@
// jweak decode that triggers read barriers (and mark them alive unnecessarily and mess with
// class unloading.)
const DexFile* dex_file;
- ArtMethod** resolved_methods;
+ mirror::MethodDexCacheType* resolved_methods;
// Identify the associated class loader's class table. This is used to make sure that
// the Java call to native DexCache.setResolvedType() inserts the resolved type in that
// class table. It is also used to make sure we don't register the same dex cache with
@@ -1205,6 +1198,23 @@
bool* new_conflict,
ArtMethod** imt) REQUIRES_SHARED(Locks::mutator_lock_);
+ // Check invoke type against the referenced class. Throws IncompatibleClassChangeError
+ // (if `kThrowOnError`) and returns true on mismatch (kInterface on a non-interface class,
+ // kVirtual on interface, kDefault on interface for dex files not supporting default methods),
+ // otherwise returns false.
+ template <bool kThrowOnError, typename ClassGetter>
+ static bool CheckInvokeClassMismatch(ObjPtr<mirror::DexCache> dex_cache,
+ InvokeType type,
+ ClassGetter class_getter)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Helper that feeds the above function with `ClassGetter` doing `LookupResolvedType()`.
+ template <bool kThrow>
+ bool CheckInvokeClassMismatch(ObjPtr<mirror::DexCache> dex_cache,
+ InvokeType type,
+ uint32_t method_idx,
+ ObjPtr<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
std::vector<const DexFile*> boot_class_path_;
std::vector<std::unique_ptr<const DexFile>> boot_dex_files_;
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 03cc6c5..39d77f0 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -440,14 +440,6 @@
}
TestRootVisitor visitor;
class_linker_->VisitRoots(&visitor, kVisitRootFlagAllRoots);
- // Verify the dex cache has resolution methods in all resolved method slots
- ObjPtr<mirror::DexCache> dex_cache = class_linker_->FindDexCache(Thread::Current(), dex);
- auto* resolved_methods = dex_cache->GetResolvedMethods();
- for (size_t i = 0, num_methods = dex_cache->NumResolvedMethods(); i != num_methods; ++i) {
- EXPECT_TRUE(
- mirror::DexCache::GetElementPtrSize(resolved_methods, i, kRuntimePointerSize) != nullptr)
- << dex.GetLocation() << " i=" << i;
- }
}
class TestRootVisitor : public SingleRootVisitor {
@@ -1121,7 +1113,7 @@
// Static final primitives that are initialized by a compile-time constant
// expression resolve to a copy of a constant value from the constant pool.
// So <clinit> should be null.
- ArtMethod* clinit = statics->FindDirectMethod("<clinit>", "()V", kRuntimePointerSize);
+ ArtMethod* clinit = statics->FindClassMethod("<clinit>", "()V", kRuntimePointerSize);
EXPECT_TRUE(clinit == nullptr);
EXPECT_EQ(9U, statics->NumStaticFields());
@@ -1208,24 +1200,30 @@
EXPECT_TRUE(J->IsAssignableFrom(B.Get()));
const Signature void_sig = I->GetDexCache()->GetDexFile()->CreateSignature("()V");
- ArtMethod* Ii = I->FindVirtualMethod("i", void_sig, kRuntimePointerSize);
- ArtMethod* Jj1 = J->FindVirtualMethod("j1", void_sig, kRuntimePointerSize);
- ArtMethod* Jj2 = J->FindVirtualMethod("j2", void_sig, kRuntimePointerSize);
+ ArtMethod* Ii = I->FindClassMethod("i", void_sig, kRuntimePointerSize);
+ ArtMethod* Jj1 = J->FindClassMethod("j1", void_sig, kRuntimePointerSize);
+ ArtMethod* Jj2 = J->FindClassMethod("j2", void_sig, kRuntimePointerSize);
ArtMethod* Kj1 = K->FindInterfaceMethod("j1", void_sig, kRuntimePointerSize);
ArtMethod* Kj2 = K->FindInterfaceMethod("j2", void_sig, kRuntimePointerSize);
ArtMethod* Kk = K->FindInterfaceMethod("k", void_sig, kRuntimePointerSize);
- ArtMethod* Ai = A->FindVirtualMethod("i", void_sig, kRuntimePointerSize);
- ArtMethod* Aj1 = A->FindVirtualMethod("j1", void_sig, kRuntimePointerSize);
- ArtMethod* Aj2 = A->FindVirtualMethod("j2", void_sig, kRuntimePointerSize);
+ ArtMethod* Ai = A->FindClassMethod("i", void_sig, kRuntimePointerSize);
+ ArtMethod* Aj1 = A->FindClassMethod("j1", void_sig, kRuntimePointerSize);
+ ArtMethod* Aj2 = A->FindClassMethod("j2", void_sig, kRuntimePointerSize);
ASSERT_TRUE(Ii != nullptr);
+ ASSERT_FALSE(Ii->IsDirect());
ASSERT_TRUE(Jj1 != nullptr);
+ ASSERT_FALSE(Jj1->IsDirect());
ASSERT_TRUE(Jj2 != nullptr);
+ ASSERT_FALSE(Jj2->IsDirect());
ASSERT_TRUE(Kj1 != nullptr);
ASSERT_TRUE(Kj2 != nullptr);
ASSERT_TRUE(Kk != nullptr);
ASSERT_TRUE(Ai != nullptr);
+ ASSERT_FALSE(Ai->IsDirect());
ASSERT_TRUE(Aj1 != nullptr);
+ ASSERT_FALSE(Aj1->IsDirect());
ASSERT_TRUE(Aj2 != nullptr);
+ ASSERT_FALSE(Aj2->IsDirect());
EXPECT_NE(Ii, Ai);
EXPECT_NE(Jj1, Aj1);
EXPECT_NE(Jj2, Aj2);
@@ -1266,7 +1264,10 @@
hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader)));
mirror::Class* klass = class_linker_->FindClass(soa.Self(), "LStaticsFromCode;", class_loader);
ArtMethod* clinit = klass->FindClassInitializer(kRuntimePointerSize);
- ArtMethod* getS0 = klass->FindDirectMethod("getS0", "()Ljava/lang/Object;", kRuntimePointerSize);
+ ArtMethod* getS0 =
+ klass->FindClassMethod("getS0", "()Ljava/lang/Object;", kRuntimePointerSize);
+ ASSERT_TRUE(getS0 != nullptr);
+ ASSERT_TRUE(getS0->IsStatic());
const DexFile::TypeId* type_id = dex_file->FindTypeId("LStaticsFromCode;");
ASSERT_TRUE(type_id != nullptr);
dex::TypeIndex type_idx = dex_file->GetIndexForTypeId(*type_id);
@@ -1489,9 +1490,12 @@
hs.NewHandle(class_linker_->FindClass(soa.Self(), "LMethodTypes;", class_loader)));
class_linker_->EnsureInitialized(soa.Self(), method_types, true, true);
- ArtMethod* method1 = method_types->FindVirtualMethod("method1",
- "(Ljava/lang/String;)Ljava/lang/String;",
- kRuntimePointerSize);
+ ArtMethod* method1 = method_types->FindClassMethod(
+ "method1",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ kRuntimePointerSize);
+ ASSERT_TRUE(method1 != nullptr);
+ ASSERT_FALSE(method1->IsDirect());
const DexFile& dex_file = *(method1->GetDexFile());
Handle<mirror::DexCache> dex_cache = hs.NewHandle(
@@ -1522,10 +1526,12 @@
// Resolve the MethodType associated with a different method signature
// and assert it's different.
- ArtMethod* method2 = method_types->FindVirtualMethod(
+ ArtMethod* method2 = method_types->FindClassMethod(
"method2",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
kRuntimePointerSize);
+ ASSERT_TRUE(method2 != nullptr);
+ ASSERT_FALSE(method2->IsDirect());
const DexFile::MethodId& method2_id = dex_file.GetMethodId(method2->GetDexMethodIndex());
Handle<mirror::MethodType> method2_type = hs.NewHandle(
class_linker_->ResolveMethodType(dex_file, method2_id.proto_idx_, dex_cache, class_loader));
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index a425224..7e762c3 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -425,7 +425,6 @@
PostRuntimeCreate();
runtime_.reset(Runtime::Current());
class_linker_ = runtime_->GetClassLinker();
- class_linker_->FixupDexCaches(runtime_->GetResolutionMethod());
// Runtime::Create acquired the mutator_lock_ that is normally given away when we
// Runtime::Start, give it away now and then switch to a more managable ScopedObjectAccess.
@@ -786,6 +785,60 @@
return classpath;
}
+void CommonRuntimeTestImpl::FillHeap(Thread* self,
+ ClassLinker* class_linker,
+ VariableSizedHandleScope* handle_scope) {
+ DCHECK(handle_scope != nullptr);
+
+ Runtime::Current()->GetHeap()->SetIdealFootprint(1 * GB);
+
+ // Class java.lang.Object.
+ Handle<mirror::Class> c(handle_scope->NewHandle(
+ class_linker->FindSystemClass(self, "Ljava/lang/Object;")));
+ // Array helps to fill memory faster.
+ Handle<mirror::Class> ca(handle_scope->NewHandle(
+ class_linker->FindSystemClass(self, "[Ljava/lang/Object;")));
+
+ // Start allocating with ~128K
+ size_t length = 128 * KB;
+ while (length > 40) {
+ const int32_t array_length = length / 4; // Object[] has elements of size 4.
+ MutableHandle<mirror::Object> h(handle_scope->NewHandle<mirror::Object>(
+ mirror::ObjectArray<mirror::Object>::Alloc(self, ca.Get(), array_length)));
+ if (self->IsExceptionPending() || h == nullptr) {
+ self->ClearException();
+
+ // Try a smaller length
+ length = length / 2;
+ // Use at most a quarter the reported free space.
+ size_t mem = Runtime::Current()->GetHeap()->GetFreeMemory();
+ if (length * 4 > mem) {
+ length = mem / 4;
+ }
+ }
+ }
+
+ // Allocate simple objects till it fails.
+ while (!self->IsExceptionPending()) {
+ handle_scope->NewHandle<mirror::Object>(c->AllocObject(self));
+ }
+ self->ClearException();
+}
+
+void CommonRuntimeTestImpl::SetUpRuntimeOptionsForFillHeap(RuntimeOptions *options) {
+ // Use a smaller heap
+ bool found = false;
+ for (std::pair<std::string, const void*>& pair : *options) {
+ if (pair.first.find("-Xmx") == 0) {
+ pair.first = "-Xmx4M"; // Smallest we can go.
+ found = true;
+ }
+ }
+ if (!found) {
+ options->emplace_back("-Xmx4M", nullptr);
+ }
+}
+
CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) {
vm_->SetCheckJniAbortHook(Hook, &actual_);
}
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index daf9ac3..74bc0b2 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -44,6 +44,8 @@
class JavaVMExt;
class Runtime;
typedef std::vector<std::pair<std::string, const void*>> RuntimeOptions;
+class Thread;
+class VariableSizedHandleScope;
uint8_t* DecodeBase64(const char* src, size_t* dst_size);
@@ -105,6 +107,14 @@
// Retuerns the filename for a test dex (i.e. XandY or ManyMethods).
std::string GetTestDexFileName(const char* name) const;
+ // A helper function to fill the heap.
+ static void FillHeap(Thread* self,
+ ClassLinker* class_linker,
+ VariableSizedHandleScope* handle_scope)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // A helper to set up a small heap (4M) to make FillHeap faster.
+ static void SetUpRuntimeOptionsForFillHeap(RuntimeOptions *options);
+
protected:
// Allow subclases such as CommonCompilerTest to add extra options.
virtual void SetUpRuntimeOptions(RuntimeOptions* options ATTRIBUTE_UNUSED) {}
diff --git a/runtime/compiler_filter.cc b/runtime/compiler_filter.cc
index 4847f38..7b2dd05 100644
--- a/runtime/compiler_filter.cc
+++ b/runtime/compiler_filter.cc
@@ -165,6 +165,10 @@
return current >= target;
}
+bool CompilerFilter::IsBetter(Filter current, Filter target) {
+ return current > target;
+}
+
std::string CompilerFilter::NameOfFilter(Filter filter) {
switch (filter) {
case CompilerFilter::kAssumeVerified: return "assume-verified";
diff --git a/runtime/compiler_filter.h b/runtime/compiler_filter.h
index f802439..60975b0 100644
--- a/runtime/compiler_filter.h
+++ b/runtime/compiler_filter.h
@@ -84,6 +84,11 @@
// not as good as kSpeed.
static bool IsAsGoodAs(Filter current, Filter target);
+ // Returns true if 'current' compiler filter is better than 'target' compiler
+ // filter. Compared to IsAsGoodAs, this returns false if the compiler filters are
+ // equal.
+ static bool IsBetter(Filter current, Filter target);
+
// Return the flag name of the given filter.
// For example: given kVerifyAtRuntime, returns "verify-at-runtime".
// The name returned corresponds to the name accepted by
diff --git a/runtime/dex_file-inl.h b/runtime/dex_file-inl.h
index 41db4d8..b163cdb 100644
--- a/runtime/dex_file-inl.h
+++ b/runtime/dex_file-inl.h
@@ -181,19 +181,18 @@
if (lhs_shorty.find('L', 1) != StringPiece::npos) {
const DexFile::TypeList* params = dex_file_->GetProtoParameters(*proto_id_);
const DexFile::TypeList* rhs_params = rhs.dex_file_->GetProtoParameters(*rhs.proto_id_);
- // Both lists are empty or have contents, or else shorty is broken.
- DCHECK_EQ(params == nullptr, rhs_params == nullptr);
- if (params != nullptr) {
- uint32_t params_size = params->Size();
- DCHECK_EQ(params_size, rhs_params->Size()); // Parameter list size must match.
- for (uint32_t i = 0; i < params_size; ++i) {
- const DexFile::TypeId& param_id = dex_file_->GetTypeId(params->GetTypeItem(i).type_idx_);
- const DexFile::TypeId& rhs_param_id =
- rhs.dex_file_->GetTypeId(rhs_params->GetTypeItem(i).type_idx_);
- if (!DexFileStringEquals(dex_file_, param_id.descriptor_idx_,
- rhs.dex_file_, rhs_param_id.descriptor_idx_)) {
- return false; // Parameter type mismatch.
- }
+ // We found a reference parameter in the matching shorty, so both lists must be non-empty.
+ DCHECK(params != nullptr);
+ DCHECK(rhs_params != nullptr);
+ uint32_t params_size = params->Size();
+ DCHECK_EQ(params_size, rhs_params->Size()); // Parameter list size must match.
+ for (uint32_t i = 0; i < params_size; ++i) {
+ const DexFile::TypeId& param_id = dex_file_->GetTypeId(params->GetTypeItem(i).type_idx_);
+ const DexFile::TypeId& rhs_param_id =
+ rhs.dex_file_->GetTypeId(rhs_params->GetTypeItem(i).type_idx_);
+ if (!DexFileStringEquals(dex_file_, param_id.descriptor_idx_,
+ rhs.dex_file_, rhs_param_id.descriptor_idx_)) {
+ return false; // Parameter type mismatch.
}
}
}
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index 6547299..a6c5d6c 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -50,6 +50,8 @@
const InlineInfoEncoding& encoding,
uint8_t inlining_depth)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!outer_method->IsObsolete());
+
// This method is being used by artQuickResolutionTrampoline, before it sets up
// the passed parameters in a GC friendly way. Therefore we must never be
// suspended while executing it.
@@ -78,10 +80,12 @@
}
// Lookup the declaring class of the inlined method.
- const DexFile* dex_file = caller->GetDexFile();
+ ObjPtr<mirror::DexCache> dex_cache = caller->GetDexCache();
+ const DexFile* dex_file = dex_cache->GetDexFile();
const DexFile::MethodId& method_id = dex_file->GetMethodId(method_index);
ArtMethod* inlined_method = caller->GetDexCacheResolvedMethod(method_index, kRuntimePointerSize);
- if (inlined_method != nullptr && !inlined_method->IsRuntimeMethod()) {
+ if (inlined_method != nullptr) {
+ DCHECK(!inlined_method->IsRuntimeMethod());
return inlined_method;
}
const char* descriptor = dex_file->StringByTypeIdx(method_id.class_idx_);
@@ -90,25 +94,17 @@
mirror::ClassLoader* class_loader = caller->GetDeclaringClass()->GetClassLoader();
mirror::Class* klass = class_linker->LookupClass(self, descriptor, class_loader);
if (klass == nullptr) {
- LOG(FATAL) << "Could not find an inlined method from an .oat file: "
- << "the class " << descriptor << " was not found in the class loader of "
- << caller->PrettyMethod() << ". "
- << "This must be due to playing wrongly with class loaders";
+ LOG(FATAL) << "Could not find an inlined method from an .oat file: the class " << descriptor
+ << " was not found in the class loader of " << caller->PrettyMethod() << ". "
+ << "This must be due to playing wrongly with class loaders";
}
- // Lookup the method.
- const char* method_name = dex_file->GetMethodName(method_id);
- const Signature signature = dex_file->GetMethodSignature(method_id);
-
- inlined_method = klass->FindDeclaredDirectMethod(method_name, signature, kRuntimePointerSize);
+ inlined_method = klass->FindClassMethod(dex_cache, method_index, kRuntimePointerSize);
if (inlined_method == nullptr) {
- inlined_method = klass->FindDeclaredVirtualMethod(method_name, signature, kRuntimePointerSize);
- if (inlined_method == nullptr) {
- LOG(FATAL) << "Could not find an inlined method from an .oat file: "
- << "the class " << descriptor << " does not have "
- << method_name << signature << " declared. "
- << "This must be due to duplicate classes or playing wrongly with class loaders";
- }
+ LOG(FATAL) << "Could not find an inlined method from an .oat file: the class " << descriptor
+ << " does not have " << dex_file->GetMethodName(method_id)
+ << dex_file->GetMethodSignature(method_id) << " declared. "
+ << "This must be due to duplicate classes or playing wrongly with class loaders";
}
caller->SetDexCacheResolvedMethod(method_index, inlined_method, kRuntimePointerSize);
@@ -444,39 +440,20 @@
ArtMethod* referrer,
Thread* self) {
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- ArtMethod* resolved_method = class_linker->GetResolvedMethod(method_idx, referrer);
- if (resolved_method == nullptr) {
+ constexpr ClassLinker::ResolveMode resolve_mode =
+ access_check ? ClassLinker::ResolveMode::kCheckICCEAndIAE
+ : ClassLinker::ResolveMode::kNoChecks;
+ ArtMethod* resolved_method;
+ if (type == kStatic) {
+ resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, referrer, type);
+ } else {
StackHandleScope<1> hs(self);
- ObjPtr<mirror::Object> null_this = nullptr;
- HandleWrapperObjPtr<mirror::Object> h_this(
- hs.NewHandleWrapper(type == kStatic ? &null_this : this_object));
- constexpr ClassLinker::ResolveMode resolve_mode =
- access_check ? ClassLinker::kForceICCECheck
- : ClassLinker::kNoICCECheckForCache;
+ HandleWrapperObjPtr<mirror::Object> h_this(hs.NewHandleWrapper(this_object));
resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, referrer, type);
}
- // Resolution and access check.
if (UNLIKELY(resolved_method == nullptr)) {
DCHECK(self->IsExceptionPending()); // Throw exception and unwind.
return nullptr; // Failure.
- } else if (access_check) {
- mirror::Class* methods_class = resolved_method->GetDeclaringClass();
- bool can_access_resolved_method =
- referrer->GetDeclaringClass()->CheckResolvedMethodAccess(methods_class,
- resolved_method,
- referrer->GetDexCache(),
- method_idx,
- type);
- if (UNLIKELY(!can_access_resolved_method)) {
- DCHECK(self->IsExceptionPending()); // Throw exception and unwind.
- return nullptr; // Failure.
- }
- // Incompatible class change should have been handled in resolve method.
- if (UNLIKELY(resolved_method->CheckIncompatibleClassChange(type))) {
- ThrowIncompatibleClassChangeError(type, resolved_method->GetInvokeType(), resolved_method,
- referrer);
- return nullptr; // Failure.
- }
}
// Next, null pointer check.
if (UNLIKELY(*this_object == nullptr && type != kStatic)) {
@@ -690,24 +667,14 @@
}
ObjPtr<mirror::Class> referring_class = referrer->GetDeclaringClass();
ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
- ArtMethod* resolved_method = dex_cache->GetResolvedMethod(method_idx, kRuntimePointerSize);
+ constexpr ClassLinker::ResolveMode resolve_mode = access_check
+ ? ClassLinker::ResolveMode::kCheckICCEAndIAE
+ : ClassLinker::ResolveMode::kNoChecks;
+ ClassLinker* linker = Runtime::Current()->GetClassLinker();
+ ArtMethod* resolved_method = linker->GetResolvedMethod<type, resolve_mode>(method_idx, referrer);
if (UNLIKELY(resolved_method == nullptr)) {
return nullptr;
}
- if (access_check) {
- // Check for incompatible class change errors and access.
- bool icce = resolved_method->CheckIncompatibleClassChange(type);
- if (UNLIKELY(icce)) {
- return nullptr;
- }
- ObjPtr<mirror::Class> methods_class = resolved_method->GetDeclaringClass();
- if (UNLIKELY(!referring_class->CanAccess(methods_class) ||
- !referring_class->CanAccessMember(methods_class,
- resolved_method->GetAccessFlags()))) {
- // Potential illegal access, may need to refine the method's class.
- return nullptr;
- }
- }
if (type == kInterface) { // Most common form of slow path dispatch.
return this_object->GetClass()->FindVirtualMethodForInterface(resolved_method,
kRuntimePointerSize);
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 36885d8..3061365 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -1182,7 +1182,7 @@
HandleWrapper<mirror::Object> h_receiver(
hs.NewHandleWrapper(virtual_or_interface ? &receiver : &dummy));
DCHECK_EQ(caller->GetDexFile(), called_method.dex_file);
- called = linker->ResolveMethod<ClassLinker::kForceICCECheck>(
+ called = linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
self, called_method.dex_method_index, caller, invoke_type);
// Update .bss entry in oat file if any.
@@ -1235,8 +1235,11 @@
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(caller->GetDeclaringClass()->GetClassLoader()));
// TODO Maybe put this into a mirror::Class function.
- mirror::Class* ref_class = linker->ResolveReferencedClassOfMethod(
- called_method.dex_method_index, dex_cache, class_loader);
+ ObjPtr<mirror::Class> ref_class = linker->LookupResolvedType(
+ *dex_cache->GetDexFile(),
+ dex_cache->GetDexFile()->GetMethodId(called_method.dex_method_index).class_idx_,
+ dex_cache.Get(),
+ class_loader.Get());
if (ref_class->IsInterface()) {
called = ref_class->FindVirtualMethodForInterfaceSuper(called, kRuntimePointerSize);
} else {
@@ -2458,6 +2461,21 @@
return artInvokeCommon<kVirtual, true>(method_idx, this_object, self, sp);
}
+// Helper function for art_quick_imt_conflict_trampoline to look up the interface method.
+extern "C" ArtMethod* artLookupResolvedMethod(uint32_t method_index, ArtMethod* referrer)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ScopedAssertNoThreadSuspension ants(__FUNCTION__);
+ DCHECK(!referrer->IsProxyMethod());
+ ArtMethod* result = Runtime::Current()->GetClassLinker()->LookupResolvedMethod(
+ method_index, referrer->GetDexCache(), referrer->GetClassLoader());
+ DCHECK(result == nullptr ||
+ result->GetDeclaringClass()->IsInterface() ||
+ result->GetDeclaringClass() ==
+ WellKnownClasses::ToClass(WellKnownClasses::java_lang_Object))
+ << result->PrettyMethod();
+ return result;
+}
+
// Determine target of interface dispatch. The interface method and this object are known non-null.
// The interface method is the method returned by the dex cache in the conflict trampoline.
extern "C" TwoWordReturn artInvokeInterfaceTrampoline(ArtMethod* interface_method,
@@ -2465,7 +2483,6 @@
Thread* self,
ArtMethod** sp)
REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK(interface_method != nullptr);
ObjPtr<mirror::Object> this_object(raw_this_object);
ScopedQuickEntrypointChecks sqec(self);
StackHandleScope<1> hs(self);
@@ -2475,7 +2492,8 @@
ArtMethod* method = nullptr;
ImTable* imt = cls->GetImt(kRuntimePointerSize);
- if (LIKELY(interface_method->GetDexMethodIndex() != DexFile::kDexNoIndex)) {
+ if (LIKELY(interface_method != nullptr)) {
+ DCHECK_NE(interface_method->GetDexMethodIndex(), DexFile::kDexNoIndex);
// If the interface method is already resolved, look whether we have a match in the
// ImtConflictTable.
ArtMethod* conflict_method = imt->Get(ImTable::GetImtIndex(interface_method),
@@ -2502,9 +2520,7 @@
return GetTwoWordFailureValue(); // Failure.
}
} else {
- // The interface method is unresolved, so look it up in the dex file of the caller.
- DCHECK_EQ(interface_method, Runtime::Current()->GetResolutionMethod());
-
+ // The interface method is unresolved, so resolve it in the dex file of the caller.
// Fetch the dex_method_idx of the target interface method from the caller.
uint32_t dex_method_idx;
uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
@@ -2622,10 +2638,8 @@
// Resolve method - it's either MethodHandle.invoke() or MethodHandle.invokeExact().
ClassLinker* linker = Runtime::Current()->GetClassLinker();
- ArtMethod* resolved_method = linker->ResolveMethod<ClassLinker::kForceICCECheck>(self,
- inst->VRegB(),
- caller_method,
- kVirtual);
+ ArtMethod* resolved_method = linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
+ self, inst->VRegB(), caller_method, kVirtual);
DCHECK((resolved_method ==
jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact)) ||
(resolved_method ==
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 3ae382e..fe0d35f 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1268,17 +1268,19 @@
}
dex_cache->FixupResolvedTypes<kWithoutReadBarrier>(new_types, fixup_adapter);
}
- ArtMethod** methods = dex_cache->GetResolvedMethods();
+ mirror::MethodDexCacheType* methods = dex_cache->GetResolvedMethods();
if (methods != nullptr) {
- ArtMethod** new_methods = fixup_adapter.ForwardObject(methods);
+ mirror::MethodDexCacheType* new_methods = fixup_adapter.ForwardObject(methods);
if (methods != new_methods) {
dex_cache->SetResolvedMethods(new_methods);
}
for (size_t j = 0, num = dex_cache->NumResolvedMethods(); j != num; ++j) {
- ArtMethod* orig = mirror::DexCache::GetElementPtrSize(new_methods, j, pointer_size);
+ auto pair = mirror::DexCache::GetNativePairPtrSize(new_methods, j, pointer_size);
+ ArtMethod* orig = pair.object;
ArtMethod* copy = fixup_adapter.ForwardObject(orig);
if (orig != copy) {
- mirror::DexCache::SetElementPtrSize(new_methods, j, copy, pointer_size);
+ pair.object = copy;
+ mirror::DexCache::SetNativePairPtrSize(new_methods, j, pair, pointer_size);
}
}
}
diff --git a/runtime/generated/asm_support_gen.h b/runtime/generated/asm_support_gen.h
index 06e4704..11b3abb 100644
--- a/runtime/generated/asm_support_gen.h
+++ b/runtime/generated/asm_support_gen.h
@@ -78,6 +78,10 @@
DEFINE_CHECK_EQ(static_cast<int32_t>(STRING_DEX_CACHE_HASH_BITS), (static_cast<int32_t>(art::LeastSignificantBit(art::mirror::DexCache::kDexCacheStringCacheSize))))
#define STRING_DEX_CACHE_ELEMENT_SIZE 8
DEFINE_CHECK_EQ(static_cast<int32_t>(STRING_DEX_CACHE_ELEMENT_SIZE), (static_cast<int32_t>(sizeof(art::mirror::StringDexCachePair))))
+#define METHOD_DEX_CACHE_SIZE_MINUS_ONE 1023
+DEFINE_CHECK_EQ(static_cast<int32_t>(METHOD_DEX_CACHE_SIZE_MINUS_ONE), (static_cast<int32_t>(art::mirror::DexCache::kDexCacheMethodCacheSize - 1)))
+#define METHOD_DEX_CACHE_HASH_BITS 10
+DEFINE_CHECK_EQ(static_cast<int32_t>(METHOD_DEX_CACHE_HASH_BITS), (static_cast<int32_t>(art::LeastSignificantBit(art::mirror::DexCache::kDexCacheMethodCacheSize))))
#define CARD_TABLE_CARD_SHIFT 0xa
DEFINE_CHECK_EQ(static_cast<size_t>(CARD_TABLE_CARD_SHIFT), (static_cast<size_t>(art::gc::accounting::CardTable::kCardShift)))
#define MIN_LARGE_OBJECT_THRESHOLD 0x3000
@@ -110,6 +114,8 @@
DEFINE_CHECK_EQ(static_cast<int32_t>(LOCK_WORD_MARK_BIT_SHIFT), (static_cast<int32_t>(art::LockWord::kMarkBitStateShift)))
#define LOCK_WORD_MARK_BIT_MASK_SHIFTED 0x20000000
DEFINE_CHECK_EQ(static_cast<uint32_t>(LOCK_WORD_MARK_BIT_MASK_SHIFTED), (static_cast<uint32_t>(art::LockWord::kMarkBitStateMaskShifted)))
+#define STD_MEMORY_ORDER_RELAXED 0
+DEFINE_CHECK_EQ(static_cast<int32_t>(STD_MEMORY_ORDER_RELAXED), (static_cast<int32_t>(std::memory_order_relaxed)))
#define OBJECT_ALIGNMENT_MASK 0x7
DEFINE_CHECK_EQ(static_cast<size_t>(OBJECT_ALIGNMENT_MASK), (static_cast<size_t>(art::kObjectAlignment - 1)))
#define OBJECT_ALIGNMENT_MASK_TOGGLED 0xfffffff8
diff --git a/runtime/image.cc b/runtime/image.cc
index ac36d7c..7d0a709 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -26,7 +26,7 @@
namespace art {
const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '5', '\0' }; // Fix DexCache fields.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '6', '\0' }; // Hash-based methods array.
ImageHeader::ImageHeader(uint32_t image_begin,
uint32_t image_size,
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
index 2a601c9..9e9fa71 100644
--- a/runtime/instrumentation_test.cc
+++ b/runtime/instrumentation_test.cc
@@ -16,6 +16,7 @@
#include "instrumentation.h"
+#include "art_method-inl.h"
#include "base/enums.h"
#include "common_runtime_test.h"
#include "common_throws.h"
@@ -484,10 +485,11 @@
Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
ASSERT_TRUE(klass != nullptr);
- ArtMethod* method = klass->FindDeclaredDirectMethod("returnReference",
- "()Ljava/lang/Object;",
- kRuntimePointerSize);
+ ArtMethod* method =
+ klass->FindClassMethod("returnReference", "()Ljava/lang/Object;", kRuntimePointerSize);
ASSERT_TRUE(method != nullptr);
+ ASSERT_TRUE(method->IsDirect());
+ ASSERT_TRUE(method->GetDeclaringClass() == klass);
TestEvent(instrumentation::Instrumentation::kMethodExited,
/*event_method*/ method,
/*event_field*/ nullptr,
@@ -503,10 +505,10 @@
Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
ASSERT_TRUE(klass != nullptr);
- ArtMethod* method = klass->FindDeclaredDirectMethod("returnPrimitive",
- "()I",
- kRuntimePointerSize);
+ ArtMethod* method = klass->FindClassMethod("returnPrimitive", "()I", kRuntimePointerSize);
ASSERT_TRUE(method != nullptr);
+ ASSERT_TRUE(method->IsDirect());
+ ASSERT_TRUE(method->GetDeclaringClass() == klass);
TestEvent(instrumentation::Instrumentation::kMethodExited,
/*event_method*/ method,
/*event_field*/ nullptr,
@@ -583,9 +585,11 @@
Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
ASSERT_TRUE(klass != nullptr);
- ArtMethod* method_to_deoptimize = klass->FindDeclaredDirectMethod("instanceMethod", "()V",
- kRuntimePointerSize);
+ ArtMethod* method_to_deoptimize =
+ klass->FindClassMethod("instanceMethod", "()V", kRuntimePointerSize);
ASSERT_TRUE(method_to_deoptimize != nullptr);
+ ASSERT_TRUE(method_to_deoptimize->IsDirect());
+ ASSERT_TRUE(method_to_deoptimize->GetDeclaringClass() == klass);
EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize));
@@ -630,9 +634,11 @@
Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
ASSERT_TRUE(klass != nullptr);
- ArtMethod* method_to_deoptimize = klass->FindDeclaredDirectMethod("instanceMethod", "()V",
- kRuntimePointerSize);
+ ArtMethod* method_to_deoptimize =
+ klass->FindClassMethod("instanceMethod", "()V", kRuntimePointerSize);
ASSERT_TRUE(method_to_deoptimize != nullptr);
+ ASSERT_TRUE(method_to_deoptimize->IsDirect());
+ ASSERT_TRUE(method_to_deoptimize->GetDeclaringClass() == klass);
EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize));
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 0687b75..be2d34d 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -593,10 +593,8 @@
}
ArtMethod* invoke_method =
- class_linker->ResolveMethod<ClassLinker::kForceICCECheck>(self,
- invoke_method_idx,
- shadow_frame.GetMethod(),
- kVirtual);
+ class_linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
+ self, invoke_method_idx, shadow_frame.GetMethod(), kVirtual);
// There is a common dispatch method for method handles that takes
// arguments either from a range or an array of arguments depending
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 11eb4e0..2c72821 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -265,7 +265,7 @@
bool ok = false;
auto* cl = Runtime::Current()->GetClassLinker();
if (cl->EnsureInitialized(self, h_klass, true, true)) {
- auto* cons = h_klass->FindDeclaredDirectMethod("<init>", "()V", cl->GetImagePointerSize());
+ auto* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize());
if (cons != nullptr) {
Handle<mirror::Object> h_obj(hs.NewHandle(klass->AllocObject(self)));
CHECK(h_obj != nullptr); // We don't expect OOM at compile-time.
@@ -591,8 +591,7 @@
}
auto* cl = Runtime::Current()->GetClassLinker();
- ArtMethod* constructor = h_class->FindDeclaredDirectMethod(
- "<init>", "([B)V", cl->GetImagePointerSize());
+ ArtMethod* constructor = h_class->FindConstructor("([B)V", cl->GetImagePointerSize());
if (constructor == nullptr) {
AbortTransactionOrFail(self, "Could not find ByteArrayInputStream constructor");
return;
@@ -1010,8 +1009,7 @@
Handle<mirror::Class> h_class(hs.NewHandle(klass));
Handle<mirror::Object> h_obj(hs.NewHandle(h_class->AllocObject(self)));
if (h_obj != nullptr) {
- ArtMethod* init_method = h_class->FindDirectMethod(
- "<init>", "()V", class_linker->GetImagePointerSize());
+ ArtMethod* init_method = h_class->FindConstructor("()V", class_linker->GetImagePointerSize());
if (init_method == nullptr) {
AbortTransactionOrFail(self, "Could not find <init> for %s", class_descriptor);
return nullptr;
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index c2ef724..87fa8be 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -387,8 +387,9 @@
ScopedObjectAccess soa(self);
mirror::Class* klass = mirror::String::GetJavaLangString();
ArtMethod* method =
- klass->FindDeclaredDirectMethod("<init>", "(Ljava/lang/String;)V",
- Runtime::Current()->GetClassLinker()->GetImagePointerSize());
+ klass->FindConstructor("(Ljava/lang/String;)V",
+ Runtime::Current()->GetClassLinker()->GetImagePointerSize());
+ ASSERT_TRUE(method != nullptr);
// create instruction data for invoke-direct {v0, v1} of method with fake index
uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 };
@@ -779,44 +780,40 @@
{
JValue result;
tmp->SetVReg(0, static_cast<int32_t>(i));
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
UnstartedCharacterToLowerCase(self, tmp, &result, 0);
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(self->IsExceptionPending());
- ASSERT_TRUE(transaction.IsAborted());
}
{
JValue result;
tmp->SetVReg(0, static_cast<int32_t>(i));
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
UnstartedCharacterToUpperCase(self, tmp, &result, 0);
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(self->IsExceptionPending());
- ASSERT_TRUE(transaction.IsAborted());
}
}
for (uint64_t i = 256; i <= std::numeric_limits<uint32_t>::max(); i <<= 1) {
{
JValue result;
tmp->SetVReg(0, static_cast<int32_t>(i));
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
UnstartedCharacterToLowerCase(self, tmp, &result, 0);
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(self->IsExceptionPending());
- ASSERT_TRUE(transaction.IsAborted());
}
{
JValue result;
tmp->SetVReg(0, static_cast<int32_t>(i));
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
UnstartedCharacterToUpperCase(self, tmp, &result, 0);
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(self->IsExceptionPending());
- ASSERT_TRUE(transaction.IsAborted());
}
}
@@ -966,12 +963,14 @@
ASSERT_TRUE(floating_decimal != nullptr);
ASSERT_TRUE(class_linker->EnsureInitialized(self, floating_decimal, true, true));
- ArtMethod* caller_method = floating_decimal->FindDeclaredDirectMethod(
+ ArtMethod* caller_method = floating_decimal->FindClassMethod(
"getBinaryToASCIIBuffer",
"()Lsun/misc/FloatingDecimal$BinaryToASCIIBuffer;",
class_linker->GetImagePointerSize());
// floating_decimal->DumpClass(LOG_STREAM(ERROR), mirror::Class::kDumpClassFullDetail);
ASSERT_TRUE(caller_method != nullptr);
+ ASSERT_TRUE(caller_method->IsDirect());
+ ASSERT_TRUE(caller_method->GetDeclaringClass() == floating_decimal.Get());
ShadowFrame* caller_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, caller_method, 0);
shadow_frame->SetLink(caller_frame);
@@ -993,12 +992,11 @@
ShadowFrame* caller_frame = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, caller_method, 0);
shadow_frame->SetLink(caller_frame);
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
UnstartedThreadLocalGet(self, shadow_frame, &result, 0);
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(self->IsExceptionPending());
- ASSERT_TRUE(transaction.IsAborted());
self->ClearException();
ShadowFrame::DeleteDeoptimizedFrame(caller_frame);
@@ -1020,10 +1018,12 @@
ASSERT_TRUE(double_class != nullptr);
ASSERT_TRUE(class_linker->EnsureInitialized(self, double_class, true, true));
- ArtMethod* method = double_class->FindDeclaredDirectMethod("toString",
- "(D)Ljava/lang/String;",
- class_linker->GetImagePointerSize());
+ ArtMethod* method = double_class->FindClassMethod("toString",
+ "(D)Ljava/lang/String;",
+ class_linker->GetImagePointerSize());
ASSERT_TRUE(method != nullptr);
+ ASSERT_TRUE(method->IsDirect());
+ ASSERT_TRUE(method->GetDeclaringClass() == double_class.Get());
// create instruction data for invoke-direct {v0, v1} of method with fake index
uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 };
@@ -1061,12 +1061,11 @@
PrepareForAborts();
{
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
UnstartedThreadCurrentThread(self, shadow_frame, &result, 0);
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(self->IsExceptionPending());
- ASSERT_TRUE(transaction.IsAborted());
self->ClearException();
}
@@ -1133,28 +1132,27 @@
mirror::String* name_string = mirror::String::AllocFromModifiedUtf8(self, name);
CHECK(name_string != nullptr);
- Transaction transaction;
if (in_transaction) {
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
}
CHECK(!self->IsExceptionPending());
runner(self, shadow_frame, name_string, &result);
- if (in_transaction) {
- Runtime::Current()->ExitTransactionMode();
- }
-
if (should_succeed) {
CHECK(!self->IsExceptionPending()) << name << " " << self->GetException()->Dump();
CHECK(result.GetL() != nullptr) << name;
} else {
CHECK(self->IsExceptionPending()) << name;
if (in_transaction) {
- ASSERT_TRUE(transaction.IsAborted());
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
}
self->ClearException();
}
+
+ if (in_transaction) {
+ Runtime::Current()->ExitTransactionMode();
+ }
}
ShadowFrame::DeleteDeoptimizedFrame(shadow_frame);
@@ -1179,8 +1177,8 @@
boot_cp.Assign(boot_cp_class->AllocObject(self)->AsClassLoader());
CHECK(boot_cp != nullptr);
- ArtMethod* boot_cp_init = boot_cp_class->FindDeclaredDirectMethod(
- "<init>", "()V", class_linker->GetImagePointerSize());
+ ArtMethod* boot_cp_init = boot_cp_class->FindConstructor(
+ "()V", class_linker->GetImagePointerSize());
CHECK(boot_cp_init != nullptr);
JValue result;
@@ -1333,8 +1331,8 @@
Handle<mirror::String> input = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "abd"));
// Find the constructor.
- ArtMethod* throw_cons = throw_class->FindDeclaredDirectMethod(
- "<init>", "(Ljava/lang/String;)V", class_linker->GetImagePointerSize());
+ ArtMethod* throw_cons = throw_class->FindConstructor(
+ "(Ljava/lang/String;)V", class_linker->GetImagePointerSize());
ASSERT_TRUE(throw_cons != nullptr);
Handle<mirror::Constructor> cons;
if (class_linker->GetImagePointerSize() == PointerSize::k64) {
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 969a570..7abf52e 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -20,6 +20,7 @@
#include "art_method-inl.h"
#include "base/enums.h"
+#include "base/logging.h"
#include "base/memory_tool.h"
#include "debugger.h"
#include "entrypoints/runtime_asm_entrypoints.h"
@@ -45,6 +46,11 @@
// At what priority to schedule jit threads. 9 is the lowest foreground priority on device.
static constexpr int kJitPoolThreadPthreadPriority = 9;
+// Different compilation threshold constants. These can be overridden on the command line.
+static constexpr size_t kJitDefaultCompileThreshold = 10000; // Non-debug default.
+static constexpr size_t kJitStressDefaultCompileThreshold = 100; // Fast-debug build.
+static constexpr size_t kJitSlowStressDefaultCompileThreshold = 2; // Slow-debug build.
+
// JIT compiler
void* Jit::jit_library_handle_= nullptr;
void* Jit::jit_compiler_handle_ = nullptr;
@@ -54,6 +60,11 @@
void (*Jit::jit_types_loaded_)(void*, mirror::Class**, size_t count) = nullptr;
bool Jit::generate_debug_info_ = false;
+struct StressModeHelper {
+ DECLARE_RUNTIME_DEBUG_FLAG(kSlowMode);
+};
+DEFINE_RUNTIME_DEBUG_FLAG(StressModeHelper, kSlowMode);
+
JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& options) {
auto* jit_options = new JitOptions;
jit_options->use_jit_compilation_ = options.GetOrDefault(RuntimeArgumentMap::UseJitCompilation);
@@ -67,7 +78,16 @@
jit_options->profile_saver_options_ =
options.GetOrDefault(RuntimeArgumentMap::ProfileSaverOpts);
- jit_options->compile_threshold_ = options.GetOrDefault(RuntimeArgumentMap::JITCompileThreshold);
+ if (options.Exists(RuntimeArgumentMap::JITCompileThreshold)) {
+ jit_options->compile_threshold_ = *options.Get(RuntimeArgumentMap::JITCompileThreshold);
+ } else {
+ jit_options->compile_threshold_ =
+ kIsDebugBuild
+ ? (StressModeHelper::kSlowMode
+ ? kJitSlowStressDefaultCompileThreshold
+ : kJitStressDefaultCompileThreshold)
+ : kJitDefaultCompileThreshold;
+ }
if (jit_options->compile_threshold_ > std::numeric_limits<uint16_t>::max()) {
LOG(FATAL) << "Method compilation threshold is above its internal limit.";
}
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index f898d41..51e49ec 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -48,8 +48,6 @@
class Jit {
public:
- static constexpr bool kStressMode = kIsDebugBuild;
- static constexpr size_t kDefaultCompileThreshold = kStressMode ? 2 : 10000;
static constexpr size_t kDefaultPriorityThreadWeightRatio = 1000;
static constexpr size_t kDefaultInvokeTransitionWeightRatio = 500;
// How frequently should the interpreter check to see if OSR compilation is ready.
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 7082083..3bee560 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -744,6 +744,115 @@
method->SetCounter(std::min(jit_warmup_threshold - 1, 1));
}
+#ifdef __aarch64__
+
+static void FlushJitCodeCacheRange(uint8_t* code_ptr,
+ uint8_t* writable_ptr,
+ size_t code_size) {
+ // Cache maintenance instructions can cause permission faults when a
+ // page is not present (e.g. swapped out or not backed). These
+ // faults should be handled by the kernel, but a bug in some Linux
+ // kernels may surface these permission faults to user-land which
+ // does not currently deal with them (b/63885946). To work around
+ // this, we read a value from each page to fault it in before
+ // attempting to perform cache maintenance operations.
+ //
+ // For reference, this behavior is caused by this commit:
+ // https://android.googlesource.com/kernel/msm/+/3fbe6bc28a6b9939d0650f2f17eb5216c719950c
+
+ // The cache-line size could be probed for from the CPU, but
+ // assuming a safe lower bound is safe for CPUs that have different
+ // cache-line sizes for big and little cores.
+ static const uintptr_t kSafeCacheLineSize = 32;
+
+ // Ensure stores are present in L1 data cache.
+ __asm __volatile("dsb ish" ::: "memory");
+
+ volatile uint8_t mutant;
+
+ // Push dirty cache-lines out to the point of unification (PoU). The
+ // point of unification is the first point in the cache/memory
+ // hierarchy where the instruction cache and data cache have the
+ // same view of memory. The PoU is where an instruction fetch will
+ // fetch the new code generated by the JIT.
+ //
+ // See: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch11s04.html
+ uintptr_t writable_addr = RoundDown(reinterpret_cast<uintptr_t>(writable_ptr),
+ kSafeCacheLineSize);
+ uintptr_t writable_end = RoundUp(reinterpret_cast<uintptr_t>(writable_ptr) + code_size,
+ kSafeCacheLineSize);
+ while (writable_addr < writable_end) {
+ // Read from the cache-line to minimize the chance that a cache
+ // maintenance instruction causes a fault (see kernel bug comment
+ // above).
+ mutant = *reinterpret_cast<const uint8_t*>(writable_addr);
+
+ // Flush cache-line
+ __asm volatile("dc cvau, %0" :: "r"(writable_addr) : "memory");
+ writable_addr += kSafeCacheLineSize;
+ }
+
+ __asm __volatile("dsb ish" ::: "memory");
+
+ uintptr_t code_addr = RoundDown(reinterpret_cast<uintptr_t>(code_ptr), kSafeCacheLineSize);
+ const uintptr_t code_end = RoundUp(reinterpret_cast<uintptr_t>(code_ptr) + code_size,
+ kSafeCacheLineSize);
+ while (code_addr < code_end) {
+ // Read from the cache-line to minimize the chance that a cache
+ // maintenance instruction causes a fault (see kernel bug comment
+ // above).
+ mutant = *reinterpret_cast<const uint8_t*>(code_addr);
+
+ // Invalidating the data cache line is only strictly necessary
+ // when the JIT code cache has two mappings (the default). We know
+ // this cache line is clean so this is just invalidating it (using
+ // "dc ivac" would be preferable, but counts as a write and this
+ // memory may not be mapped write permission).
+ __asm volatile("dc cvau, %0" :: "r"(code_addr) : "memory");
+
+ // Invalidate the instruction cache line to force instructions in
+ // range to be re-fetched following update.
+ __asm volatile("ic ivau, %0" :: "r"(code_addr) : "memory");
+
+ code_addr += kSafeCacheLineSize;
+ }
+
+ // Wait for code cache invalidations to complete.
+ __asm __volatile("dsb ish" ::: "memory");
+
+ // Reset fetched instruction stream.
+ __asm __volatile("isb");
+}
+
+#else // __aarch64
+
+static void FlushJitCodeCacheRange(uint8_t* code_ptr,
+ uint8_t* writable_ptr,
+ size_t code_size) {
+ if (writable_ptr != code_ptr) {
+ // When there are two mappings of the JIT code cache, RX and
+ // RW, flush the RW version first as we've just dirtied the
+ // cache lines with new code. Flushing the RX version first
+ // can cause a permission fault as the those addresses are not
+ // writable, but can appear dirty in the cache. There is a lot
+ // of potential subtlety here depending on how the cache is
+ // indexed and tagged.
+ //
+ // Flushing the RX version after the RW version is just
+ // invalidating cachelines in the instruction cache. This is
+ // necessary as the instruction cache will often have a
+ // different set of cache lines present and because the JIT
+ // code cache can start a new function at any boundary within
+ // a cache-line.
+ FlushDataCache(reinterpret_cast<char*>(writable_ptr),
+ reinterpret_cast<char*>(writable_ptr + code_size));
+ }
+ FlushInstructionCache(reinterpret_cast<char*>(code_ptr),
+ reinterpret_cast<char*>(code_ptr + code_size));
+}
+
+#endif // __aarch64
+
uint8_t* JitCodeCache::CommitCodeInternal(Thread* self,
ArtMethod* method,
uint8_t* stack_map,
@@ -795,33 +904,8 @@
core_spill_mask,
fp_spill_mask,
code_size);
- // Flush caches before we remove write permission because some ARMv8 Qualcomm kernels may
- // trigger a segfault if a page fault occurs when requesting a cache maintenance operation.
- // This is a kernel bug that we need to work around until affected devices (e.g. Nexus 5X and
- // 6P) stop being supported or their kernels are fixed.
- //
- // For reference, this behavior is caused by this commit:
- // https://android.googlesource.com/kernel/msm/+/3fbe6bc28a6b9939d0650f2f17eb5216c719950c
- if (writable_ptr != code_ptr) {
- // When there are two mappings of the JIT code cache, RX and
- // RW, flush the RW version first as we've just dirtied the
- // cache lines with new code. Flushing the RX version first
- // can cause a permission fault as the those addresses are not
- // writable, but can appear dirty in the cache. There is a lot
- // of potential subtlety here depending on how the cache is
- // indexed and tagged.
- //
- // Flushing the RX version after the RW version is just
- // invalidating cachelines in the instruction cache. This is
- // necessary as the instruction cache will often have a
- // different set of cache lines present and because the JIT
- // code cache can start a new function at any boundary within
- // a cache-line.
- FlushDataCache(reinterpret_cast<char*>(writable_ptr),
- reinterpret_cast<char*>(writable_ptr + code_size));
- }
- FlushInstructionCache(reinterpret_cast<char*>(code_ptr),
- reinterpret_cast<char*>(code_ptr + code_size));
+
+ FlushJitCodeCacheRange(code_ptr, writable_ptr, code_size);
DCHECK(!Runtime::Current()->IsAotCompiler());
if (has_should_deoptimize_flag) {
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 45c3792..0b7063d 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -1652,4 +1652,27 @@
return &(inline_cache->FindOrAdd(dex_pc, DexPcData(&arena_))->second);
}
+std::unordered_set<std::string> ProfileCompilationInfo::GetClassDescriptors(
+ const std::vector<const DexFile*>& dex_files) {
+ std::unordered_set<std::string> ret;
+ for (const DexFile* dex_file : dex_files) {
+ const DexFileData* data = FindDexData(dex_file);
+ if (data != nullptr) {
+ for (dex::TypeIndex type_idx : data->class_set) {
+ if (!dex_file->IsTypeIndexValid(type_idx)) {
+ // Something went bad. The profile is probably corrupted. Abort and return an emtpy set.
+ LOG(WARNING) << "Corrupted profile: invalid type index "
+ << type_idx.index_ << " in dex " << dex_file->GetLocation();
+ return std::unordered_set<std::string>();
+ }
+ const DexFile::TypeId& type_id = dex_file->GetTypeId(type_idx);
+ ret.insert(dex_file->GetTypeDescriptor(type_id));
+ }
+ } else {
+ VLOG(compiler) << "Failed to find profile data for " << dex_file->GetLocation();
+ }
+ }
+ return ret;
+}
+
} // namespace art
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index 079ce8d..4ab8be8 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -380,6 +380,9 @@
ArenaAllocator* GetArena() { return &arena_; }
+ // Return all of the class descriptors in the profile for a set of dex files.
+ std::unordered_set<std::string> GetClassDescriptors(const std::vector<const DexFile*>& dex_files);
+
private:
enum ProfileLoadSatus {
kProfileLoadWouldOverwiteData,
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 40b40d7..927f94b 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -233,17 +233,10 @@
}
ArtMethod* method = nullptr;
auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- if (is_static) {
- method = c->FindDirectMethod(name, sig, pointer_size);
- } else if (c->IsInterface()) {
+ if (c->IsInterface()) {
method = c->FindInterfaceMethod(name, sig, pointer_size);
} else {
- method = c->FindVirtualMethod(name, sig, pointer_size);
- if (method == nullptr) {
- // No virtual method matching the signature. Search declared
- // private methods and constructors.
- method = c->FindDeclaredDirectMethod(name, sig, pointer_size);
- }
+ method = c->FindClassMethod(name, sig, pointer_size);
}
if (method == nullptr || method->IsStatic() != is_static) {
ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");
diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc
index 728b7c7..3f00450 100644
--- a/runtime/jni_internal_test.cc
+++ b/runtime/jni_internal_test.cc
@@ -626,9 +626,9 @@
hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader_)));
mirror::Class* c = class_linker_->FindClass(soa.Self(), "LMyClassNatives;", loader);
const auto pointer_size = class_linker_->GetImagePointerSize();
- ArtMethod* method = direct ? c->FindDirectMethod(method_name, method_sig, pointer_size) :
- c->FindVirtualMethod(method_name, method_sig, pointer_size);
+ ArtMethod* method = c->FindClassMethod(method_name, method_sig, pointer_size);
ASSERT_TRUE(method != nullptr) << method_name << " " << method_sig;
+ ASSERT_EQ(direct, method->IsDirect());
method->SetEntryPointFromQuickCompiledCode(class_linker_->GetRuntimeQuickGenericJniStub());
}
// Start runtime.
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index 003cd4e..121c259 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -533,7 +533,11 @@
PointerSize pointer_size) {
ObjPtr<Class> declaring_class = method->GetDeclaringClass();
DCHECK(declaring_class != nullptr) << PrettyClass();
- DCHECK(declaring_class->IsInterface()) << method->PrettyMethod();
+ if (UNLIKELY(!declaring_class->IsInterface())) {
+ DCHECK(declaring_class->IsObjectClass()) << method->PrettyMethod();
+ DCHECK(method->IsPublic() && !method->IsStatic());
+ return FindVirtualMethodForVirtual(method, pointer_size);
+ }
DCHECK(!method->IsCopied());
// TODO cache to improve lookup speed
const int32_t iftable_count = GetIfTableCount();
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index b0e5b6a..6f70b19 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -396,95 +396,44 @@
}
}
-ArtMethod* Class::FindInterfaceMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size) {
- // Check the current class before checking the interfaces.
- ArtMethod* method = FindDeclaredVirtualMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
- }
-
- int32_t iftable_count = GetIfTableCount();
- ObjPtr<IfTable> iftable = GetIfTable();
- for (int32_t i = 0; i < iftable_count; ++i) {
- method = iftable->GetInterface(i)->FindDeclaredVirtualMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
+template <typename SignatureType>
+static inline ArtMethod* FindInterfaceMethodWithSignature(ObjPtr<Class> klass,
+ const StringPiece& name,
+ const SignatureType& signature,
+ PointerSize pointer_size)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // If the current class is not an interface, skip the search of its declared methods;
+ // such lookup is used only to distinguish between IncompatibleClassChangeError and
+ // NoSuchMethodError and the caller has already tried to search methods in the class.
+ if (LIKELY(klass->IsInterface())) {
+ // Search declared methods, both direct and virtual.
+ // (This lookup is used also for invoke-static on interface classes.)
+ for (ArtMethod& method : klass->GetDeclaredMethodsSlice(pointer_size)) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ return &method;
+ }
}
}
- return nullptr;
-}
-ArtMethod* Class::FindInterfaceMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size) {
- // Check the current class before checking the interfaces.
- ArtMethod* method = FindDeclaredVirtualMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
- }
-
- int32_t iftable_count = GetIfTableCount();
- ObjPtr<IfTable> iftable = GetIfTable();
- for (int32_t i = 0; i < iftable_count; ++i) {
- method = iftable->GetInterface(i)->FindDeclaredVirtualMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
+ // TODO: If there is a unique maximally-specific non-abstract superinterface method,
+ // we should return it, otherwise an arbitrary one can be returned.
+ ObjPtr<IfTable> iftable = klass->GetIfTable();
+ for (int32_t i = 0, iftable_count = iftable->Count(); i < iftable_count; ++i) {
+ ObjPtr<Class> iface = iftable->GetInterface(i);
+ for (ArtMethod& method : iface->GetVirtualMethodsSlice(pointer_size)) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ return &method;
+ }
}
}
- return nullptr;
-}
-ArtMethod* Class::FindInterfaceMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size) {
- // Check the current class before checking the interfaces.
- ArtMethod* method = FindDeclaredVirtualMethod(dex_cache, dex_method_idx, pointer_size);
- if (method != nullptr) {
- return method;
- }
-
- int32_t iftable_count = GetIfTableCount();
- ObjPtr<IfTable> iftable = GetIfTable();
- for (int32_t i = 0; i < iftable_count; ++i) {
- method = iftable->GetInterface(i)->FindDeclaredVirtualMethod(
- dex_cache, dex_method_idx, pointer_size);
- if (method != nullptr) {
- return method;
- }
- }
- return nullptr;
-}
-
-ArtMethod* Class::FindDeclaredDirectMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size) {
- for (auto& method : GetDirectMethods(pointer_size)) {
- if (name == method.GetName() && method.GetSignature() == signature) {
- return &method;
- }
- }
- return nullptr;
-}
-
-ArtMethod* Class::FindDeclaredDirectMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size) {
- for (auto& method : GetDirectMethods(pointer_size)) {
- if (name == method.GetName() && signature == method.GetSignature()) {
- return &method;
- }
- }
- return nullptr;
-}
-
-ArtMethod* Class::FindDeclaredDirectMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size) {
- if (GetDexCache() == dex_cache) {
- for (auto& method : GetDirectMethods(pointer_size)) {
- if (method.GetDexMethodIndex() == dex_method_idx) {
+ // Then search for public non-static methods in the java.lang.Object.
+ if (LIKELY(klass->IsInterface())) {
+ ObjPtr<Class> object_class = klass->GetSuperClass();
+ DCHECK(object_class->IsObjectClass());
+ for (ArtMethod& method : object_class->GetDeclaredMethodsSlice(pointer_size)) {
+ if (method.IsPublic() && !method.IsStatic() &&
+ method.GetName() == name && method.GetSignature() == signature) {
return &method;
}
}
@@ -492,37 +441,220 @@
return nullptr;
}
-ArtMethod* Class::FindDirectMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size) {
- for (ObjPtr<Class> klass = this; klass != nullptr; klass = klass->GetSuperClass()) {
- ArtMethod* method = klass->FindDeclaredDirectMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
- }
- }
- return nullptr;
+ArtMethod* Class::FindInterfaceMethod(const StringPiece& name,
+ const StringPiece& signature,
+ PointerSize pointer_size) {
+ return FindInterfaceMethodWithSignature(this, name, signature, pointer_size);
}
-ArtMethod* Class::FindDirectMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size) {
- for (ObjPtr<Class> klass = this; klass != nullptr; klass = klass->GetSuperClass()) {
- ArtMethod* method = klass->FindDeclaredDirectMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
- }
- }
- return nullptr;
+ArtMethod* Class::FindInterfaceMethod(const StringPiece& name,
+ const Signature& signature,
+ PointerSize pointer_size) {
+ return FindInterfaceMethodWithSignature(this, name, signature, pointer_size);
}
-ArtMethod* Class::FindDirectMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size) {
- for (ObjPtr<Class> klass = this; klass != nullptr; klass = klass->GetSuperClass()) {
- ArtMethod* method = klass->FindDeclaredDirectMethod(dex_cache, dex_method_idx, pointer_size);
- if (method != nullptr) {
- return method;
+ArtMethod* Class::FindInterfaceMethod(ObjPtr<DexCache> dex_cache,
+ uint32_t dex_method_idx,
+ PointerSize pointer_size) {
+ // We always search by name and signature, ignoring the type index in the MethodId.
+ const DexFile& dex_file = *dex_cache->GetDexFile();
+ const DexFile::MethodId& method_id = dex_file.GetMethodId(dex_method_idx);
+ StringPiece name = dex_file.StringDataByIdx(method_id.name_idx_);
+ const Signature signature = dex_file.GetMethodSignature(method_id);
+ return FindInterfaceMethod(name, signature, pointer_size);
+}
+
+static inline bool IsInheritedMethod(ObjPtr<mirror::Class> klass,
+ ObjPtr<mirror::Class> declaring_class,
+ ArtMethod& method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK_EQ(declaring_class, method.GetDeclaringClass());
+ DCHECK_NE(klass, declaring_class);
+ DCHECK(klass->IsArrayClass() ? declaring_class->IsObjectClass()
+ : klass->IsSubClass(declaring_class));
+ uint32_t access_flags = method.GetAccessFlags();
+ if ((access_flags & (kAccPublic | kAccProtected)) != 0) {
+ return true;
+ }
+ if ((access_flags & kAccPrivate) != 0) {
+ return false;
+ }
+ for (; klass != declaring_class; klass = klass->GetSuperClass()) {
+ if (!klass->IsInSamePackage(declaring_class)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename SignatureType>
+static inline ArtMethod* FindClassMethodWithSignature(ObjPtr<Class> this_klass,
+ const StringPiece& name,
+ const SignatureType& signature,
+ PointerSize pointer_size)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Search declared methods first.
+ for (ArtMethod& method : this_klass->GetDeclaredMethodsSlice(pointer_size)) {
+ ArtMethod* np_method = method.GetInterfaceMethodIfProxy(pointer_size);
+ if (np_method->GetName() == name && np_method->GetSignature() == signature) {
+ return &method;
+ }
+ }
+
+ // Then search the superclass chain. If we find an inherited method, return it.
+ // If we find a method that's not inherited because of access restrictions,
+ // try to find a method inherited from an interface in copied methods.
+ ObjPtr<Class> klass = this_klass->GetSuperClass();
+ ArtMethod* uninherited_method = nullptr;
+ for (; klass != nullptr; klass = klass->GetSuperClass()) {
+ DCHECK(!klass->IsProxyClass());
+ for (ArtMethod& method : klass->GetDeclaredMethodsSlice(pointer_size)) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ if (IsInheritedMethod(this_klass, klass, method)) {
+ return &method;
+ }
+ uninherited_method = &method;
+ break;
+ }
+ }
+ if (uninherited_method != nullptr) {
+ break;
+ }
+ }
+
+ // Then search copied methods.
+ // If we found a method that's not inherited, stop the search in its declaring class.
+ ObjPtr<Class> end_klass = klass;
+ DCHECK_EQ(uninherited_method != nullptr, end_klass != nullptr);
+ klass = this_klass;
+ if (UNLIKELY(klass->IsProxyClass())) {
+ DCHECK(klass->GetCopiedMethodsSlice(pointer_size).empty());
+ klass = klass->GetSuperClass();
+ }
+ for (; klass != end_klass; klass = klass->GetSuperClass()) {
+ DCHECK(!klass->IsProxyClass());
+ for (ArtMethod& method : klass->GetCopiedMethodsSlice(pointer_size)) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ return &method; // No further check needed, copied methods are inherited by definition.
+ }
+ }
+ }
+ return uninherited_method; // Return the `uninherited_method` if any.
+}
+
+
+ArtMethod* Class::FindClassMethod(const StringPiece& name,
+ const StringPiece& signature,
+ PointerSize pointer_size) {
+ return FindClassMethodWithSignature(this, name, signature, pointer_size);
+}
+
+ArtMethod* Class::FindClassMethod(const StringPiece& name,
+ const Signature& signature,
+ PointerSize pointer_size) {
+ return FindClassMethodWithSignature(this, name, signature, pointer_size);
+}
+
+ArtMethod* Class::FindClassMethod(ObjPtr<DexCache> dex_cache,
+ uint32_t dex_method_idx,
+ PointerSize pointer_size) {
+ // FIXME: Hijacking a proxy class by a custom class loader can break this assumption.
+ DCHECK(!IsProxyClass());
+
+ // First try to find a declared method by dex_method_idx if we have a dex_cache match.
+ ObjPtr<DexCache> this_dex_cache = GetDexCache();
+ if (this_dex_cache == dex_cache) {
+ // Lookup is always performed in the class referenced by the MethodId.
+ DCHECK_EQ(dex_type_idx_, GetDexFile().GetMethodId(dex_method_idx).class_idx_.index_);
+ for (ArtMethod& method : GetDeclaredMethodsSlice(pointer_size)) {
+ if (method.GetDexMethodIndex() == dex_method_idx) {
+ return &method;
+ }
+ }
+ }
+ // If not found, we need to search by name and signature.
+ const DexFile& dex_file = *dex_cache->GetDexFile();
+ const DexFile::MethodId& method_id = dex_file.GetMethodId(dex_method_idx);
+ const Signature signature = dex_file.GetMethodSignature(method_id);
+ StringPiece name; // Delay strlen() until actually needed.
+ // If we do not have a dex_cache match, try to find the declared method in this class now.
+ if (this_dex_cache != dex_cache && !GetDeclaredMethodsSlice(pointer_size).empty()) {
+ DCHECK(name.empty());
+ name = dex_file.StringDataByIdx(method_id.name_idx_);
+ for (ArtMethod& method : GetDeclaredMethodsSlice(pointer_size)) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ return &method;
+ }
+ }
+ }
+
+ // Then search the superclass chain. If we find an inherited method, return it.
+ // If we find a method that's not inherited because of access restrictions,
+ // try to find a method inherited from an interface in copied methods.
+ ArtMethod* uninherited_method = nullptr;
+ ObjPtr<Class> klass = GetSuperClass();
+ for (; klass != nullptr; klass = klass->GetSuperClass()) {
+ ArtMethod* candidate_method = nullptr;
+ ArraySlice<ArtMethod> declared_methods = klass->GetDeclaredMethodsSlice(pointer_size);
+ if (klass->GetDexCache() == dex_cache) {
+ // Matching dex_cache. We cannot compare the `dex_method_idx` anymore because
+ // the type index differs, so compare the name index and proto index.
+ for (ArtMethod& method : declared_methods) {
+ const DexFile::MethodId& cmp_method_id = dex_file.GetMethodId(method.GetDexMethodIndex());
+ if (cmp_method_id.name_idx_ == method_id.name_idx_ &&
+ cmp_method_id.proto_idx_ == method_id.proto_idx_) {
+ candidate_method = &method;
+ break;
+ }
+ }
+ } else {
+ if (!declared_methods.empty() && name.empty()) {
+ name = dex_file.StringDataByIdx(method_id.name_idx_);
+ }
+ for (ArtMethod& method : declared_methods) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ candidate_method = &method;
+ break;
+ }
+ }
+ }
+ if (candidate_method != nullptr) {
+ if (IsInheritedMethod(this, klass, *candidate_method)) {
+ return candidate_method;
+ } else {
+ uninherited_method = candidate_method;
+ break;
+ }
+ }
+ }
+
+ // Then search copied methods.
+ // If we found a method that's not inherited, stop the search in its declaring class.
+ ObjPtr<Class> end_klass = klass;
+ DCHECK_EQ(uninherited_method != nullptr, end_klass != nullptr);
+ // After we have searched the declared methods of the super-class chain,
+ // search copied methods which can contain methods from interfaces.
+ for (klass = this; klass != end_klass; klass = klass->GetSuperClass()) {
+ ArraySlice<ArtMethod> copied_methods = klass->GetCopiedMethodsSlice(pointer_size);
+ if (!copied_methods.empty() && name.empty()) {
+ name = dex_file.StringDataByIdx(method_id.name_idx_);
+ }
+ for (ArtMethod& method : copied_methods) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ return &method; // No further check needed, copied methods are inherited by definition.
+ }
+ }
+ }
+ return uninherited_method; // Return the `uninherited_method` if any.
+}
+
+ArtMethod* Class::FindConstructor(const StringPiece& signature, PointerSize pointer_size) {
+ // Internal helper, never called on proxy classes. We can skip GetInterfaceMethodIfProxy().
+ DCHECK(!IsProxyClass());
+ StringPiece name("<init>");
+ for (ArtMethod& method : GetDirectMethodsSliceUnchecked(pointer_size)) {
+ if (method.GetName() == name && method.GetSignature() == signature) {
+ return &method;
}
}
return nullptr;
@@ -539,47 +671,6 @@
return nullptr;
}
-// TODO These should maybe be changed to be named FindOwnedVirtualMethod or something similar
-// because they do not only find 'declared' methods and will return copied methods. This behavior is
-// desired and correct but the naming can lead to confusion because in the java language declared
-// excludes interface methods which might be found by this.
-ArtMethod* Class::FindDeclaredVirtualMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size) {
- for (auto& method : GetVirtualMethods(pointer_size)) {
- ArtMethod* const np_method = method.GetInterfaceMethodIfProxy(pointer_size);
- if (name == np_method->GetName() && np_method->GetSignature() == signature) {
- return &method;
- }
- }
- return nullptr;
-}
-
-ArtMethod* Class::FindDeclaredVirtualMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size) {
- for (auto& method : GetVirtualMethods(pointer_size)) {
- ArtMethod* const np_method = method.GetInterfaceMethodIfProxy(pointer_size);
- if (name == np_method->GetName() && signature == np_method->GetSignature()) {
- return &method;
- }
- }
- return nullptr;
-}
-
-ArtMethod* Class::FindDeclaredVirtualMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size) {
- if (GetDexCache() == dex_cache) {
- for (auto& method : GetDeclaredVirtualMethods(pointer_size)) {
- if (method.GetDexMethodIndex() == dex_method_idx) {
- return &method;
- }
- }
- }
- return nullptr;
-}
-
ArtMethod* Class::FindDeclaredVirtualMethodByName(const StringPiece& name,
PointerSize pointer_size) {
for (auto& method : GetVirtualMethods(pointer_size)) {
@@ -591,42 +682,6 @@
return nullptr;
}
-ArtMethod* Class::FindVirtualMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size) {
- for (ObjPtr<Class> klass = this; klass != nullptr; klass = klass->GetSuperClass()) {
- ArtMethod* method = klass->FindDeclaredVirtualMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
- }
- }
- return nullptr;
-}
-
-ArtMethod* Class::FindVirtualMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size) {
- for (ObjPtr<Class> klass = this; klass != nullptr; klass = klass->GetSuperClass()) {
- ArtMethod* method = klass->FindDeclaredVirtualMethod(name, signature, pointer_size);
- if (method != nullptr) {
- return method;
- }
- }
- return nullptr;
-}
-
-ArtMethod* Class::FindVirtualMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size) {
- for (ObjPtr<Class> klass = this; klass != nullptr; klass = klass->GetSuperClass()) {
- ArtMethod* method = klass->FindDeclaredVirtualMethod(dex_cache, dex_method_idx, pointer_size);
- if (method != nullptr) {
- return method;
- }
- }
- return nullptr;
-}
-
ArtMethod* Class::FindVirtualMethodForInterfaceSuper(ArtMethod* method, PointerSize pointer_size) {
DCHECK(method->GetDeclaringClass()->IsInterface());
DCHECK(IsInterface()) << "Should only be called on a interface class";
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index e516a06..c626897 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -915,6 +915,13 @@
ArtMethod* FindVirtualMethodForVirtualOrInterface(ArtMethod* method, PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Find a method with the given name and signature in an interface class.
+ //
+ // Search for the method declared in the class, then search for a method declared in any
+ // superinterface, then search the superclass java.lang.Object (implicitly declared methods
+ // in an interface without superinterfaces, see JLS 9.2, can be inherited, see JLS 9.4.1).
+ // TODO: Implement search for a unique maximally-specific non-abstract superinterface method.
+
ArtMethod* FindInterfaceMethod(const StringPiece& name,
const StringPiece& signature,
PointerSize pointer_size)
@@ -930,49 +937,46 @@
PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
- ArtMethod* FindDeclaredDirectMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size)
+ // Find a method with the given name and signature in a non-interface class.
+ //
+ // Search for the method in the class, following the JLS rules which conflict with the RI
+ // in some cases. The JLS says that inherited methods are searched (JLS 15.12.2.1) and
+ // these can come from a superclass or a superinterface (JLS 8.4.8). We perform the
+ // following search:
+ // 1. Search the methods declared directly in the class. If we find a method with the
+ // given name and signature, return that method.
+ // 2. Search the methods declared in superclasses until we find a method with the given
+ // signature or complete the search in java.lang.Object. If we find a method with the
+ // given name and signature, check if it's been inherited by the class where we're
+ // performing the lookup (qualifying type). If it's inherited, return it. Otherwise,
+ // just remember the method and its declaring class and proceed to step 3.
+ // 3. Search "copied" methods (containing methods inherited from interfaces) in the class
+ // and its superclass chain. If we found a method in step 2 (which was not inherited,
+ // otherwise we would not be performing step 3), end the search when we reach its
+ // declaring class, otherwise search the entire superclass chain. If we find a method
+ // with the given name and signature, return that method.
+ // 4. Return the method found in step 2 if any (not inherited), or null.
+ //
+ // It's the responsibility of the caller to throw exceptions if the returned method (or null)
+ // does not satisfy the request. Special consideration should be given to the case where this
+ // function returns a method that's not inherited (found in step 2, returned in step 4).
+
+ ArtMethod* FindClassMethod(const StringPiece& name,
+ const StringPiece& signature,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
- ArtMethod* FindDeclaredDirectMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size)
+ ArtMethod* FindClassMethod(const StringPiece& name,
+ const Signature& signature,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
- ArtMethod* FindDeclaredDirectMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size)
+ ArtMethod* FindClassMethod(ObjPtr<DexCache> dex_cache,
+ uint32_t dex_method_idx,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
- ArtMethod* FindDirectMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ArtMethod* FindDirectMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ArtMethod* FindDirectMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ArtMethod* FindDeclaredVirtualMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ArtMethod* FindDeclaredVirtualMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ArtMethod* FindDeclaredVirtualMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size)
+ ArtMethod* FindConstructor(const StringPiece& signature, PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
ArtMethod* FindDeclaredVirtualMethodByName(const StringPiece& name,
@@ -983,21 +987,6 @@
PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
- ArtMethod* FindVirtualMethod(const StringPiece& name,
- const StringPiece& signature,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ArtMethod* FindVirtualMethod(const StringPiece& name,
- const Signature& signature,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ArtMethod* FindVirtualMethod(ObjPtr<DexCache> dex_cache,
- uint32_t dex_method_idx,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
ArtMethod* FindClassInitializer(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_);
bool HasDefaultMethods() REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 18e22ef..fdb14f1 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -208,24 +208,38 @@
}
}
+inline uint32_t DexCache::MethodSlotIndex(uint32_t method_idx) {
+ DCHECK_LT(method_idx, GetDexFile()->NumMethodIds());
+ const uint32_t slot_idx = method_idx % kDexCacheMethodCacheSize;
+ DCHECK_LT(slot_idx, NumResolvedMethods());
+ return slot_idx;
+}
+
inline ArtMethod* DexCache::GetResolvedMethod(uint32_t method_idx, PointerSize ptr_size) {
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), ptr_size);
- DCHECK_LT(method_idx, NumResolvedMethods()); // NOTE: Unchecked, i.e. not throwing AIOOB.
- ArtMethod* method = GetElementPtrSize<ArtMethod*>(GetResolvedMethods(), method_idx, ptr_size);
- // Hide resolution trampoline methods from the caller
- if (method != nullptr && method->IsRuntimeMethod()) {
- DCHECK_EQ(method, Runtime::Current()->GetResolutionMethod());
- return nullptr;
- }
- return method;
+ auto pair = GetNativePairPtrSize(GetResolvedMethods(), MethodSlotIndex(method_idx), ptr_size);
+ return pair.GetObjectForIndex(method_idx);
}
inline void DexCache::SetResolvedMethod(uint32_t method_idx,
ArtMethod* method,
PointerSize ptr_size) {
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), ptr_size);
- DCHECK_LT(method_idx, NumResolvedMethods()); // NOTE: Unchecked, i.e. not throwing AIOOB.
- SetElementPtrSize(GetResolvedMethods(), method_idx, method, ptr_size);
+ DCHECK(method != nullptr);
+ MethodDexCachePair pair(method, method_idx);
+ SetNativePairPtrSize(GetResolvedMethods(), MethodSlotIndex(method_idx), pair, ptr_size);
+}
+
+inline void DexCache::ClearResolvedMethod(uint32_t method_idx, PointerSize ptr_size) {
+ DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), ptr_size);
+ uint32_t slot_idx = MethodSlotIndex(method_idx);
+ auto* resolved_methods = GetResolvedMethods();
+ // This is racy but should only be called from the single-threaded ImageWriter.
+ DCHECK(Runtime::Current()->IsAotCompiler());
+ if (GetNativePairPtrSize(resolved_methods, slot_idx, ptr_size).index == method_idx) {
+ MethodDexCachePair cleared(nullptr, MethodDexCachePair::InvalidIndexForSlot(slot_idx));
+ SetNativePairPtrSize(resolved_methods, slot_idx, cleared, ptr_size);
+ }
}
template <typename PtrType>
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index 96e3475..1a9f999 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -61,14 +61,14 @@
: reinterpret_cast<uint8_t*>(linear_alloc->Alloc(self, layout.Size()));
}
- mirror::StringDexCacheType* strings = (dex_file->NumStringIds() == 0u) ? nullptr :
- reinterpret_cast<mirror::StringDexCacheType*>(raw_arrays + layout.StringsOffset());
- mirror::TypeDexCacheType* types = (dex_file->NumTypeIds() == 0u) ? nullptr :
- reinterpret_cast<mirror::TypeDexCacheType*>(raw_arrays + layout.TypesOffset());
- ArtMethod** methods = (dex_file->NumMethodIds() == 0u) ? nullptr :
- reinterpret_cast<ArtMethod**>(raw_arrays + layout.MethodsOffset());
- mirror::FieldDexCacheType* fields = (dex_file->NumFieldIds() == 0u) ? nullptr :
- reinterpret_cast<mirror::FieldDexCacheType*>(raw_arrays + layout.FieldsOffset());
+ StringDexCacheType* strings = (dex_file->NumStringIds() == 0u) ? nullptr :
+ reinterpret_cast<StringDexCacheType*>(raw_arrays + layout.StringsOffset());
+ TypeDexCacheType* types = (dex_file->NumTypeIds() == 0u) ? nullptr :
+ reinterpret_cast<TypeDexCacheType*>(raw_arrays + layout.TypesOffset());
+ MethodDexCacheType* methods = (dex_file->NumMethodIds() == 0u) ? nullptr :
+ reinterpret_cast<MethodDexCacheType*>(raw_arrays + layout.MethodsOffset());
+ FieldDexCacheType* fields = (dex_file->NumFieldIds() == 0u) ? nullptr :
+ reinterpret_cast<FieldDexCacheType*>(raw_arrays + layout.FieldsOffset());
size_t num_strings = kDexCacheStringCacheSize;
if (dex_file->NumStringIds() < num_strings) {
@@ -82,6 +82,10 @@
if (dex_file->NumFieldIds() < num_fields) {
num_fields = dex_file->NumFieldIds();
}
+ size_t num_methods = kDexCacheMethodCacheSize;
+ if (dex_file->NumMethodIds() < num_methods) {
+ num_methods = dex_file->NumMethodIds();
+ }
// Note that we allocate the method type dex caches regardless of this flag,
// and we make sure here that they're not used by the runtime. This is in the
@@ -105,7 +109,7 @@
GcRoot<mirror::CallSite>* call_sites = (dex_file->NumCallSiteIds() == 0)
? nullptr
- : reinterpret_cast<GcRoot<mirror::CallSite>*>(raw_arrays + layout.CallSitesOffset());
+ : reinterpret_cast<GcRoot<CallSite>*>(raw_arrays + layout.CallSitesOffset());
DCHECK_ALIGNED(raw_arrays, alignof(StringDexCacheType)) <<
"Expected raw_arrays to align to StringDexCacheType.";
@@ -125,8 +129,9 @@
CHECK_EQ(types[i].load(std::memory_order_relaxed).index, 0u);
CHECK(types[i].load(std::memory_order_relaxed).object.IsNull());
}
- for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) {
- CHECK(GetElementPtrSize(methods, i, image_pointer_size) == nullptr);
+ for (size_t i = 0; i < num_methods; ++i) {
+ CHECK_EQ(GetNativePairPtrSize(methods, i, image_pointer_size).index, 0u);
+ CHECK(GetNativePairPtrSize(methods, i, image_pointer_size).object == nullptr);
}
for (size_t i = 0; i < num_fields; ++i) {
CHECK_EQ(GetNativePairPtrSize(fields, i, image_pointer_size).index, 0u);
@@ -149,6 +154,9 @@
if (fields != nullptr) {
mirror::FieldDexCachePair::Initialize(fields, image_pointer_size);
}
+ if (methods != nullptr) {
+ mirror::MethodDexCachePair::Initialize(methods, image_pointer_size);
+ }
if (method_types != nullptr) {
mirror::MethodTypeDexCachePair::Initialize(method_types);
}
@@ -159,14 +167,13 @@
types,
num_types,
methods,
- dex_file->NumMethodIds(),
+ num_methods,
fields,
num_fields,
method_types,
num_method_types,
call_sites,
- dex_file->NumCallSiteIds(),
- image_pointer_size);
+ dex_file->NumCallSiteIds());
}
void DexCache::Init(const DexFile* dex_file,
@@ -175,15 +182,14 @@
uint32_t num_strings,
TypeDexCacheType* resolved_types,
uint32_t num_resolved_types,
- ArtMethod** resolved_methods,
+ MethodDexCacheType* resolved_methods,
uint32_t num_resolved_methods,
FieldDexCacheType* resolved_fields,
uint32_t num_resolved_fields,
MethodTypeDexCacheType* resolved_method_types,
uint32_t num_resolved_method_types,
GcRoot<CallSite>* resolved_call_sites,
- uint32_t num_resolved_call_sites,
- PointerSize pointer_size) {
+ uint32_t num_resolved_call_sites) {
CHECK(dex_file != nullptr);
CHECK(location != nullptr);
CHECK_EQ(num_strings != 0u, strings != nullptr);
@@ -207,31 +213,13 @@
SetField32<false>(NumResolvedFieldsOffset(), num_resolved_fields);
SetField32<false>(NumResolvedMethodTypesOffset(), num_resolved_method_types);
SetField32<false>(NumResolvedCallSitesOffset(), num_resolved_call_sites);
-
- Runtime* const runtime = Runtime::Current();
- if (runtime->HasResolutionMethod()) {
- // Initialize the resolve methods array to contain trampolines for resolution.
- Fixup(runtime->GetResolutionMethod(), pointer_size);
- }
-}
-
-void DexCache::Fixup(ArtMethod* trampoline, PointerSize pointer_size) {
- // Fixup the resolve methods array to contain trampoline for resolution.
- CHECK(trampoline != nullptr);
- CHECK(trampoline->IsRuntimeMethod());
- auto* resolved_methods = GetResolvedMethods();
- for (size_t i = 0, length = NumResolvedMethods(); i < length; i++) {
- if (GetElementPtrSize<ArtMethod*>(resolved_methods, i, pointer_size) == nullptr) {
- SetElementPtrSize(resolved_methods, i, trampoline, pointer_size);
- }
- }
}
void DexCache::SetLocation(ObjPtr<mirror::String> location) {
SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(DexCache, location_), location);
}
-#if !defined(__aarch64__) && !defined(__x86_64__)
+#if !defined(__aarch64__) && !defined(__x86_64__) && !defined(__mips__)
static pthread_mutex_t dex_cache_slow_atomic_mutex = PTHREAD_MUTEX_INITIALIZER;
DexCache::ConversionPair64 DexCache::AtomicLoadRelaxed16B(std::atomic<ConversionPair64>* target) {
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index cf570b8..f75786b 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -129,6 +129,9 @@
using FieldDexCachePair = NativeDexCachePair<ArtField>;
using FieldDexCacheType = std::atomic<FieldDexCachePair>;
+using MethodDexCachePair = NativeDexCachePair<ArtMethod>;
+using MethodDexCacheType = std::atomic<MethodDexCachePair>;
+
using MethodTypeDexCachePair = DexCachePair<MethodType>;
using MethodTypeDexCacheType = std::atomic<MethodTypeDexCachePair>;
@@ -153,6 +156,11 @@
static_assert(IsPowerOfTwo(kDexCacheFieldCacheSize),
"Field dex cache size is not a power of 2.");
+ // Size of method dex cache. Needs to be a power of 2 for entrypoint assumptions to hold.
+ static constexpr size_t kDexCacheMethodCacheSize = 1024;
+ static_assert(IsPowerOfTwo(kDexCacheMethodCacheSize),
+ "Method dex cache size is not a power of 2.");
+
// Size of method type dex cache. Needs to be a power of 2 for entrypoint assumptions
// to hold.
static constexpr size_t kDexCacheMethodTypeCacheSize = 1024;
@@ -171,6 +179,10 @@
return kDexCacheFieldCacheSize;
}
+ static constexpr size_t StaticMethodSize() {
+ return kDexCacheMethodCacheSize;
+ }
+
static constexpr size_t StaticMethodTypeSize() {
return kDexCacheMethodTypeCacheSize;
}
@@ -189,9 +201,6 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(Locks::dex_lock_);
- void Fixup(ArtMethod* trampoline, PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
void FixupStrings(StringDexCacheType* dest, const Visitor& visitor)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -284,6 +293,8 @@
ArtMethod* resolved,
PointerSize ptr_size)
REQUIRES_SHARED(Locks::mutator_lock_);
+ ALWAYS_INLINE void ClearResolvedMethod(uint32_t method_idx, PointerSize ptr_size)
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Pointer sized variant, used for patching.
ALWAYS_INLINE ArtField* GetResolvedField(uint32_t idx, PointerSize ptr_size)
@@ -328,11 +339,11 @@
SetFieldPtr<false>(ResolvedTypesOffset(), resolved_types);
}
- ArtMethod** GetResolvedMethods() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
- return GetFieldPtr<ArtMethod**>(ResolvedMethodsOffset());
+ MethodDexCacheType* GetResolvedMethods() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetFieldPtr<MethodDexCacheType*>(ResolvedMethodsOffset());
}
- void SetResolvedMethods(ArtMethod** resolved_methods)
+ void SetResolvedMethods(MethodDexCacheType* resolved_methods)
ALWAYS_INLINE
REQUIRES_SHARED(Locks::mutator_lock_) {
SetFieldPtr<false>(ResolvedMethodsOffset(), resolved_methods);
@@ -429,6 +440,7 @@
uint32_t StringSlotIndex(dex::StringIndex string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
uint32_t TypeSlotIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_);
uint32_t FieldSlotIndex(uint32_t field_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+ uint32_t MethodSlotIndex(uint32_t method_idx) REQUIRES_SHARED(Locks::mutator_lock_);
uint32_t MethodTypeSlotIndex(uint32_t proto_idx) REQUIRES_SHARED(Locks::mutator_lock_);
private:
@@ -438,15 +450,14 @@
uint32_t num_strings,
TypeDexCacheType* resolved_types,
uint32_t num_resolved_types,
- ArtMethod** resolved_methods,
+ MethodDexCacheType* resolved_methods,
uint32_t num_resolved_methods,
FieldDexCacheType* resolved_fields,
uint32_t num_resolved_fields,
MethodTypeDexCacheType* resolved_method_types,
uint32_t num_resolved_method_types,
GcRoot<CallSite>* resolved_call_sites,
- uint32_t num_resolved_call_sites,
- PointerSize pointer_size)
+ uint32_t num_resolved_call_sites)
REQUIRES_SHARED(Locks::mutator_lock_);
// std::pair<> is not trivially copyable and as such it is unsuitable for atomic operations,
@@ -471,8 +482,8 @@
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
// Due to lack of 16-byte atomics support, we use hand-crafted routines.
-#if defined(__aarch64__)
- // 16-byte atomics are supported on aarch64.
+#if defined(__aarch64__) || defined(__mips__)
+ // 16-byte atomics are supported on aarch64, mips and mips64.
ALWAYS_INLINE static ConversionPair64 AtomicLoadRelaxed16B(
std::atomic<ConversionPair64>* target) {
return target->load(std::memory_order_relaxed);
diff --git a/runtime/mirror/dex_cache_test.cc b/runtime/mirror/dex_cache_test.cc
index 5b1ba8d..d2b9240 100644
--- a/runtime/mirror/dex_cache_test.cc
+++ b/runtime/mirror/dex_cache_test.cc
@@ -54,7 +54,8 @@
|| java_lang_dex_file_->NumStringIds() == dex_cache->NumStrings());
EXPECT_TRUE(dex_cache->StaticTypeSize() == dex_cache->NumResolvedTypes()
|| java_lang_dex_file_->NumTypeIds() == dex_cache->NumResolvedTypes());
- EXPECT_EQ(java_lang_dex_file_->NumMethodIds(), dex_cache->NumResolvedMethods());
+ EXPECT_TRUE(dex_cache->StaticMethodSize() == dex_cache->NumResolvedMethods()
+ || java_lang_dex_file_->NumMethodIds() == dex_cache->NumResolvedMethods());
EXPECT_TRUE(dex_cache->StaticArtFieldSize() == dex_cache->NumResolvedFields()
|| java_lang_dex_file_->NumFieldIds() == dex_cache->NumResolvedFields());
EXPECT_TRUE(dex_cache->StaticMethodTypeSize() == dex_cache->NumResolvedMethodTypes()
@@ -128,14 +129,18 @@
hs.NewHandle(class_linker_->FindClass(soa.Self(), "LMethodTypes;", class_loader)));
class_linker_->EnsureInitialized(soa.Self(), method_types, true, true);
- ArtMethod* method1 = method_types->FindVirtualMethod(
+ ArtMethod* method1 = method_types->FindClassMethod(
"method1",
"(Ljava/lang/String;)Ljava/lang/String;",
kRuntimePointerSize);
- ArtMethod* method2 = method_types->FindVirtualMethod(
+ ASSERT_TRUE(method1 != nullptr);
+ ASSERT_FALSE(method1->IsDirect());
+ ArtMethod* method2 = method_types->FindClassMethod(
"method2",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
kRuntimePointerSize);
+ ASSERT_TRUE(method2 != nullptr);
+ ASSERT_FALSE(method2->IsDirect());
const DexFile& dex_file = *(method1->GetDexFile());
Handle<mirror::DexCache> dex_cache = hs.NewHandle(
diff --git a/runtime/monitor_test.cc b/runtime/monitor_test.cc
index 27ce149..fb12841 100644
--- a/runtime/monitor_test.cc
+++ b/runtime/monitor_test.cc
@@ -36,11 +36,8 @@
protected:
void SetUpRuntimeOptions(RuntimeOptions *options) OVERRIDE {
// Use a smaller heap
- for (std::pair<std::string, const void*>& pair : *options) {
- if (pair.first.find("-Xmx") == 0) {
- pair.first = "-Xmx4M"; // Smallest we can go.
- }
- }
+ SetUpRuntimeOptionsForFillHeap(options);
+
options->push_back(std::make_pair("-Xint", nullptr));
}
public:
@@ -56,52 +53,6 @@
bool completed_;
};
-// Fill the heap.
-static const size_t kMaxHandles = 1000000; // Use arbitrary large amount for now.
-static void FillHeap(Thread* self, ClassLinker* class_linker,
- std::unique_ptr<StackHandleScope<kMaxHandles>>* hsp,
- std::vector<MutableHandle<mirror::Object>>* handles)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Runtime::Current()->GetHeap()->SetIdealFootprint(1 * GB);
-
- hsp->reset(new StackHandleScope<kMaxHandles>(self));
- // Class java.lang.Object.
- Handle<mirror::Class> c((*hsp)->NewHandle(class_linker->FindSystemClass(self,
- "Ljava/lang/Object;")));
- // Array helps to fill memory faster.
- Handle<mirror::Class> ca((*hsp)->NewHandle(class_linker->FindSystemClass(self,
- "[Ljava/lang/Object;")));
-
- // Start allocating with 128K
- size_t length = 128 * KB / 4;
- while (length > 10) {
- MutableHandle<mirror::Object> h((*hsp)->NewHandle<mirror::Object>(
- mirror::ObjectArray<mirror::Object>::Alloc(self, ca.Get(), length / 4)));
- if (self->IsExceptionPending() || h == nullptr) {
- self->ClearException();
-
- // Try a smaller length
- length = length / 8;
- // Use at most half the reported free space.
- size_t mem = Runtime::Current()->GetHeap()->GetFreeMemory();
- if (length * 8 > mem) {
- length = mem / 8;
- }
- } else {
- handles->push_back(h);
- }
- }
-
- // Allocate simple objects till it fails.
- while (!self->IsExceptionPending()) {
- MutableHandle<mirror::Object> h = (*hsp)->NewHandle<mirror::Object>(c->AllocObject(self));
- if (!self->IsExceptionPending() && h != nullptr) {
- handles->push_back(h);
- }
- }
- self->ClearException();
-}
-
// Check that an exception can be thrown correctly.
// This test is potentially racy, but the timeout is long enough that it should work.
@@ -304,16 +255,12 @@
test->complete_barrier_ = std::unique_ptr<Barrier>(new Barrier(3));
test->completed_ = false;
- // Fill the heap.
- std::unique_ptr<StackHandleScope<kMaxHandles>> hsp;
- std::vector<MutableHandle<mirror::Object>> handles;
-
// Our job: Fill the heap, then try Wait.
- FillHeap(soa.Self(), class_linker, &hsp, &handles);
+ {
+ VariableSizedHandleScope vhs(soa.Self());
+ test->FillHeap(soa.Self(), class_linker, &vhs);
- // Now release everything.
- for (MutableHandle<mirror::Object>& h : handles) {
- h.Assign(nullptr);
+ // Now release everything.
}
// Need to drop the mutator lock to allow barriers.
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index f6a8360..07dfb65 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -458,7 +458,8 @@
const char* filename,
const char* instruction_set,
const char* compiler_filter_name,
- bool profile_changed) {
+ bool profile_changed,
+ bool downgrade) {
if ((filename == nullptr) || !OS::FileExists(filename)) {
LOG(ERROR) << "DexFile_getDexOptNeeded file '" << filename << "' does not exist";
ScopedLocalRef<jclass> fnfe(env, env->FindClass("java/io/FileNotFoundException"));
@@ -492,7 +493,7 @@
if (oat_file_assistant.IsInBootClassPath()) {
return OatFileAssistant::kNoDexOptNeeded;
}
- return oat_file_assistant.GetDexOptNeeded(filter, profile_changed);
+ return oat_file_assistant.GetDexOptNeeded(filter, profile_changed, downgrade);
}
static jstring DexFile_getDexFileStatus(JNIEnv* env,
@@ -528,7 +529,8 @@
jstring javaFilename,
jstring javaInstructionSet,
jstring javaTargetCompilerFilter,
- jboolean newProfile) {
+ jboolean newProfile,
+ jboolean downgrade) {
ScopedUtfChars filename(env, javaFilename);
if (env->ExceptionCheck()) {
return -1;
@@ -548,7 +550,8 @@
filename.c_str(),
instruction_set.c_str(),
target_compiler_filter.c_str(),
- newProfile == JNI_TRUE);
+ newProfile == JNI_TRUE,
+ downgrade == JNI_TRUE);
}
// public API
@@ -725,7 +728,7 @@
NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"),
NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"),
NATIVE_METHOD(DexFile, getDexOptNeeded,
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)I"),
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),
NATIVE_METHOD(DexFile, openDexFileNative,
"(Ljava/lang/String;"
"Ljava/lang/String;"
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 4f5ec8c..020612b 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -298,15 +298,16 @@
// Based on ClassLinker::ResolveString.
static void PreloadDexCachesResolveString(
- Handle<mirror::DexCache> dex_cache, dex::StringIndex string_idx, StringTable& strings)
+ ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx, StringTable& strings)
REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::String> string = dex_cache->GetResolvedString(string_idx);
- if (string != nullptr) {
- return;
+ uint32_t slot_idx = dex_cache->StringSlotIndex(string_idx);
+ auto pair = dex_cache->GetStrings()[slot_idx].load(std::memory_order_relaxed);
+ if (!pair.object.IsNull()) {
+ return; // The entry already contains some String.
}
const DexFile* dex_file = dex_cache->GetDexFile();
const char* utf8 = dex_file->StringDataByIdx(string_idx);
- string = strings[utf8];
+ ObjPtr<mirror::String> string = strings[utf8];
if (string == nullptr) {
return;
}
@@ -319,18 +320,17 @@
ObjPtr<mirror::DexCache> dex_cache,
dex::TypeIndex type_idx)
REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Class> klass = dex_cache->GetResolvedType(type_idx);
- if (klass != nullptr) {
- return;
+ uint32_t slot_idx = dex_cache->TypeSlotIndex(type_idx);
+ auto pair = dex_cache->GetResolvedTypes()[slot_idx].load(std::memory_order_relaxed);
+ if (!pair.object.IsNull()) {
+ return; // The entry already contains some Class.
}
const DexFile* dex_file = dex_cache->GetDexFile();
const char* class_name = dex_file->StringByTypeIdx(type_idx);
ClassLinker* linker = Runtime::Current()->GetClassLinker();
- if (class_name[1] == '\0') {
- klass = linker->FindPrimitiveClass(class_name[0]);
- } else {
- klass = linker->LookupClass(self, class_name, nullptr);
- }
+ ObjPtr<mirror::Class> klass = (class_name[1] == '\0')
+ ? linker->FindPrimitiveClass(class_name[0])
+ : linker->LookupClass(self, class_name, nullptr);
if (klass == nullptr) {
return;
}
@@ -345,26 +345,27 @@
}
// Based on ClassLinker::ResolveField.
-static void PreloadDexCachesResolveField(Handle<mirror::DexCache> dex_cache, uint32_t field_idx,
+static void PreloadDexCachesResolveField(ObjPtr<mirror::DexCache> dex_cache,
+ uint32_t field_idx,
bool is_static)
REQUIRES_SHARED(Locks::mutator_lock_) {
- ArtField* field = dex_cache->GetResolvedField(field_idx, kRuntimePointerSize);
- if (field != nullptr) {
- return;
+ uint32_t slot_idx = dex_cache->FieldSlotIndex(field_idx);
+ auto pair = mirror::DexCache::GetNativePairPtrSize(dex_cache->GetResolvedFields(),
+ slot_idx,
+ kRuntimePointerSize);
+ if (pair.object != nullptr) {
+ return; // The entry already contains some ArtField.
}
const DexFile* dex_file = dex_cache->GetDexFile();
const DexFile::FieldId& field_id = dex_file->GetFieldId(field_idx);
- Thread* const self = Thread::Current();
- StackHandleScope<1> hs(self);
- Handle<mirror::Class> klass(hs.NewHandle(dex_cache->GetResolvedType(field_id.class_idx_)));
+ ObjPtr<mirror::Class> klass =
+ ClassLinker::LookupResolvedType(field_id.class_idx_, dex_cache, nullptr);
if (klass == nullptr) {
return;
}
- if (is_static) {
- field = mirror::Class::FindStaticField(self, klass.Get(), dex_cache.Get(), field_idx);
- } else {
- field = klass->FindInstanceField(dex_cache.Get(), field_idx);
- }
+ ArtField* field = is_static
+ ? mirror::Class::FindStaticField(Thread::Current(), klass, dex_cache, field_idx)
+ : klass->FindInstanceField(dex_cache, field_idx);
if (field == nullptr) {
return;
}
@@ -372,35 +373,25 @@
}
// Based on ClassLinker::ResolveMethod.
-static void PreloadDexCachesResolveMethod(Handle<mirror::DexCache> dex_cache, uint32_t method_idx,
- InvokeType invoke_type)
+static void PreloadDexCachesResolveMethod(ObjPtr<mirror::DexCache> dex_cache, uint32_t method_idx)
REQUIRES_SHARED(Locks::mutator_lock_) {
- ArtMethod* method = dex_cache->GetResolvedMethod(method_idx, kRuntimePointerSize);
- if (method != nullptr) {
- return;
+ uint32_t slot_idx = dex_cache->MethodSlotIndex(method_idx);
+ auto pair = mirror::DexCache::GetNativePairPtrSize(dex_cache->GetResolvedMethods(),
+ slot_idx,
+ kRuntimePointerSize);
+ if (pair.object != nullptr) {
+ return; // The entry already contains some ArtMethod.
}
const DexFile* dex_file = dex_cache->GetDexFile();
const DexFile::MethodId& method_id = dex_file->GetMethodId(method_idx);
- ObjPtr<mirror::Class> klass = dex_cache->GetResolvedType(method_id.class_idx_);
+ ObjPtr<mirror::Class> klass =
+ ClassLinker::LookupResolvedType(method_id.class_idx_, dex_cache, nullptr);
if (klass == nullptr) {
return;
}
- switch (invoke_type) {
- case kDirect:
- case kStatic:
- method = klass->FindDirectMethod(dex_cache.Get(), method_idx, kRuntimePointerSize);
- break;
- case kInterface:
- method = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, kRuntimePointerSize);
- break;
- case kSuper:
- case kVirtual:
- method = klass->FindVirtualMethod(dex_cache.Get(), method_idx, kRuntimePointerSize);
- break;
- default:
- LOG(FATAL) << "Unreachable - invocation type: " << invoke_type;
- UNREACHABLE();
- }
+ ArtMethod* method = klass->IsInterface()
+ ? klass->FindInterfaceMethod(dex_cache, method_idx, kRuntimePointerSize)
+ : klass->FindClassMethod(dex_cache, method_idx, kRuntimePointerSize);
if (method == nullptr) {
return;
}
@@ -462,27 +453,31 @@
}
ObjPtr<mirror::DexCache> const dex_cache = class_linker->FindDexCache(self, *dex_file);
DCHECK(dex_cache != nullptr); // Boot class path dex caches are never unloaded.
- for (size_t j = 0; j < dex_cache->NumStrings(); j++) {
- ObjPtr<mirror::String> string = dex_cache->GetResolvedString(dex::StringIndex(j));
- if (string != nullptr) {
+ for (size_t j = 0, num_strings = dex_cache->NumStrings(); j < num_strings; ++j) {
+ auto pair = dex_cache->GetStrings()[j].load(std::memory_order_relaxed);
+ if (!pair.object.IsNull()) {
filled->num_strings++;
}
}
- for (size_t j = 0; j < dex_cache->NumResolvedTypes(); j++) {
- ObjPtr<mirror::Class> klass = dex_cache->GetResolvedType(dex::TypeIndex(j));
- if (klass != nullptr) {
+ for (size_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; ++j) {
+ auto pair = dex_cache->GetResolvedTypes()[j].load(std::memory_order_relaxed);
+ if (!pair.object.IsNull()) {
filled->num_types++;
}
}
- for (size_t j = 0; j < dex_cache->NumResolvedFields(); j++) {
- ArtField* field = dex_cache->GetResolvedField(j, class_linker->GetImagePointerSize());
- if (field != nullptr) {
+ for (size_t j = 0, num_fields = dex_cache->NumResolvedFields(); j < num_fields; ++j) {
+ auto pair = mirror::DexCache::GetNativePairPtrSize(dex_cache->GetResolvedFields(),
+ j,
+ kRuntimePointerSize);
+ if (pair.object != nullptr) {
filled->num_fields++;
}
}
- for (size_t j = 0; j < dex_cache->NumResolvedMethods(); j++) {
- ArtMethod* method = dex_cache->GetResolvedMethod(j, kRuntimePointerSize);
- if (method != nullptr) {
+ for (size_t j = 0, num_methods = dex_cache->NumResolvedMethods(); j < num_methods; ++j) {
+ auto pair = mirror::DexCache::GetNativePairPtrSize(dex_cache->GetResolvedMethods(),
+ j,
+ kRuntimePointerSize);
+ if (pair.object != nullptr) {
filled->num_methods++;
}
}
@@ -522,8 +517,7 @@
for (size_t i = 0; i < boot_class_path.size(); i++) {
const DexFile* dex_file = boot_class_path[i];
CHECK(dex_file != nullptr);
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->RegisterDexFile(*dex_file, nullptr)));
+ ObjPtr<mirror::DexCache> dex_cache = linker->RegisterDexFile(*dex_file, nullptr);
CHECK(dex_cache != nullptr); // Boot class path dex caches are never unloaded.
if (kPreloadDexCachesStrings) {
for (size_t j = 0; j < dex_cache->NumStrings(); j++) {
@@ -533,7 +527,7 @@
if (kPreloadDexCachesTypes) {
for (size_t j = 0; j < dex_cache->NumResolvedTypes(); j++) {
- PreloadDexCachesResolveType(soa.Self(), dex_cache.Get(), dex::TypeIndex(j));
+ PreloadDexCachesResolveType(soa.Self(), dex_cache, dex::TypeIndex(j));
}
}
@@ -557,13 +551,11 @@
}
for (; it.HasNextDirectMethod(); it.Next()) {
uint32_t method_idx = it.GetMemberIndex();
- InvokeType invoke_type = it.GetMethodInvokeType(class_def);
- PreloadDexCachesResolveMethod(dex_cache, method_idx, invoke_type);
+ PreloadDexCachesResolveMethod(dex_cache, method_idx);
}
for (; it.HasNextVirtualMethod(); it.Next()) {
uint32_t method_idx = it.GetMemberIndex();
- InvokeType invoke_type = it.GetMethodInvokeType(class_def);
- PreloadDexCachesResolveMethod(dex_cache, method_idx, invoke_type);
+ PreloadDexCachesResolveMethod(dex_cache, method_idx);
}
}
}
diff --git a/runtime/native_stack_dump.cc b/runtime/native_stack_dump.cc
index cbff0bb..7e16357 100644
--- a/runtime/native_stack_dump.cc
+++ b/runtime/native_stack_dump.cc
@@ -337,7 +337,7 @@
} else {
os << StringPrintf(Is64BitInstructionSet(kRuntimeISA) ? "%016" PRIxPTR " "
: "%08" PRIxPTR " ",
- BacktraceMap::GetRelativePc(it->map, it->pc));
+ it->rel_pc);
os << it->map.name;
os << " (";
if (!it->func_name.empty()) {
diff --git a/runtime/oat.h b/runtime/oat.h
index de4b942..c4a983e 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
class PACKED(4) OatHeader {
public:
static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
- // Last oat version changed reason: update kMultiDexSeparator from ':' to '!'.
- static constexpr uint8_t kOatVersion[] = { '1', '3', '0', '\0' };
+ // Last oat version changed reason: MIPS Baker thunks.
+ static constexpr uint8_t kOatVersion[] = { '1', '3', '1', '\0' };
static constexpr const char* kImageLocationKey = "image-location";
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index c876657..96423c3 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -187,9 +187,11 @@
return true;
}
-int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target, bool profile_changed) {
+int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
+ bool profile_changed,
+ bool downgrade) {
OatFileInfo& info = GetBestInfo();
- DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target, profile_changed);
+ DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target, profile_changed, downgrade);
if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
return dexopt_needed;
}
@@ -230,7 +232,7 @@
}
OatFileInfo& info = GetBestInfo();
- switch (info.GetDexOptNeeded(target, profile_changed)) {
+ switch (info.GetDexOptNeeded(target, profile_changed, /*downgrade*/ false)) {
case kNoDexOptNeeded:
return kUpdateSucceeded;
@@ -1005,9 +1007,9 @@
}
OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded(
- CompilerFilter::Filter target, bool profile_changed) {
+ CompilerFilter::Filter target, bool profile_changed, bool downgrade) {
bool compilation_desired = CompilerFilter::IsAotCompilationEnabled(target);
- bool filter_okay = CompilerFilterIsOkay(target, profile_changed);
+ bool filter_okay = CompilerFilterIsOkay(target, profile_changed, downgrade);
if (filter_okay && Status() == kOatUpToDate) {
// The oat file is in good shape as is.
@@ -1064,7 +1066,7 @@
}
bool OatFileAssistant::OatFileInfo::CompilerFilterIsOkay(
- CompilerFilter::Filter target, bool profile_changed) {
+ CompilerFilter::Filter target, bool profile_changed, bool downgrade) {
const OatFile* file = GetFile();
if (file == nullptr) {
return false;
@@ -1075,7 +1077,8 @@
VLOG(oat) << "Compiler filter not okay because Profile changed";
return false;
}
- return CompilerFilter::IsAsGoodAs(current, target);
+ return downgrade ? !CompilerFilter::IsBetter(current, target) :
+ CompilerFilter::IsAsGoodAs(current, target);
}
bool OatFileAssistant::OatFileInfo::IsExecutable() {
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 92d87ea..320aa4f 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -147,13 +147,24 @@
bool Lock(std::string* error_msg);
// Return what action needs to be taken to produce up-to-date code for this
- // dex location that is at least as good as an oat file generated with the
- // given compiler filter. profile_changed should be true to indicate the
- // profile has recently changed for this dex location.
+ // dex location. If "downgrade" is set to false, it verifies if the current
+ // compiler filter is at least as good as an oat file generated with the
+ // given compiler filter otherwise, if its set to true, it checks whether
+ // the oat file generated with the target filter will be downgraded as
+ // compared to the current state. For example, if the current compiler filter is
+ // quicken, and target filter is verify, it will recommend to dexopt, while
+ // if the target filter is speed profile, it will recommend to keep it in its
+ // current state.
+ // profile_changed should be true to indicate the profile has recently changed
+ // for this dex location.
+ // If the purpose of the dexopt is to downgrade the compiler filter,
+ // set downgrade to true.
// Returns a positive status code if the status refers to the oat file in
// the oat location. Returns a negative status code if the status refers to
// the oat file in the odex location.
- int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter, bool profile_changed = false);
+ int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
+ bool profile_changed = false,
+ bool downgrade = false);
// Returns true if there is up-to-date code for this dex location,
// irrespective of the compiler filter of the up-to-date code.
@@ -310,8 +321,11 @@
// given target_compilation_filter.
// profile_changed should be true to indicate the profile has recently
// changed for this dex location.
+ // downgrade should be true if the purpose of dexopt is to downgrade the
+ // compiler filter.
DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
- bool profile_changed);
+ bool profile_changed,
+ bool downgrade);
// Returns the loaded file.
// Loads the file if needed. Returns null if the file failed to load.
@@ -344,7 +358,9 @@
// least as good as the given target filter. profile_changed should be
// true to indicate the profile has recently changed for this dex
// location.
- bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed);
+ // downgrade should be true if the purpose of dexopt is to downgrade the
+ // compiler filter.
+ bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed, bool downgrade);
// Release the loaded oat file.
// Returns null if the oat file hasn't been loaded.
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index e68a657..c679d73 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -40,6 +40,7 @@
#include "art_method-inl.h"
#include "base/array_ref.h"
#include "base/logging.h"
+#include "base/stringpiece.h"
#include "class_linker-inl.h"
#include "debugger.h"
#include "dex_file.h"
@@ -572,13 +573,15 @@
// Try and get the declared method. First try to get a virtual method then a direct method if that's
// not found.
static art::ArtMethod* FindMethod(art::Handle<art::mirror::Class> klass,
- const char* name,
+ art::StringPiece name,
art::Signature sig) REQUIRES_SHARED(art::Locks::mutator_lock_) {
- art::ArtMethod* m = klass->FindDeclaredVirtualMethod(name, sig, art::kRuntimePointerSize);
- if (m == nullptr) {
- m = klass->FindDeclaredDirectMethod(name, sig, art::kRuntimePointerSize);
+ DCHECK(!klass->IsProxyClass());
+ for (art::ArtMethod& m : klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize)) {
+ if (m.GetName() == name && m.GetSignature() == sig) {
+ return &m;
+ }
}
- return m;
+ return nullptr;
}
bool Redefiner::ClassRedefinition::CheckSameMethods() {
@@ -1368,7 +1371,7 @@
const art::DexFile::TypeId& declaring_class_id = dex_file_->GetTypeId(class_def.class_idx_);
const art::DexFile& old_dex_file = mclass->GetDexFile();
// Update methods.
- for (art::ArtMethod& method : mclass->GetMethods(image_pointer_size)) {
+ for (art::ArtMethod& method : mclass->GetDeclaredMethods(image_pointer_size)) {
const art::DexFile::StringId* new_name_id = dex_file_->FindStringId(method.GetName());
art::dex::TypeIndex method_return_idx =
dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(method.GetReturnTypeDescriptor()));
diff --git a/runtime/openjdkjvmti/ti_search.cc b/runtime/openjdkjvmti/ti_search.cc
index 50a01fc..25bc5d6 100644
--- a/runtime/openjdkjvmti/ti_search.cc
+++ b/runtime/openjdkjvmti/ti_search.cc
@@ -105,17 +105,21 @@
}
art::ArtMethod* get_property =
- properties_class->FindDeclaredVirtualMethod(
+ properties_class->FindClassMethod(
"getProperty",
"(Ljava/lang/String;)Ljava/lang/String;",
art::kRuntimePointerSize);
DCHECK(get_property != nullptr);
+ DCHECK(!get_property->IsDirect());
+ DCHECK(get_property->GetDeclaringClass() == properties_class);
art::ArtMethod* set_property =
- properties_class->FindDeclaredVirtualMethod(
+ properties_class->FindClassMethod(
"setProperty",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;",
art::kRuntimePointerSize);
DCHECK(set_property != nullptr);
+ DCHECK(!set_property->IsDirect());
+ DCHECK(set_property->GetDeclaringClass() == properties_class);
// This is an allocation. Do this late to avoid the need for handles.
ScopedLocalRef<jobject> cp_jobj(self->GetJniEnv(), nullptr);
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index f16b419..9acea2a 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -701,7 +701,7 @@
jvmtiError ThreadUtil::SuspendOther(art::Thread* self,
jthread target_jthread,
- art::Thread* target) {
+ const art::Thread* target) {
// Loop since we need to bail out and try again if we would end up getting suspended while holding
// the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
// release the lock, wait to get resumed and try again.
@@ -729,12 +729,12 @@
if (state == art::ThreadState::kTerminated || state == art::ThreadState::kStarting) {
return ERR(THREAD_NOT_ALIVE);
}
- target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
+ art::Thread* ret_target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
target_jthread,
/* request_suspension */ true,
art::SuspendReason::kForUserCode,
&timeout);
- if (target == nullptr && !timeout) {
+ if (ret_target == nullptr && !timeout) {
// TODO It would be good to get more information about why exactly the thread failed to
// suspend.
return ERR(INTERNAL);
diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h
index d07dc06..0f7e837 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/runtime/openjdkjvmti/ti_thread.h
@@ -98,7 +98,9 @@
// cause the thread to wake up if the thread is suspended for the debugger or gc or something.
static jvmtiError SuspendSelf(art::Thread* self)
REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
- static jvmtiError SuspendOther(art::Thread* self, jthread target_jthread, art::Thread* target)
+ static jvmtiError SuspendOther(art::Thread* self,
+ jthread target_jthread,
+ const art::Thread* target)
REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
static art::ArtField* context_class_loader_;
diff --git a/runtime/proxy_test.cc b/runtime/proxy_test.cc
index 4e95b01..b055bf9 100644
--- a/runtime/proxy_test.cc
+++ b/runtime/proxy_test.cc
@@ -18,6 +18,7 @@
#include <vector>
#include "art_field-inl.h"
+#include "art_method-inl.h"
#include "base/enums.h"
#include "class_linker-inl.h"
#include "common_compiler_test.h"
@@ -63,21 +64,27 @@
jsize array_index = 0;
// Fill the method array
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
- ArtMethod* method = javaLangObject->FindDeclaredVirtualMethod(
+ ArtMethod* method = javaLangObject->FindClassMethod(
"equals", "(Ljava/lang/Object;)Z", kRuntimePointerSize);
CHECK(method != nullptr);
+ CHECK(!method->IsDirect());
+ CHECK(method->GetDeclaringClass() == javaLangObject);
DCHECK(!Runtime::Current()->IsActiveTransaction());
soa.Env()->SetObjectArrayElement(
proxyClassMethods, array_index++, soa.AddLocalReference<jobject>(
mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>(soa.Self(), method)));
- method = javaLangObject->FindDeclaredVirtualMethod("hashCode", "()I", kRuntimePointerSize);
+ method = javaLangObject->FindClassMethod("hashCode", "()I", kRuntimePointerSize);
CHECK(method != nullptr);
+ CHECK(!method->IsDirect());
+ CHECK(method->GetDeclaringClass() == javaLangObject);
soa.Env()->SetObjectArrayElement(
proxyClassMethods, array_index++, soa.AddLocalReference<jobject>(
mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>(soa.Self(), method)));
- method = javaLangObject->FindDeclaredVirtualMethod(
+ method = javaLangObject->FindClassMethod(
"toString", "()Ljava/lang/String;", kRuntimePointerSize);
CHECK(method != nullptr);
+ CHECK(!method->IsDirect());
+ CHECK(method->GetDeclaringClass() == javaLangObject);
soa.Env()->SetObjectArrayElement(
proxyClassMethods, array_index++, soa.AddLocalReference<jobject>(
mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>(soa.Self(), method)));
diff --git a/runtime/reference_table_test.cc b/runtime/reference_table_test.cc
index 260be8f..d830387 100644
--- a/runtime/reference_table_test.cc
+++ b/runtime/reference_table_test.cc
@@ -56,8 +56,8 @@
h_ref_class->AllocObject(self)));
CHECK(h_ref_instance != nullptr);
- ArtMethod* constructor = h_ref_class->FindDeclaredDirectMethod(
- "<init>", "(Ljava/lang/Object;)V", class_linker->GetImagePointerSize());
+ ArtMethod* constructor = h_ref_class->FindConstructor(
+ "(Ljava/lang/Object;)V", class_linker->GetImagePointerSize());
CHECK(constructor != nullptr);
uint32_t args[2];
diff --git a/runtime/reflection_test.cc b/runtime/reflection_test.cc
index 5eb75ed..fa2f1e5 100644
--- a/runtime/reflection_test.cc
+++ b/runtime/reflection_test.cc
@@ -108,9 +108,9 @@
class_loader);
CHECK(c != nullptr);
- *method = is_static ? c->FindDirectMethod(method_name, method_signature, kRuntimePointerSize)
- : c->FindVirtualMethod(method_name, method_signature, kRuntimePointerSize);
- CHECK(method != nullptr);
+ *method = c->FindClassMethod(method_name, method_signature, kRuntimePointerSize);
+ CHECK(*method != nullptr);
+ CHECK_EQ(is_static, (*method)->IsStatic());
if (is_static) {
*receiver = nullptr;
@@ -520,10 +520,11 @@
mirror::Class* klass = class_linker_->FindClass(soa.Self(), "LMain;", class_loader);
ASSERT_TRUE(klass != nullptr);
- ArtMethod* method = klass->FindDirectMethod("main",
- "([Ljava/lang/String;)V",
- kRuntimePointerSize);
+ ArtMethod* method = klass->FindClassMethod("main",
+ "([Ljava/lang/String;)V",
+ kRuntimePointerSize);
ASSERT_TRUE(method != nullptr);
+ ASSERT_TRUE(method->IsStatic());
// Start runtime.
bool started = runtime_->Start();
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index adad703..2366e10 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -632,9 +632,10 @@
hs.NewHandle(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_ClassLoader)));
CHECK(cl->EnsureInitialized(soa.Self(), class_loader_class, true, true));
- ArtMethod* getSystemClassLoader = class_loader_class->FindDirectMethod(
+ ArtMethod* getSystemClassLoader = class_loader_class->FindClassMethod(
"getSystemClassLoader", "()Ljava/lang/ClassLoader;", pointer_size);
CHECK(getSystemClassLoader != nullptr);
+ CHECK(getSystemClassLoader->IsStatic());
JValue result = InvokeWithJValues(soa,
nullptr,
@@ -2064,19 +2065,30 @@
}
// Transaction support.
-void Runtime::EnterTransactionMode(Transaction* transaction) {
+void Runtime::EnterTransactionMode() {
DCHECK(IsAotCompiler());
- DCHECK(transaction != nullptr);
DCHECK(!IsActiveTransaction());
- preinitialization_transaction_ = transaction;
+ preinitialization_transaction_ = std::make_unique<Transaction>();
+}
+
+void Runtime::EnterTransactionMode(mirror::Class* root) {
+ DCHECK(IsAotCompiler());
+ preinitialization_transaction_ = std::make_unique<Transaction>(root);
}
void Runtime::ExitTransactionMode() {
DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
preinitialization_transaction_ = nullptr;
}
+void Runtime::RollbackAndExitTransactionMode() {
+ DCHECK(IsAotCompiler());
+ DCHECK(IsActiveTransaction());
+ std::unique_ptr<Transaction> rollback_transaction_= std::move(preinitialization_transaction_);
+ ExitTransactionMode();
+ rollback_transaction_->Rollback();
+}
+
bool Runtime::IsTransactionAborted() const {
if (!IsActiveTransaction()) {
return false;
diff --git a/runtime/runtime.h b/runtime/runtime.h
index af9d215..9424596 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -24,6 +24,7 @@
#include <set>
#include <string>
#include <utility>
+#include <memory>
#include <vector>
#include "arch/instruction_set.h"
@@ -457,8 +458,12 @@
bool IsActiveTransaction() const {
return preinitialization_transaction_ != nullptr;
}
- void EnterTransactionMode(Transaction* transaction);
+ void EnterTransactionMode();
+ void EnterTransactionMode(mirror::Class* root);
void ExitTransactionMode();
+ // Transaction rollback and exit transaction are always done together, it's convenience to
+ // do them in one function.
+ void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
bool IsTransactionAborted() const;
void AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message)
@@ -842,7 +847,7 @@
bool dump_gc_performance_on_shutdown_;
// Transaction used for pre-initializing classes at compilation time.
- Transaction* preinitialization_transaction_;
+ std::unique_ptr<Transaction> preinitialization_transaction_;
// If kNone, verification is disabled. kEnable by default.
verifier::VerifyMode verify_;
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 09a200a..78a60fa 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -70,7 +70,7 @@
RUNTIME_OPTIONS_KEY (bool, EnableHSpaceCompactForOOM, true)
RUNTIME_OPTIONS_KEY (bool, UseJitCompilation, false)
RUNTIME_OPTIONS_KEY (bool, DumpNativeStackOnSigQuit, true)
-RUNTIME_OPTIONS_KEY (unsigned int, JITCompileThreshold, jit::Jit::kDefaultCompileThreshold)
+RUNTIME_OPTIONS_KEY (unsigned int, JITCompileThreshold)
RUNTIME_OPTIONS_KEY (unsigned int, JITWarmupThreshold)
RUNTIME_OPTIONS_KEY (unsigned int, JITOsrThreshold)
RUNTIME_OPTIONS_KEY (unsigned int, JITPriorityThreadWeight)
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 9d4f116..cdbb908 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2780,7 +2780,7 @@
}
}
ArtMethod* exception_init_method =
- exception_class->FindDeclaredDirectMethod("<init>", signature, cl->GetImagePointerSize());
+ exception_class->FindConstructor(signature, cl->GetImagePointerSize());
CHECK(exception_init_method != nullptr) << "No <init>" << signature << " in "
<< PrettyDescriptor(exception_class_descriptor);
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 907d37e..359030e 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -38,6 +38,10 @@
CHECK(Runtime::Current()->IsAotCompiler());
}
+Transaction::Transaction(mirror::Class* root) : Transaction() {
+ root_ = root;
+}
+
Transaction::~Transaction() {
if (kEnableTransactionStats) {
MutexLock mu(Thread::Current(), log_lock_);
@@ -270,6 +274,7 @@
void Transaction::VisitRoots(RootVisitor* visitor) {
MutexLock mu(Thread::Current(), log_lock_);
+ visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&root_), RootInfo(kRootUnknown));
VisitObjectLogs(visitor);
VisitArrayLogs(visitor);
VisitInternStringLogs(visitor);
diff --git a/runtime/transaction.h b/runtime/transaction.h
index 747c2d0..22518f6 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -32,6 +32,7 @@
namespace art {
namespace mirror {
class Array;
+class Class;
class DexCache;
class Object;
class String;
@@ -44,6 +45,7 @@
static constexpr const char* kAbortExceptionSignature = "Ldalvik/system/TransactionAbortError;";
Transaction();
+ explicit Transaction(mirror::Class* root);
~Transaction();
void Abort(const std::string& abort_message)
@@ -288,6 +290,7 @@
std::list<ResolveStringLog> resolve_string_logs_ GUARDED_BY(log_lock_);
bool aborted_ GUARDED_BY(log_lock_);
std::string abort_message_ GUARDED_BY(log_lock_);
+ mirror::Class* root_ GUARDED_BY(log_lock_);
DISALLOW_COPY_AND_ASSIGN(Transaction);
};
diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc
index 9206292..e52dd08 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -69,14 +69,12 @@
mirror::Class::Status old_status = h_klass->GetStatus();
LockWord old_lock_word = h_klass->GetLockWord(false);
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
- Runtime::Current()->ExitTransactionMode();
+ ASSERT_TRUE(Runtime::Current()->IsTransactionAborted());
ASSERT_FALSE(success);
ASSERT_TRUE(h_klass->IsErroneous());
ASSERT_TRUE(soa.Self()->IsExceptionPending());
- ASSERT_TRUE(transaction.IsAborted());
// Check class's monitor get back to its original state without rolling back changes.
LockWord new_lock_word = h_klass->GetLockWord(false);
@@ -84,7 +82,7 @@
// Check class status is rolled back properly.
soa.Self()->ClearException();
- transaction.Rollback();
+ Runtime::Current()->RollbackAndExitTransactionMode();
ASSERT_EQ(old_status, h_klass->GetStatus());
}
};
@@ -97,15 +95,12 @@
hs.NewHandle(class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Object;")));
ASSERT_TRUE(h_klass != nullptr);
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
Handle<mirror::Object> h_obj(hs.NewHandle(h_klass->AllocObject(soa.Self())));
ASSERT_TRUE(h_obj != nullptr);
ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
- Runtime::Current()->ExitTransactionMode();
-
// Rolling back transaction's changes must not clear the Object::class field.
- transaction.Rollback();
+ Runtime::Current()->RollbackAndExitTransactionMode();
EXPECT_EQ(h_obj->GetClass(), h_klass.Get());
}
@@ -124,15 +119,13 @@
h_obj->MonitorEnter(soa.Self());
LockWord old_lock_word = h_obj->GetLockWord(false);
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
// Unlock object's monitor inside the transaction.
h_obj->MonitorExit(soa.Self());
LockWord new_lock_word = h_obj->GetLockWord(false);
- Runtime::Current()->ExitTransactionMode();
-
// Rolling back transaction's changes must not change monitor's state.
- transaction.Rollback();
+ Runtime::Current()->RollbackAndExitTransactionMode();
+
LockWord aborted_lock_word = h_obj->GetLockWord(false);
EXPECT_FALSE(LockWord::Equal<false>(old_lock_word, new_lock_word));
EXPECT_TRUE(LockWord::Equal<false>(aborted_lock_word, new_lock_word));
@@ -148,8 +141,7 @@
constexpr int32_t kArraySize = 2;
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
// Allocate an array during transaction.
Handle<mirror::Array> h_obj(
@@ -159,10 +151,9 @@
Runtime::Current()->GetHeap()->GetCurrentAllocator())));
ASSERT_TRUE(h_obj != nullptr);
ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
- Runtime::Current()->ExitTransactionMode();
+ Runtime::Current()->RollbackAndExitTransactionMode();
// Rolling back transaction's changes must not reset array's length.
- transaction.Rollback();
EXPECT_EQ(h_obj->GetLength(), kArraySize);
}
@@ -238,8 +229,7 @@
ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
// Modify fields inside transaction then rollback changes.
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
booleanField->SetBoolean<true>(h_klass.Get(), true);
byteField->SetByte<true>(h_klass.Get(), 1);
charField->SetChar<true>(h_klass.Get(), 1u);
@@ -249,8 +239,7 @@
floatField->SetFloat<true>(h_klass.Get(), 1.0);
doubleField->SetDouble<true>(h_klass.Get(), 1.0);
objectField->SetObject<true>(h_klass.Get(), h_obj.Get());
- Runtime::Current()->ExitTransactionMode();
- transaction.Rollback();
+ Runtime::Current()->RollbackAndExitTransactionMode();
// Check values have properly been restored to their original (default) value.
EXPECT_EQ(booleanField->GetBoolean(h_klass.Get()), false);
@@ -340,8 +329,7 @@
ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
// Modify fields inside transaction then rollback changes.
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
booleanField->SetBoolean<true>(h_instance.Get(), true);
byteField->SetByte<true>(h_instance.Get(), 1);
charField->SetChar<true>(h_instance.Get(), 1u);
@@ -351,8 +339,7 @@
floatField->SetFloat<true>(h_instance.Get(), 1.0);
doubleField->SetDouble<true>(h_instance.Get(), 1.0);
objectField->SetObject<true>(h_instance.Get(), h_obj.Get());
- Runtime::Current()->ExitTransactionMode();
- transaction.Rollback();
+ Runtime::Current()->RollbackAndExitTransactionMode();
// Check values have properly been restored to their original (default) value.
EXPECT_EQ(booleanField->GetBoolean(h_instance.Get()), false);
@@ -457,8 +444,7 @@
ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
// Modify fields inside transaction then rollback changes.
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
booleanArray->SetWithoutChecks<true>(0, true);
byteArray->SetWithoutChecks<true>(0, 1);
charArray->SetWithoutChecks<true>(0, 1u);
@@ -468,8 +454,7 @@
floatArray->SetWithoutChecks<true>(0, 1.0);
doubleArray->SetWithoutChecks<true>(0, 1.0);
objectArray->SetWithoutChecks<true>(0, h_obj.Get());
- Runtime::Current()->ExitTransactionMode();
- transaction.Rollback();
+ Runtime::Current()->RollbackAndExitTransactionMode();
// Check values have properly been restored to their original (default) value.
EXPECT_EQ(booleanArray->GetWithoutChecks(0), false);
@@ -511,8 +496,7 @@
EXPECT_TRUE(class_linker_->LookupString(*dex_file, string_idx, h_dex_cache.Get()) == nullptr);
EXPECT_TRUE(h_dex_cache->GetResolvedString(string_idx) == nullptr);
// Do the transaction, then roll back.
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
ASSERT_TRUE(success);
ASSERT_TRUE(h_klass->IsInitialized());
@@ -523,8 +507,7 @@
EXPECT_STREQ(s->ToModifiedUtf8().c_str(), kResolvedString);
EXPECT_EQ(s, h_dex_cache->GetResolvedString(string_idx));
}
- Runtime::Current()->ExitTransactionMode();
- transaction.Rollback();
+ Runtime::Current()->RollbackAndExitTransactionMode();
// Check that the string did not stay resolved.
EXPECT_TRUE(class_linker_->LookupString(*dex_file, string_idx, h_dex_cache.Get()) == nullptr);
EXPECT_TRUE(h_dex_cache->GetResolvedString(string_idx) == nullptr);
@@ -547,8 +530,7 @@
class_linker_->VerifyClass(soa.Self(), h_klass);
ASSERT_TRUE(h_klass->IsVerified());
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(success);
@@ -571,8 +553,7 @@
class_linker_->VerifyClass(soa.Self(), h_klass);
ASSERT_TRUE(h_klass->IsVerified());
- Transaction transaction;
- Runtime::Current()->EnterTransactionMode(&transaction);
+ Runtime::Current()->EnterTransactionMode();
bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
Runtime::Current()->ExitTransactionMode();
ASSERT_TRUE(success);
diff --git a/runtime/utils/dex_cache_arrays_layout-inl.h b/runtime/utils/dex_cache_arrays_layout-inl.h
index 72f63c6..9d4e9fb 100644
--- a/runtime/utils/dex_cache_arrays_layout-inl.h
+++ b/runtime/utils/dex_cache_arrays_layout-inl.h
@@ -64,7 +64,7 @@
"Expecting alignof(StringDexCacheType) == 8");
static_assert(alignof(mirror::MethodTypeDexCacheType) == 8,
"Expecting alignof(MethodTypeDexCacheType) == 8");
- // This is the same as alignof(FieldDexCacheType) for the given pointer size.
+ // This is the same as alignof({Field,Method}DexCacheType) for the given pointer size.
return 2u * static_cast<size_t>(pointer_size);
}
@@ -84,7 +84,7 @@
if (num_elements < cache_size) {
cache_size = num_elements;
}
- return ArraySize(PointerSize::k64, cache_size);
+ return PairArraySize(GcRootAsPointerSize<mirror::Class>(), cache_size);
}
inline size_t DexCacheArraysLayout::TypesAlignment() const {
@@ -96,11 +96,15 @@
}
inline size_t DexCacheArraysLayout::MethodsSize(size_t num_elements) const {
- return ArraySize(pointer_size_, num_elements);
+ size_t cache_size = mirror::DexCache::kDexCacheMethodCacheSize;
+ if (num_elements < cache_size) {
+ cache_size = num_elements;
+ }
+ return PairArraySize(pointer_size_, cache_size);
}
inline size_t DexCacheArraysLayout::MethodsAlignment() const {
- return static_cast<size_t>(pointer_size_);
+ return 2u * static_cast<size_t>(pointer_size_);
}
inline size_t DexCacheArraysLayout::StringOffset(uint32_t string_idx) const {
@@ -113,7 +117,7 @@
if (num_elements < cache_size) {
cache_size = num_elements;
}
- return ArraySize(PointerSize::k64, cache_size);
+ return PairArraySize(GcRootAsPointerSize<mirror::String>(), cache_size);
}
inline size_t DexCacheArraysLayout::StringsAlignment() const {
@@ -132,7 +136,7 @@
if (num_elements < cache_size) {
cache_size = num_elements;
}
- return 2u * static_cast<size_t>(pointer_size_) * cache_size;
+ return PairArraySize(pointer_size_, cache_size);
}
inline size_t DexCacheArraysLayout::FieldsAlignment() const {
@@ -170,6 +174,10 @@
return static_cast<size_t>(element_size) * num_elements;
}
+inline size_t DexCacheArraysLayout::PairArraySize(PointerSize element_size, uint32_t num_elements) {
+ return 2u * static_cast<size_t>(element_size) * num_elements;
+}
+
} // namespace art
#endif // ART_RUNTIME_UTILS_DEX_CACHE_ARRAYS_LAYOUT_INL_H_
diff --git a/runtime/utils/dex_cache_arrays_layout.h b/runtime/utils/dex_cache_arrays_layout.h
index 377a374..fc04159 100644
--- a/runtime/utils/dex_cache_arrays_layout.h
+++ b/runtime/utils/dex_cache_arrays_layout.h
@@ -130,6 +130,7 @@
static size_t ElementOffset(PointerSize element_size, uint32_t idx);
static size_t ArraySize(PointerSize element_size, uint32_t num_elements);
+ static size_t PairArraySize(PointerSize element_size, uint32_t num_elements);
};
} // namespace art
diff --git a/runtime/utils_test.cc b/runtime/utils_test.cc
index 634bd47..48b703a 100644
--- a/runtime/utils_test.cc
+++ b/runtime/utils_test.cc
@@ -192,18 +192,21 @@
ASSERT_TRUE(c != nullptr);
ArtMethod* m;
- m = c->FindVirtualMethod("charAt", "(I)C", kRuntimePointerSize);
+ m = c->FindClassMethod("charAt", "(I)C", kRuntimePointerSize);
ASSERT_TRUE(m != nullptr);
+ ASSERT_FALSE(m->IsDirect());
EXPECT_EQ("Java_java_lang_String_charAt", m->JniShortName());
EXPECT_EQ("Java_java_lang_String_charAt__I", m->JniLongName());
- m = c->FindVirtualMethod("indexOf", "(Ljava/lang/String;I)I", kRuntimePointerSize);
+ m = c->FindClassMethod("indexOf", "(Ljava/lang/String;I)I", kRuntimePointerSize);
ASSERT_TRUE(m != nullptr);
+ ASSERT_FALSE(m->IsDirect());
EXPECT_EQ("Java_java_lang_String_indexOf", m->JniShortName());
EXPECT_EQ("Java_java_lang_String_indexOf__Ljava_lang_String_2I", m->JniLongName());
- m = c->FindDirectMethod("copyValueOf", "([CII)Ljava/lang/String;", kRuntimePointerSize);
+ m = c->FindClassMethod("copyValueOf", "([CII)Ljava/lang/String;", kRuntimePointerSize);
ASSERT_TRUE(m != nullptr);
+ ASSERT_TRUE(m->IsStatic());
EXPECT_EQ("Java_java_lang_String_copyValueOf", m->JniShortName());
EXPECT_EQ("Java_java_lang_String_copyValueOf___3CII", m->JniLongName());
}
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index ea480f4..0351fd3 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -72,8 +72,8 @@
private:
static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' };
- // Last update: Change quickening info format.
- static constexpr uint8_t kVdexVersion[] = { '0', '0', '8', '\0' };
+ // Last update: Change method lookup.
+ static constexpr uint8_t kVdexVersion[] = { '0', '0', '9', '\0' };
uint8_t magic_[4];
uint8_t version_[4];
diff --git a/runtime/verifier/method_resolution_kind.h b/runtime/verifier/method_resolution_kind.h
deleted file mode 100644
index f72eb7a..0000000
--- a/runtime/verifier/method_resolution_kind.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#ifndef ART_RUNTIME_VERIFIER_METHOD_RESOLUTION_KIND_H_
-#define ART_RUNTIME_VERIFIER_METHOD_RESOLUTION_KIND_H_
-
-namespace art {
-namespace verifier {
-
-// Values corresponding to the method resolution algorithms defined in mirror::Class.
-enum MethodResolutionKind {
- kDirectMethodResolution,
- kVirtualMethodResolution,
- kInterfaceMethodResolution,
-};
-
-} // namespace verifier
-} // namespace art
-
-#endif // ART_RUNTIME_VERIFIER_METHOD_RESOLUTION_KIND_H_
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index efb02f6..6dc7953 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -39,7 +39,6 @@
#include "indenter.h"
#include "intern_table.h"
#include "leb128.h"
-#include "method_resolution_kind.h"
#include "mirror/class.h"
#include "mirror/class-inl.h"
#include "mirror/dex_cache-inl.h"
@@ -230,7 +229,7 @@
}
previous_method_idx = method_idx;
InvokeType type = it->GetMethodInvokeType(class_def);
- ArtMethod* method = linker->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+ ArtMethod* method = linker->ResolveMethod<ClassLinker::ResolveMode::kNoChecks>(
*dex_file, method_idx, dex_cache, class_loader, nullptr, type);
if (method == nullptr) {
DCHECK(self->IsExceptionPending());
@@ -3821,21 +3820,6 @@
return *common_super;
}
-inline static MethodResolutionKind GetMethodResolutionKind(
- MethodType method_type, bool is_interface) {
- if (method_type == METHOD_DIRECT || method_type == METHOD_STATIC) {
- return kDirectMethodResolution;
- } else if (method_type == METHOD_INTERFACE) {
- return kInterfaceMethodResolution;
- } else if (method_type == METHOD_SUPER && is_interface) {
- return kInterfaceMethodResolution;
- } else {
- DCHECK(method_type == METHOD_VIRTUAL || method_type == METHOD_SUPER
- || method_type == METHOD_POLYMORPHIC);
- return kVirtualMethodResolution;
- }
-}
-
ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess(
uint32_t dex_method_idx, MethodType method_type) {
const DexFile::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx);
@@ -3849,47 +3833,41 @@
if (klass_type.IsUnresolvedTypes()) {
return nullptr; // Can't resolve Class so no more to do here
}
- mirror::Class* klass = klass_type.GetClass();
+ ObjPtr<mirror::Class> klass = klass_type.GetClass();
const RegType& referrer = GetDeclaringClass();
auto* cl = Runtime::Current()->GetClassLinker();
auto pointer_size = cl->GetImagePointerSize();
- MethodResolutionKind res_kind = GetMethodResolutionKind(method_type, klass->IsInterface());
ArtMethod* res_method = dex_cache_->GetResolvedMethod(dex_method_idx, pointer_size);
- bool stash_method = false;
if (res_method == nullptr) {
- const char* name = dex_file_->GetMethodName(method_id);
- const Signature signature = dex_file_->GetMethodSignature(method_id);
-
- if (res_kind == kDirectMethodResolution) {
- res_method = klass->FindDirectMethod(name, signature, pointer_size);
- } else if (res_kind == kVirtualMethodResolution) {
- res_method = klass->FindVirtualMethod(name, signature, pointer_size);
+ // Try to find the method with the appropriate lookup for the klass type (interface or not).
+ // If this lookup does not match `method_type`, errors shall be reported below.
+ if (klass->IsInterface()) {
+ res_method = klass->FindInterfaceMethod(dex_cache_.Get(), dex_method_idx, pointer_size);
} else {
- DCHECK_EQ(res_kind, kInterfaceMethodResolution);
- res_method = klass->FindInterfaceMethod(name, signature, pointer_size);
+ res_method = klass->FindClassMethod(dex_cache_.Get(), dex_method_idx, pointer_size);
}
-
if (res_method != nullptr) {
- stash_method = true;
- } else {
- // If a virtual or interface method wasn't found with the expected type, look in
- // the direct methods. This can happen when the wrong invoke type is used or when
- // a class has changed, and will be flagged as an error in later checks.
- // Note that in this case, we do not put the resolved method in the Dex cache
- // because it was not discovered using the expected type of method resolution.
- if (res_kind != kDirectMethodResolution) {
- // Record result of the initial resolution attempt.
- VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_kind, nullptr);
- // Change resolution type to 'direct' and try to resolve again.
- res_kind = kDirectMethodResolution;
- res_method = klass->FindDirectMethod(name, signature, pointer_size);
- }
+ dex_cache_->SetResolvedMethod(dex_method_idx, res_method, pointer_size);
}
}
- // Record result of method resolution attempt.
- VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_kind, res_method);
+ // Record result of method resolution attempt. The klass resolution has recorded whether
+ // the class is an interface or not and therefore the type of the lookup performed above.
+ // TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
+ VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_method);
+
+ if (res_method == nullptr) {
+ // Try to find the method also with the other type for better error reporting below
+ // but do not store such bogus lookup result in the DexCache or VerifierDeps.
+ if (klass->IsInterface()) {
+ res_method = klass->FindClassMethod(dex_cache_.Get(), dex_method_idx, pointer_size);
+ } else {
+ // If there was an interface method with the same signature,
+ // we would have found it also in the "copied" methods.
+ DCHECK(klass->FindInterfaceMethod(dex_cache_.Get(), dex_method_idx, pointer_size) == nullptr);
+ }
+ }
if (res_method == nullptr) {
Fail(VERIFY_ERROR_NO_METHOD) << "couldn't find method "
@@ -3940,11 +3918,6 @@
}
}
- // Only stash after the above passed. Otherwise the method wasn't guaranteed to be correct.
- if (stash_method) {
- dex_cache_->SetResolvedMethod(dex_method_idx, res_method, pointer_size);
- }
-
// Check if access is allowed.
if (!referrer.CanAccessMember(res_method->GetDeclaringClass(), res_method->GetAccessFlags())) {
Fail(VERIFY_ERROR_ACCESS_METHOD) << "illegal method access (call "
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 740b7dd..883de38 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -711,6 +711,29 @@
DCHECK(c1 != nullptr && !c1->IsPrimitive());
DCHECK(c2 != nullptr && !c2->IsPrimitive());
mirror::Class* join_class = ClassJoin(c1, c2);
+ if (UNLIKELY(join_class == nullptr)) {
+ // Internal error joining the classes (e.g., OOME). Report an unresolved reference type.
+ // We cannot report an unresolved merge type, as that will attempt to merge the resolved
+ // components, leaving us in an infinite loop.
+ // We do not want to report the originating exception, as that would require a fast path
+ // out all the way to VerifyClass. Instead attempt to continue on without a detailed type.
+ Thread* self = Thread::Current();
+ self->AssertPendingException();
+ self->ClearException();
+
+ // When compiling on the host, we rather want to abort to ensure determinism for preopting.
+ // (In that case, it is likely a misconfiguration of dex2oat.)
+ if (!kIsTargetBuild && Runtime::Current()->IsAotCompiler()) {
+ LOG(FATAL) << "Could not create class join of "
+ << c1->PrettyClass()
+ << " & "
+ << c2->PrettyClass();
+ UNREACHABLE();
+ }
+
+ return reg_types->MakeUnresolvedReference();
+ }
+
// Record the dependency that both `c1` and `c2` are assignable to `join_class`.
// The `verifier` is null during unit tests.
if (verifier != nullptr) {
@@ -753,10 +776,18 @@
DCHECK(result->IsObjectClass());
return result;
}
+ Thread* self = Thread::Current();
ObjPtr<mirror::Class> common_elem = ClassJoin(s_ct, t_ct);
+ if (UNLIKELY(common_elem == nullptr)) {
+ self->AssertPendingException();
+ return nullptr;
+ }
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- mirror::Class* array_class = class_linker->FindArrayClass(Thread::Current(), &common_elem);
- DCHECK(array_class != nullptr);
+ mirror::Class* array_class = class_linker->FindArrayClass(self, &common_elem);
+ if (UNLIKELY(array_class == nullptr)) {
+ self->AssertPendingException();
+ return nullptr;
+ }
return array_class;
} else {
size_t s_depth = s->Depth();
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index 6c01a79..c5d8ff5 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -355,6 +355,10 @@
* the perversion of Object being assignable to an interface type (note, however, that we don't
* allow assignment of Object or Interface to any concrete class and are therefore type safe).
*
+ * Note: This may return null in case of internal errors, e.g., OOME when a new class would have
+ * to be created but there is no heap space. The exception will stay pending, and it is
+ * the job of the caller to handle it.
+ *
* [1] Java bytecode verification: algorithms and formalizations, Xavier Leroy
*/
static mirror::Class* ClassJoin(mirror::Class* s, mirror::Class* t)
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index 93286ea..0c00868 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -222,6 +222,11 @@
}
}
+const RegType& RegTypeCache::MakeUnresolvedReference() {
+ // The descriptor is intentionally invalid so nothing else will match this type.
+ return AddEntry(new (&arena_) UnresolvedReferenceType(AddString("a"), entries_.size()));
+}
+
const RegType* RegTypeCache::FindClass(mirror::Class* klass, bool precise) const {
DCHECK(klass != nullptr);
if (klass->IsPrimitive()) {
diff --git a/runtime/verifier/reg_type_cache.h b/runtime/verifier/reg_type_cache.h
index 37f8a1f..c9bf6a9 100644
--- a/runtime/verifier/reg_type_cache.h
+++ b/runtime/verifier/reg_type_cache.h
@@ -97,6 +97,10 @@
REQUIRES_SHARED(Locks::mutator_lock_);
const RegType& FromUnresolvedSuperClass(const RegType& child)
REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Note: this should not be used outside of RegType::ClassJoin!
+ const RegType& MakeUnresolvedReference() REQUIRES_SHARED(Locks::mutator_lock_);
+
const ConstantType& Zero() REQUIRES_SHARED(Locks::mutator_lock_) {
return FromCat1Const(0, true);
}
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index b0ea6c8..1aa0966 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -22,6 +22,7 @@
#include "base/casts.h"
#include "base/scoped_arena_allocator.h"
#include "common_runtime_test.h"
+#include "compiler_callbacks.h"
#include "reg_type_cache-inl.h"
#include "reg_type-inl.h"
#include "scoped_thread_state_change-inl.h"
@@ -677,5 +678,59 @@
EXPECT_FALSE(imprecise_const.Equals(precise_const));
}
+class RegTypeOOMTest : public RegTypeTest {
+ protected:
+ void SetUpRuntimeOptions(RuntimeOptions *options) OVERRIDE {
+ SetUpRuntimeOptionsForFillHeap(options);
+
+ // We must not appear to be a compiler, or we'll abort on the host.
+ callbacks_.reset();
+ }
+};
+
+TEST_F(RegTypeOOMTest, ClassJoinOOM) {
+ // TODO: Figure out why FillHeap isn't good enough under CMS.
+ TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS();
+
+ // Tests that we don't abort with OOMs.
+
+ ArenaStack stack(Runtime::Current()->GetArenaPool());
+ ScopedArenaAllocator allocator(&stack);
+ ScopedObjectAccess soa(Thread::Current());
+
+ // We cannot allow moving GC. Otherwise we'd have to ensure the reg types are updated (reference
+ // reg types store a class pointer in a GCRoot, which is normally updated through active verifiers
+ // being registered with their thread), which is unnecessarily complex.
+ Runtime::Current()->GetHeap()->IncrementDisableMovingGC(soa.Self());
+
+ // We merge nested array of primitive wrappers. These have a join type of an array of Number of
+ // the same depth. We start with depth five, as we want at least two newly created classes to
+ // test recursion (it's just more likely that nobody uses such deep arrays in runtime bringup).
+ constexpr const char* kIntArrayFive = "[[[[[Ljava/lang/Integer;";
+ constexpr const char* kFloatArrayFive = "[[[[[Ljava/lang/Float;";
+ constexpr const char* kNumberArrayFour = "[[[[Ljava/lang/Number;";
+ constexpr const char* kNumberArrayFive = "[[[[[Ljava/lang/Number;";
+
+ RegTypeCache cache(true, allocator);
+ const RegType& int_array_array = cache.From(nullptr, kIntArrayFive, false);
+ ASSERT_TRUE(int_array_array.HasClass());
+ const RegType& float_array_array = cache.From(nullptr, kFloatArrayFive, false);
+ ASSERT_TRUE(float_array_array.HasClass());
+
+ // Check assumptions: the joined classes don't exist, yet.
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ ASSERT_TRUE(class_linker->LookupClass(soa.Self(), kNumberArrayFour, nullptr) == nullptr);
+ ASSERT_TRUE(class_linker->LookupClass(soa.Self(), kNumberArrayFive, nullptr) == nullptr);
+
+ // Fill the heap.
+ VariableSizedHandleScope hs(soa.Self());
+ FillHeap(soa.Self(), class_linker, &hs);
+
+ const RegType& join_type = int_array_array.Merge(float_array_array, &cache, nullptr);
+ ASSERT_TRUE(join_type.IsUnresolvedReference());
+
+ Runtime::Current()->GetHeap()->DecrementDisableMovingGC(soa.Self());
+}
+
} // namespace verifier
} // namespace art
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index 122e05f..112eec8 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -54,9 +54,7 @@
MergeSets(my_deps->unassignable_types_, other_deps.unassignable_types_);
MergeSets(my_deps->classes_, other_deps.classes_);
MergeSets(my_deps->fields_, other_deps.fields_);
- MergeSets(my_deps->direct_methods_, other_deps.direct_methods_);
- MergeSets(my_deps->virtual_methods_, other_deps.virtual_methods_);
- MergeSets(my_deps->interface_methods_, other_deps.interface_methods_);
+ MergeSets(my_deps->methods_, other_deps.methods_);
for (dex::TypeIndex entry : other_deps.unverified_classes_) {
my_deps->unverified_classes_.push_back(entry);
}
@@ -317,7 +315,6 @@
void VerifierDeps::AddMethodResolution(const DexFile& dex_file,
uint32_t method_idx,
- MethodResolutionKind resolution_kind,
ArtMethod* method) {
DexFileDeps* dex_deps = GetDexFileDeps(dex_file);
if (dex_deps == nullptr) {
@@ -334,14 +331,7 @@
MethodResolution method_tuple(method_idx,
GetAccessFlags(method),
GetMethodDeclaringClassStringId(dex_file, method_idx, method));
- if (resolution_kind == kDirectMethodResolution) {
- dex_deps->direct_methods_.emplace(method_tuple);
- } else if (resolution_kind == kVirtualMethodResolution) {
- dex_deps->virtual_methods_.emplace(method_tuple);
- } else {
- DCHECK_EQ(resolution_kind, kInterfaceMethodResolution);
- dex_deps->interface_methods_.emplace(method_tuple);
- }
+ dex_deps->methods_.insert(method_tuple);
}
mirror::Class* VerifierDeps::FindOneClassPathBoundaryForInterface(mirror::Class* destination,
@@ -537,11 +527,10 @@
void VerifierDeps::MaybeRecordMethodResolution(const DexFile& dex_file,
uint32_t method_idx,
- MethodResolutionKind resolution_kind,
ArtMethod* method) {
VerifierDeps* thread_deps = GetThreadLocalVerifierDeps();
if (thread_deps != nullptr) {
- thread_deps->AddMethodResolution(dex_file, method_idx, resolution_kind, method);
+ thread_deps->AddMethodResolution(dex_file, method_idx, method);
}
}
@@ -698,9 +687,7 @@
EncodeSet(buffer, deps.unassignable_types_);
EncodeSet(buffer, deps.classes_);
EncodeSet(buffer, deps.fields_);
- EncodeSet(buffer, deps.direct_methods_);
- EncodeSet(buffer, deps.virtual_methods_);
- EncodeSet(buffer, deps.interface_methods_);
+ EncodeSet(buffer, deps.methods_);
EncodeUint16Vector(buffer, deps.unverified_classes_);
}
}
@@ -723,9 +710,7 @@
DecodeSet(&data_start, data_end, &deps->unassignable_types_);
DecodeSet(&data_start, data_end, &deps->classes_);
DecodeSet(&data_start, data_end, &deps->fields_);
- DecodeSet(&data_start, data_end, &deps->direct_methods_);
- DecodeSet(&data_start, data_end, &deps->virtual_methods_);
- DecodeSet(&data_start, data_end, &deps->interface_methods_);
+ DecodeSet(&data_start, data_end, &deps->methods_);
DecodeUint16Vector(&data_start, data_end, &deps->unverified_classes_);
}
CHECK_LE(data_start, data_end);
@@ -763,9 +748,7 @@
(unassignable_types_ == rhs.unassignable_types_) &&
(classes_ == rhs.classes_) &&
(fields_ == rhs.fields_) &&
- (direct_methods_ == rhs.direct_methods_) &&
- (virtual_methods_ == rhs.virtual_methods_) &&
- (interface_methods_ == rhs.interface_methods_) &&
+ (methods_ == rhs.methods_) &&
(unverified_classes_ == rhs.unverified_classes_);
}
@@ -825,27 +808,21 @@
}
}
- for (const auto& entry :
- { std::make_pair(kDirectMethodResolution, dep.second->direct_methods_),
- std::make_pair(kVirtualMethodResolution, dep.second->virtual_methods_),
- std::make_pair(kInterfaceMethodResolution, dep.second->interface_methods_) }) {
- for (const MethodResolution& method : entry.second) {
- const DexFile::MethodId& method_id = dex_file.GetMethodId(method.GetDexMethodIndex());
+ for (const MethodResolution& method : dep.second->methods_) {
+ const DexFile::MethodId& method_id = dex_file.GetMethodId(method.GetDexMethodIndex());
+ vios->Stream()
+ << dex_file.GetMethodDeclaringClassDescriptor(method_id) << "->"
+ << dex_file.GetMethodName(method_id)
+ << dex_file.GetMethodSignature(method_id).ToString()
+ << " is expected to be ";
+ if (!method.IsResolved()) {
+ vios->Stream() << "unresolved\n";
+ } else {
vios->Stream()
- << dex_file.GetMethodDeclaringClassDescriptor(method_id) << "->"
- << dex_file.GetMethodName(method_id)
- << dex_file.GetMethodSignature(method_id).ToString()
- << " is expected to be ";
- if (!method.IsResolved()) {
- vios->Stream() << "unresolved\n";
- } else {
- vios->Stream()
- << "in class "
- << GetStringFromId(dex_file, method.GetDeclaringClassIndex())
- << ", have the access flags " << std::hex << method.GetAccessFlags() << std::dec
- << ", and be of kind " << entry.first
- << "\n";
- }
+ << "in class "
+ << GetStringFromId(dex_file, method.GetDeclaringClassIndex())
+ << ", have the access flags " << std::hex << method.GetAccessFlags() << std::dec
+ << "\n";
}
}
@@ -1030,7 +1007,6 @@
bool VerifierDeps::VerifyMethods(Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
const std::set<MethodResolution>& methods,
- MethodResolutionKind kind,
Thread* self) const {
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
PointerSize pointer_size = class_linker->GetImagePointerSize();
@@ -1054,27 +1030,20 @@
}
DCHECK(cls->IsResolved());
ArtMethod* method = nullptr;
- if (kind == kDirectMethodResolution) {
- method = cls->FindDirectMethod(name, signature, pointer_size);
- } else if (kind == kVirtualMethodResolution) {
- method = cls->FindVirtualMethod(name, signature, pointer_size);
- } else {
- DCHECK_EQ(kind, kInterfaceMethodResolution);
+ if (cls->IsInterface()) {
method = cls->FindInterfaceMethod(name, signature, pointer_size);
+ } else {
+ method = cls->FindClassMethod(name, signature, pointer_size);
}
if (entry.IsResolved()) {
std::string temp;
if (method == nullptr) {
- LOG(INFO) << "VerifierDeps: Could not resolve "
- << kind
- << " method "
+ LOG(INFO) << "VerifierDeps: Could not resolve method "
<< GetMethodDescription(dex_file, entry.GetDexMethodIndex());
return false;
} else if (expected_decl_klass != method->GetDeclaringClass()->GetDescriptor(&temp)) {
- LOG(INFO) << "VerifierDeps: Unexpected declaring class for "
- << kind
- << " method resolution "
+ LOG(INFO) << "VerifierDeps: Unexpected declaring class for method resolution "
<< GetMethodDescription(dex_file, entry.GetDexMethodIndex())
<< " (expected="
<< expected_decl_klass
@@ -1083,9 +1052,7 @@
<< ")";
return false;
} else if (entry.GetAccessFlags() != GetAccessFlags(method)) {
- LOG(INFO) << "VerifierDeps: Unexpected access flags for resolved "
- << kind
- << " method resolution "
+ LOG(INFO) << "VerifierDeps: Unexpected access flags for resolved method resolution "
<< GetMethodDescription(dex_file, entry.GetDexMethodIndex())
<< std::hex
<< " (expected="
@@ -1096,9 +1063,7 @@
return false;
}
} else if (method != nullptr) {
- LOG(INFO) << "VerifierDeps: Unexpected successful resolution of "
- << kind
- << " method "
+ LOG(INFO) << "VerifierDeps: Unexpected successful resolution of method "
<< GetMethodDescription(dex_file, entry.GetDexMethodIndex());
return false;
}
@@ -1118,12 +1083,7 @@
result = result && VerifyClasses(class_loader, dex_file, deps.classes_, self);
result = result && VerifyFields(class_loader, dex_file, deps.fields_, self);
- result = result && VerifyMethods(
- class_loader, dex_file, deps.direct_methods_, kDirectMethodResolution, self);
- result = result && VerifyMethods(
- class_loader, dex_file, deps.virtual_methods_, kVirtualMethodResolution, self);
- result = result && VerifyMethods(
- class_loader, dex_file, deps.interface_methods_, kInterfaceMethodResolution, self);
+ result = result && VerifyMethods(class_loader, dex_file, deps.methods_, self);
return result;
}
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index 43eb948..b883a9e 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -25,7 +25,6 @@
#include "base/mutex.h"
#include "dex_file_types.h"
#include "handle.h"
-#include "method_resolution_kind.h"
#include "obj_ptr.h"
#include "thread.h"
#include "verifier_enums.h" // For MethodVerifier::FailureKind.
@@ -88,12 +87,10 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::verifier_deps_lock_);
- // Record the outcome `method` of resolving method `method_idx` from `dex_file`
- // using `res_kind` kind of method resolution algorithm. If `method` is null,
- // the method is assumed unresolved.
+ // Record the outcome `method` of resolving method `method_idx` from `dex_file`.
+ // If `method` is null, the method is assumed unresolved.
static void MaybeRecordMethodResolution(const DexFile& dex_file,
uint32_t method_idx,
- MethodResolutionKind res_kind,
ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::verifier_deps_lock_);
@@ -193,9 +190,7 @@
// Sets of recorded class/field/method resolutions.
std::set<ClassResolution> classes_;
std::set<FieldResolution> fields_;
- std::set<MethodResolution> direct_methods_;
- std::set<MethodResolution> virtual_methods_;
- std::set<MethodResolution> interface_methods_;
+ std::set<MethodResolution> methods_;
// List of classes that were not fully verified in that dex file.
std::vector<dex::TypeIndex> unverified_classes_;
@@ -267,7 +262,6 @@
void AddMethodResolution(const DexFile& dex_file,
uint32_t method_idx,
- MethodResolutionKind res_kind,
ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::verifier_deps_lock_);
@@ -321,7 +315,6 @@
bool VerifyMethods(Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
const std::set<MethodResolution>& methods,
- MethodResolutionKind kind,
Thread* self) const
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/test/066-mismatched-super/expected.txt b/test/066-mismatched-super/expected.txt
index 09c0596..f5b15ca 100644
--- a/test/066-mismatched-super/expected.txt
+++ b/test/066-mismatched-super/expected.txt
@@ -1 +1,2 @@
Got expected ICCE
+Got expected VerifyError
diff --git a/test/066-mismatched-super/info.txt b/test/066-mismatched-super/info.txt
index 7865ffc..2b70e06 100644
--- a/test/066-mismatched-super/info.txt
+++ b/test/066-mismatched-super/info.txt
@@ -1,2 +1,5 @@
-This tests what happens when class A extends abstract class B, but somebody
-turns B into an interface without rebuilding A.
+This tests two cases:
+1. What happens when class A extends abstract class B, but somebody
+ turns B into an interface without rebuilding A.
+2. What happens when class A extends a class B, but somebody
+ turns B into a final class without rebuilding A.
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/066-mismatched-super/src/ExtendsFinal.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/066-mismatched-super/src/ExtendsFinal.java
index 023e409..2f53b3b 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/066-mismatched-super/src/ExtendsFinal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,5 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public class ExtendsFinal extends Final {
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/066-mismatched-super/src/Final.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/066-mismatched-super/src/Final.java
index 023e409..a44d096 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/066-mismatched-super/src/Final.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,5 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public /* final */ class Final {
}
diff --git a/test/066-mismatched-super/src/Main.java b/test/066-mismatched-super/src/Main.java
index 55d0bab..6ae1198 100644
--- a/test/066-mismatched-super/src/Main.java
+++ b/test/066-mismatched-super/src/Main.java
@@ -20,10 +20,16 @@
public class Main {
public static void main(String[] args) {
try {
- Indirect.main();
+ Base base = new Base();
System.out.println("Succeeded unexpectedly");
} catch (IncompatibleClassChangeError icce) {
System.out.println("Got expected ICCE");
}
+ try {
+ ExtendsFinal ef = new ExtendsFinal();
+ System.out.println("Succeeded unexpectedly");
+ } catch (VerifyError ve) {
+ System.out.println("Got expected VerifyError");
+ }
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/066-mismatched-super/src2/Final.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/066-mismatched-super/src2/Final.java
index 023e409..766da9b 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/066-mismatched-super/src2/Final.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,5 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public final class Final {
}
diff --git a/test/162-method-resolution/expected.txt b/test/162-method-resolution/expected.txt
new file mode 100644
index 0000000..1bf39c9
--- /dev/null
+++ b/test/162-method-resolution/expected.txt
@@ -0,0 +1,43 @@
+Calling Test1Derived.test():
+Test1Derived.foo()
+Calling Test1User.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IllegalAccessError
+Calling Test1User2.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IllegalAccessError
+Calling Test2User.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
+Calling Test2User2.test():
+Test2Base.foo()
+Calling Test3User.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
+Calling Test4User.test():
+Test4Derived@...
+Calling Test5User.test():
+Test5Derived.foo()
+Calling Test5User2.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
+Calling Test6User.test():
+Test6Derived@...
+Calling Test6User2.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
+Calling Test7User.test():
+Test7Interface.foo()
+Calling Test7User2.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IllegalAccessError
+Calling Test8User.test():
+Test8Derived.foo()
+Calling Test8User2.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
+Calling Test9User.test():
+Test9Derived.foo()
+Calling Test9User2.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
diff --git a/test/162-method-resolution/info.txt b/test/162-method-resolution/info.txt
new file mode 100644
index 0000000..ff57a9a
--- /dev/null
+++ b/test/162-method-resolution/info.txt
@@ -0,0 +1,4 @@
+Tests that the method resolution is consistent with JLS and the RI.
+Where the RI conflicts with JLS, we follow the JLS and suppress the divergence
+when the test is executed with --jvm.
+(See Main.java for per-test details.)
diff --git a/test/162-method-resolution/jasmin-multidex/Test1User.j b/test/162-method-resolution/jasmin-multidex/Test1User.j
new file mode 100644
index 0000000..09ba77b
--- /dev/null
+++ b/test/162-method-resolution/jasmin-multidex/Test1User.j
@@ -0,0 +1,26 @@
+; 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.
+
+.class public Test1User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test1Derived
+ dup
+ invokespecial Test1Derived.<init>()V
+ invokevirtual Test1Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin-multidex/Test3User.j b/test/162-method-resolution/jasmin-multidex/Test3User.j
new file mode 100644
index 0000000..90f3a4e
--- /dev/null
+++ b/test/162-method-resolution/jasmin-multidex/Test3User.j
@@ -0,0 +1,26 @@
+; 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.
+
+.class public Test3User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test3Derived
+ dup
+ invokespecial Test3Derived.<init>()V
+ invokevirtual Test3Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test1Derived.j b/test/162-method-resolution/jasmin/Test1Derived.j
new file mode 100644
index 0000000..d754c64
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test1Derived.j
@@ -0,0 +1,43 @@
+; 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.
+
+.class public Test1Derived
+.super Test1Base
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial Test1Base.<init>()V
+ return
+.end method
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test1Derived
+ dup
+ invokespecial Test1Derived.<init>()V
+ invokespecial Test1Derived.foo()V
+ return
+.end method
+
+.method private foo()V
+ .limit stack 2
+ .limit locals 1
+ getstatic java/lang/System/out Ljava/io/PrintStream;
+ ldc "Test1Derived.foo()"
+ invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test1User2.j b/test/162-method-resolution/jasmin/Test1User2.j
new file mode 100644
index 0000000..8af9aab
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test1User2.j
@@ -0,0 +1,26 @@
+; 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.
+
+.class public Test1User2
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test1Derived
+ dup
+ invokespecial Test1Derived.<init>()V
+ invokevirtual Test1Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test2Derived.j b/test/162-method-resolution/jasmin/Test2Derived.j
new file mode 100644
index 0000000..bb4525d
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test2Derived.j
@@ -0,0 +1,25 @@
+; 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.
+
+.class public Test2Derived
+.super Test2Base
+.implements Test2Interface
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial Test2Base.<init>()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test2User.j b/test/162-method-resolution/jasmin/Test2User.j
new file mode 100644
index 0000000..2cce074
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test2User.j
@@ -0,0 +1,26 @@
+; 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.
+
+.class public Test2User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test2Derived
+ dup
+ invokespecial Test2Derived.<init>()V
+ invokevirtual Test2Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test2User2.j b/test/162-method-resolution/jasmin/Test2User2.j
new file mode 100644
index 0000000..eb80f32
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test2User2.j
@@ -0,0 +1,23 @@
+; 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.
+
+.class public Test2User2
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 0
+ .limit locals 0
+ invokestatic Test2Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test3Derived.j b/test/162-method-resolution/jasmin/Test3Derived.j
new file mode 100644
index 0000000..2bf4bf1
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test3Derived.j
@@ -0,0 +1,25 @@
+; 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.
+
+.class public Test3Derived
+.super Test3Base
+.implements Test3Interface
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial Test3Base.<init>()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test4User.j b/test/162-method-resolution/jasmin/Test4User.j
new file mode 100644
index 0000000..5b65368
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test4User.j
@@ -0,0 +1,29 @@
+; 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.
+
+.class public Test4User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 3
+ .limit locals 0
+ getstatic java/lang/System/out Ljava/io/PrintStream;
+ new Test4Derived
+ dup
+ invokespecial Test4Derived.<init>()V
+ invokeinterface Test4Interface.toString()Ljava/lang/String; 1
+ invokestatic Main.normalizeToString(Ljava/lang/String;)Ljava/lang/String;
+ invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test5User.j b/test/162-method-resolution/jasmin/Test5User.j
new file mode 100644
index 0000000..036e366
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test5User.j
@@ -0,0 +1,40 @@
+; 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.
+
+.class public Test5User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 1
+ new Test5Derived
+ dup
+ invokespecial Test5Derived.<init>()V
+ astore_0
+
+ ; Call an unresolved method bar() to force verification at runtime
+ ; to populate the dex cache entry for Test5Base.foo()V.
+ ; try { b.bar(); } catch (IncompatibleClassChangeError icce) { }
+ aload_0
+ dup ; Bogus operand to be swallowed by the pop in the non-exceptional path.
+ catch_begin:
+ invokevirtual Test5Derived.bar()V
+ catch_end:
+ pop ; Pops the exception or the bogus operand from above.
+ .catch java/lang/IncompatibleClassChangeError from catch_begin to catch_end using catch_end
+
+ aload_0
+ invokevirtual Test5Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test5User2.j b/test/162-method-resolution/jasmin/Test5User2.j
new file mode 100644
index 0000000..9484a69
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test5User2.j
@@ -0,0 +1,26 @@
+; 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.
+
+.class public Test5User2
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test5Derived
+ dup
+ invokespecial Test5Derived.<init>()V
+ invokeinterface Test5Derived.foo()V 1
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test6User.j b/test/162-method-resolution/jasmin/Test6User.j
new file mode 100644
index 0000000..55b43f1
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test6User.j
@@ -0,0 +1,29 @@
+; 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.
+
+.class public Test6User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 3
+ .limit locals 0
+ getstatic java/lang/System/out Ljava/io/PrintStream;
+ new Test6Derived
+ dup
+ invokespecial Test6Derived.<init>()V
+ invokeinterface Test6Interface.toString()Ljava/lang/String; 1
+ invokestatic Main.normalizeToString(Ljava/lang/String;)Ljava/lang/String;
+ invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test6User2.j b/test/162-method-resolution/jasmin/Test6User2.j
new file mode 100644
index 0000000..ab9ac0e
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test6User2.j
@@ -0,0 +1,29 @@
+; 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.
+
+.class public Test6User2
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 3
+ .limit locals 0
+ getstatic java/lang/System/out Ljava/io/PrintStream;
+ new Test6Derived
+ dup
+ invokespecial Test6Derived.<init>()V
+ invokevirtual Test6Interface.toString()Ljava/lang/String;
+ invokestatic Main.normalizeToString(Ljava/lang/String;)Ljava/lang/String;
+ invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test8Derived.j b/test/162-method-resolution/jasmin/Test8Derived.j
new file mode 100644
index 0000000..73f8b28
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test8Derived.j
@@ -0,0 +1,33 @@
+; 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.
+
+.class public Test8Derived
+.super Test8Base
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial Test8Base.<init>()V
+ return
+.end method
+
+.method public foo()V
+ .limit stack 2
+ .limit locals 1
+ getstatic java/lang/System/out Ljava/io/PrintStream;
+ ldc "Test8Derived.foo()"
+ invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test8User.j b/test/162-method-resolution/jasmin/Test8User.j
new file mode 100644
index 0000000..af60c6e
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test8User.j
@@ -0,0 +1,26 @@
+; 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.
+
+.class public Test8User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test8Derived
+ dup
+ invokespecial Test8Derived.<init>()V
+ invokevirtual Test8Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test8User2.j b/test/162-method-resolution/jasmin/Test8User2.j
new file mode 100644
index 0000000..5cdb95c
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test8User2.j
@@ -0,0 +1,23 @@
+; 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.
+
+.class public Test8User2
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 0
+ .limit locals 0
+ invokestatic Test8Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test9Derived.j b/test/162-method-resolution/jasmin/Test9Derived.j
new file mode 100644
index 0000000..789f0f2
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test9Derived.j
@@ -0,0 +1,33 @@
+; 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.
+
+.class public Test9Derived
+.super Test9Base
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial Test9Base.<init>()V
+ return
+.end method
+
+.method public static foo()V
+ .limit stack 2
+ .limit locals 1
+ getstatic java/lang/System/out Ljava/io/PrintStream;
+ ldc "Test9Derived.foo()"
+ invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test9User.j b/test/162-method-resolution/jasmin/Test9User.j
new file mode 100644
index 0000000..81f9a7d
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test9User.j
@@ -0,0 +1,23 @@
+; 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.
+
+.class public Test9User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 0
+ .limit locals 0
+ invokestatic Test9Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test9User2.j b/test/162-method-resolution/jasmin/Test9User2.j
new file mode 100644
index 0000000..ae53905
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test9User2.j
@@ -0,0 +1,26 @@
+; 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.
+
+.class public Test9User2
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 2
+ .limit locals 0
+ new Test9Derived
+ dup
+ invokespecial Test9Derived.<init>()V
+ invokevirtual Test9Derived.foo()V
+ return
+.end method
diff --git a/test/162-method-resolution/multidex.jpp b/test/162-method-resolution/multidex.jpp
new file mode 100644
index 0000000..22e3aee
--- /dev/null
+++ b/test/162-method-resolution/multidex.jpp
@@ -0,0 +1,117 @@
+Test1Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test1Base
+Test1Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test1Derived
+Test1User2:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test1User2
+
+Test2Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test2Base
+Test2Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test2Derived
+Test2Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test2Interface
+Test2User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test2User
+Test2User2:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test2User2
+
+Test3Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test3Base
+Test3Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test3Derived
+Test3Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test3Interface
+
+Test4Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test4Interface
+Test4Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test4Derived
+Test4User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test4User
+
+Test5Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test5Interface
+Test5Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test5Base
+Test5Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test5Derived
+Test5User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test5User
+Test5User2:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test5User2
+
+Test6Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test6Interface
+Test6Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test6Derived
+Test6User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test6User
+Test6User2:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test6User2
+
+Test7Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test7Base
+Test7Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test7Interface
+Test7Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test7Derived
+Test7User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test7User
+
+Test8Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test8Base
+Test8Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test8Derived
+Test8User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test8User
+Test8User2:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test8User2
+
+Test9Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test9Base
+Test9Derived:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test9Derived
+Test9User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test9User
+Test9User2:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test9User2
+
+Main:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Main
diff --git a/test/162-method-resolution/src/Main.java b/test/162-method-resolution/src/Main.java
new file mode 100644
index 0000000..fa95aa7
--- /dev/null
+++ b/test/162-method-resolution/src/Main.java
@@ -0,0 +1,401 @@
+/*
+ * 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.
+ */
+
+import java.lang.reflect.Method;
+
+public class Main {
+ public static void main(String[] args) {
+ // Check if we're running dalvik or RI.
+ usingRI = false;
+ try {
+ Class.forName("dalvik.system.PathClassLoader");
+ } catch (ClassNotFoundException e) {
+ usingRI = true;
+ }
+
+ try {
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+ test6();
+ test7();
+ test8();
+ test9();
+
+ // TODO: How to test that interface method resolution returns the unique
+ // maximally-specific non-abstract superinterface method if there is one?
+ // Maybe reflection? (This is not even implemented yet!)
+ } catch (Throwable t) {
+ t.printStackTrace(System.out);
+ }
+ }
+
+ /*
+ * Test1
+ * -----
+ * Tested functions:
+ * public class Test1Base {
+ * public void foo() { ... }
+ * }
+ * public class Test1Derived extends Test1Base {
+ * private void foo() { ... }
+ * ...
+ * }
+ * Tested invokes:
+ * invoke-direct Test1Derived.foo()V from Test1Derived in first dex file
+ * expected: executes Test1Derived.foo()V
+ * invoke-virtual Test1Derived.foo()V from Test1User in second dex file
+ * expected: throws IllegalAccessError (JLS 15.12.4.3)
+ * invoke-virtual Test1Derived.foo()V from Test1User2 in first dex file
+ * expected: throws IllegalAccessError (JLS 15.12.4.3)
+ *
+ * Previously, the behavior was inconsistent between dex files, throwing ICCE
+ * from one and invoking the method from another. This was because the lookups for
+ * direct and virtual methods were independent but results were stored in a single
+ * slot in the DexCache method array and then retrieved from there without checking
+ * the resolution kind. Thus, the first invoke-direct stored the private
+ * Test1Derived.foo() in the DexCache and the attempt to use invoke-virtual
+ * from the same dex file (by Test1User2) would throw ICCE. However, the same
+ * invoke-virtual from a different dex file (by Test1User) would ignore the
+ * direct method Test1Derived.foo() and find the Test1Base.foo() and call it.
+ *
+ * The method lookup has been changed and we now consistently find the private
+ * Derived.foo() and throw ICCE for both invoke-virtual calls.
+ *
+ * Files:
+ * src/Test1Base.java - defines public foo()V.
+ * jasmin/Test1Derived.j - defines private foo()V, calls it with invokespecial.
+ * jasmin-multidex/Test1User.j - calls invokevirtual Test1Derived.foo().
+ * jasmin/Test1User2.j - calls invokevirtual Test1Derived.foo().
+ */
+ private static void test1() throws Exception {
+ invokeUserTest("Test1Derived");
+ invokeUserTest("Test1User");
+ invokeUserTest("Test1User2");
+ }
+
+ /*
+ * Test2
+ * -----
+ * Tested functions:
+ * public class Test2Base {
+ * public static void foo() { ... }
+ * }
+ * public interface Test2Interface {
+ * default void foo() { ... } // default: avoid subclassing Test2Derived.
+ * }
+ * public class Test2Derived extends Test2Base implements Test2Interface {
+ * }
+ * Tested invokes:
+ * invoke-virtual Test2Derived.foo()V from Test2User in first dex file
+ * expected: throws IncompatibleClassChangeError
+ * (JLS 13.4.19, the inherited Base.foo() changed from non-static to static)
+ * invoke-static Test2Derived.foo()V from Test2User2 in first dex file
+ * expected: executes Test2Base.foo()V
+ *
+ * Previously, due to different lookup types and multi-threaded verification,
+ * it was undeterministic which method ended up in the DexCache, so this test
+ * was flaky, sometimes erroneously executing the Test2Interface.foo().
+ *
+ * The method lookup has been changed and we now consistently find the
+ * Test2Base.foo()V over the method from the interface, in line with the RI.
+ *
+ * Files:
+ * src/Test2Base.java - defines public static foo()V.
+ * src/Test2Interface.java - defines default foo()V.
+ * jasmin/Test2Derived.j - extends Test2Derived, implements Test2Interface.
+ * jasmin/Test2User.j - calls invokevirtual Test2Derived.foo()
+ * jasmin/Test2User2.j - calls invokestatic Test2Derived.foo()
+ */
+ private static void test2() throws Exception {
+ invokeUserTest("Test2User");
+ invokeUserTest("Test2User2");
+ }
+
+ /*
+ * Test3
+ * -----
+ * Tested functions:
+ * public class Test3Base {
+ * public static void foo() { ... }
+ * }
+ * public interface Test3Interface {
+ * default void foo() { ... } // default: avoid subclassing Test3Derived.
+ * }
+ * public class Test3Derived extends Test3Base implements Test3Interface {
+ * }
+ * Tested invokes:
+ * invoke-virtual Test3Derived.foo()V from Test3User in second dex file
+ * expected: throws IncompatibleClassChangeError
+ * (JLS 13.4.19, the inherited Base.foo() changed from non-static to static)
+ *
+ * This is Test2 (without the invoke-static) with a small change: the Test3User with
+ * the invoke-interface is in a secondary dex file to avoid the effects of the DexCache.
+ *
+ * Previously the invoke-virtual would resolve to the Test3Interface.foo()V but
+ * it now resolves to Test3Base.foo()V and throws ICCE in line with the RI.
+ *
+ * Files:
+ * src/Test3Base.java - defines public static foo()V.
+ * src/Test3Interface.java - defines default foo()V.
+ * src/Test3Derived.java - extends Test2Derived, implements Test2Interface.
+ * jasmin-multidex/Test3User.j - calls invokevirtual Test3Derived.foo()
+ */
+ private static void test3() throws Exception {
+ invokeUserTest("Test3User");
+ }
+
+ /*
+ * Test4
+ * -----
+ * Tested functions:
+ * public interface Test4Interface {
+ * // Not declaring toString().
+ * }
+ * Tested invokes:
+ * invoke-interface Test4Interface.toString()Ljava/lang/String; in first dex file
+ * expected: executes java.lang.Object.toString()Ljava/lang/String
+ * (JLS 9.2 specifies implicitly declared methods from Object).
+ *
+ * The RI resolves the call to java.lang.Object.toString() and executes it.
+ * ART used to resolve it in a secondary resolution attempt only to distinguish
+ * between ICCE and NSME and then throw ICCE. We now allow the call to proceed.
+ *
+ * Files:
+ * src/Test4Interface.java - does not declare toString().
+ * src/Test4Derived.java - extends Test4Interface.
+ * jasmin/Test4User.j - calls invokeinterface Test4Interface.toString().
+ */
+ private static void test4() throws Exception {
+ invokeUserTest("Test4User");
+ }
+
+ /*
+ * Test5
+ * -----
+ * Tested functions:
+ * public interface Test5Interface {
+ * public void foo();
+ * }
+ * public abstract class Test5Base implements Test5Interface{
+ * // Not declaring foo().
+ * }
+ * public class Test5Derived extends Test5Base {
+ * public void foo() { ... }
+ * }
+ * Tested invokes:
+ * invoke-virtual Test5Base.foo()V from Test5User in first dex file
+ * expected: executes Test5Derived.foo()V
+ * invoke-interface Test5Base.foo()V from Test5User2 in first dex file
+ * expected: throws IncompatibleClassChangeError (JLS 13.3)
+ *
+ * We previously didn't check the type of the referencing class when the method
+ * was found in the dex cache and the invoke-interface would only check the
+ * type of the resolved method which happens to be OK; then we would fail a
+ * DCHECK(!method->IsCopied()) in Class::FindVirtualMethodForInterface(). This has
+ * been fixed and we consistently check the type of the referencing class as well.
+ *
+ * Since normal virtual method dispatch in compiled or quickened code does not
+ * actually use the DexCache and we want to populate the Test5Base.foo()V entry
+ * anyway, we force verification at runtime by adding a call to an arbitrary
+ * unresolved method to Test5User.test(), catching and ignoring the ICCE. Files:
+ * src/Test5Interface.java - interface, declares foo()V.
+ * src/Test5Base.java - abstract class, implements Test5Interface.
+ * src/Test5Derived.java - extends Test5Base, implements foo()V.
+ * jasmin/Test5User2.j - calls invokeinterface Test5Base.foo()V.
+ * jasmin/Test5User.j - calls invokevirtual Test5Base.foo()V,
+ * - also calls undefined Test5Base.bar()V, supresses ICCE.
+ */
+ private static void test5() throws Exception {
+ invokeUserTest("Test5User");
+ invokeUserTest("Test5User2");
+ }
+
+ /*
+ * Test6
+ * -----
+ * Tested functions:
+ * public interface Test6Interface {
+ * // Not declaring toString().
+ * }
+ * Tested invokes:
+ * invoke-interface Test6Interface.toString() from Test6User in first dex file
+ * expected: executes java.lang.Object.toString()Ljava/lang/String
+ * (JLS 9.2 specifies implicitly declared methods from Object).
+ * invoke-virtual Test6Interface.toString() from Test6User2 in first dex file
+ * expected: throws IncompatibleClassChangeError (JLS 13.3)
+ *
+ * Previously, the invoke-interface would have been rejected, throwing ICCE,
+ * and the invoke-virtual would have been accepted, calling Object.toString().
+ *
+ * The method lookup has been changed and we now accept the invoke-interface,
+ * calling Object.toString(), and reject the invoke-virtual, throwing ICCE,
+ * in line with the RI. However, if the method is already in the DexCache for
+ * the invoke-virtual, we need to check the referenced class in order to throw
+ * the ICCE as the resolved method kind actually matches the invoke-virtual.
+ * This test ensures that we do.
+ *
+ * Files:
+ * src/Test6Interface.java - interface, does not declare toString().
+ * src/Test6Derived.java - implements Test6Interface.
+ * jasmin/Test6User.j - calls invokeinterface Test6Interface.toString().
+ * jasmin/Test6User2.j - calls invokevirtual Test6Interface.toString().
+ */
+ private static void test6() throws Exception {
+ invokeUserTest("Test6User");
+ invokeUserTest("Test6User2");
+ }
+
+ /*
+ * Test7
+ * -----
+ * Tested function:
+ * public class Test7Base {
+ * private void foo() { ... }
+ * }
+ * public interface Test7Interface {
+ * default void foo() { ... }
+ * }
+ * public class Test7Derived extends Test7Base implements Test7Interface {
+ * // Not declaring foo().
+ * }
+ * Tested invokes:
+ * invoke-virtual Test7Derived.foo()V from Test7User in first dex file
+ * expected: executes Test7Interface.foo()V (inherited by Test7Derived, JLS 8.4.8)
+ * invoke-interface Test7Interface.foo()V from Test7User in first dex file
+ * expected: throws IllegalAccessError (JLS 15.12.4.4)
+ * on a Test7Derived object.
+ *
+ * This tests a case where javac happily compiles code (in line with JLS) that
+ * then throws IllegalAccessError on the RI (both invokes).
+ *
+ * For the invoke-virtual, the RI throws IAE as the private Test7Base.foo() is
+ * found before the inherited (see JLS 8.4.8) Test7Interface.foo(). This conflicts
+ * with the JLS 15.12.2.1 saying that members inherited (JLS 8.4.8) from superclasses
+ * and superinterfaces are included in the search. ART follows the JLS behavior.
+ *
+ * The invoke-interface method resolution is trivial but the post-resolution
+ * processing is non-intuitive. According to the JLS 15.12.4.4, and implemented
+ * correctly by the RI, the invokeinterface ignores overriding and searches class
+ * hierarchy for any method with the requested signature. Thus it finds the private
+ * Test7Base.foo()V and throws IllegalAccessError. Unfortunately, ART does not comply
+ * and simply calls Test7Interface.foo()V. Bug: 63624936.
+ *
+ * Files:
+ * src/Test7User.java - calls invoke-virtual Test7Derived.foo()V.
+ * src/Test7Base.java - defines private foo()V.
+ * src/Test7Interface.java - defines default foo()V.
+ * src/Test7Derived.java - extends Test7Base, implements Test7Interface.
+ */
+ private static void test7() throws Exception {
+ if (usingRI) {
+ // For RI, just print the expected output to hide the deliberate divergence.
+ System.out.println("Calling Test7User.test():\n" +
+ "Test7Interface.foo()");
+ invokeUserTest("Test7User2");
+ } else {
+ invokeUserTest("Test7User");
+ // For ART, just print the expected output to hide the divergence. Bug: 63624936.
+ // The expected.txt lists the desired behavior, not the current behavior.
+ System.out.println("Calling Test7User2.test():\n" +
+ "Caught java.lang.reflect.InvocationTargetException\n" +
+ " caused by java.lang.IllegalAccessError");
+ }
+ }
+
+ /*
+ * Test8
+ * -----
+ * Tested function:
+ * public class Test8Base {
+ * public static void foo() { ... }
+ * }
+ * public class Test8Derived extends Test8Base {
+ * public void foo() { ... }
+ * }
+ * Tested invokes:
+ * invoke-virtual Test8Derived.foo()V from Test8User in first dex file
+ * expected: executes Test8Derived.foo()V
+ * invoke-static Test8Derived.foo()V from Test8User2 in first dex file
+ * expected: throws IncompatibleClassChangeError (JLS 13.4.19)
+ *
+ * Another test for invoke type mismatch.
+ *
+ * Files:
+ * src/Test8Base.java - defines static foo()V.
+ * jasmin/Test8Derived.j - defines non-static foo()V.
+ * jasmin/Test8User.j - calls invokevirtual Test8Derived.foo()V.
+ * jasmin/Test8User2.j - calls invokestatic Test8Derived.foo()V.
+ */
+ private static void test8() throws Exception {
+ invokeUserTest("Test8User");
+ invokeUserTest("Test8User2");
+ }
+
+ /*
+ * Test9
+ * -----
+ * Tested function:
+ * public class Test9Base {
+ * public void foo() { ... }
+ * }
+ * public class Test9Derived extends Test9Base {
+ * public static void foo() { ... }
+ * }
+ * Tested invokes:
+ * invoke-static Test9Derived.foo()V from Test9User in first dex file
+ * expected: executes Test9Derived.foo()V
+ * invoke-virtual Test9Derived.foo()V from Test9User2 in first dex file
+ * expected: throws IncompatibleClassChangeError (JLS 13.4.19)
+ *
+ * Another test for invoke type mismatch.
+ *
+ * Files:
+ * src/Test9Base.java - defines non-static foo()V.
+ * jasmin/Test9Derived.j - defines static foo()V.
+ * jasmin/Test9User.j - calls invokestatic Test8Derived.foo()V.
+ * jasmin/Test9User2.j - calls invokevirtual Test8Derived.foo()V.
+ */
+ private static void test9() throws Exception {
+ invokeUserTest("Test9User");
+ invokeUserTest("Test9User2");
+ }
+
+ private static void invokeUserTest(String userName) throws Exception {
+ System.out.println("Calling " + userName + ".test():");
+ try {
+ Class<?> user = Class.forName(userName);
+ Method utest = user.getDeclaredMethod("test");
+ utest.invoke(null);
+ } catch (Throwable t) {
+ System.out.println("Caught " + t.getClass().getName());
+ for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
+ System.out.println(" caused by " + c.getClass().getName());
+ }
+ }
+ }
+
+ // Replace the variable part of the output of the default toString() implementation
+ // so that we have a deterministic output.
+ static String normalizeToString(String s) {
+ int atPos = s.indexOf("@");
+ return s.substring(0, atPos + 1) + "...";
+ }
+
+ static boolean usingRI;
+}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test1Base.java
similarity index 65%
rename from test/066-mismatched-super/src/Indirect.java
rename to test/162-method-resolution/src/Test1Base.java
index 023e409..63a0ce3 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test1Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test1Base {
+ public void foo() {
+ System.out.println("Test1Base.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test2Base.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test2Base.java
index 023e409..7d028d4 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test2Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test2Base {
+ public static void foo() {
+ System.out.println("Test2Base.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test2Interface.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test2Interface.java
index 023e409..d5f1820 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test2Interface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public interface Test2Interface {
+ default void foo() {
+ System.out.println("Test2Interface.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test3Base.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test3Base.java
index 023e409..2c63ff3 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test3Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test3Base {
+ public static void foo() {
+ System.out.println("Test3Base.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test3Interface.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test3Interface.java
index 023e409..baaf671 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test3Interface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public interface Test3Interface {
+ default void foo() {
+ System.out.println("Test3Interface.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test4Derived.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test4Derived.java
index 023e409..e253f3b 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test4Derived.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,5 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public class Test4Derived implements Test4Interface {
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test4Interface.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test4Interface.java
index 023e409..49b516f 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test4Interface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public interface Test4Interface {
+ // removed: public String toString();
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test5Base.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test5Base.java
index 023e409..25914ee 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test5Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,5 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public abstract class Test5Base implements Test5Interface {
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test5Derived.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test5Derived.java
index 023e409..5717ed5 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test5Derived.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test5Derived extends Test5Base {
+ public void foo() {
+ System.out.println("Test5Derived.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test5Interface.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test5Interface.java
index 023e409..82c20b2 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test5Interface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public interface Test5Interface {
+ public void foo();
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test6Derived.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test6Derived.java
index 023e409..9213347 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test6Derived.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,5 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public class Test6Derived implements Test6Interface {
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test6Interface.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test6Interface.java
index 023e409..86e2e4b 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test6Interface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public interface Test6Interface {
+ // removed: public String toString();
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test7Base.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test7Base.java
index 023e409..4cc3223 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test7Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test7Base {
+ private void foo() {
+ System.out.println("Test7Base.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test7Derived.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test7Derived.java
index 023e409..25f0b56 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test7Derived.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,5 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public class Test7Derived extends Test7Base implements Test7Interface {
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test7Interface.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test7Interface.java
index 023e409..598b2dd 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test7Interface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public interface Test7Interface {
+ default void foo() {
+ System.out.println("Test7Interface.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test7User.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test7User.java
index 023e409..5cb5b0a 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test7User.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test7User {
+ public static void test() {
+ new Test7Derived().foo();
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test7User2.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test7User2.java
index 023e409..794c5c2 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test7User2.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,9 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test7User2 {
+ public static void test() {
+ Test7Interface iface = new Test7Derived();
+ iface.foo();
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test8Base.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test8Base.java
index 023e409..b4fd3bc 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test8Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test8Base {
+ public static void foo() {
+ System.out.println("Test8Base.foo()");
}
}
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/162-method-resolution/src/Test9Base.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/162-method-resolution/src/Test9Base.java
index 023e409..85ec79b 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/162-method-resolution/src/Test9Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
+public class Test9Base {
+ public void foo() {
+ System.out.println("Test9Base.foo()");
}
}
diff --git a/test/1910-transform-with-default/expected.txt b/test/1910-transform-with-default/expected.txt
new file mode 100644
index 0000000..f43ef61
--- /dev/null
+++ b/test/1910-transform-with-default/expected.txt
@@ -0,0 +1,4 @@
+hello
+hello
+Goodbye
+Goodbye
diff --git a/test/1910-transform-with-default/info.txt b/test/1910-transform-with-default/info.txt
new file mode 100644
index 0000000..96ebddd
--- /dev/null
+++ b/test/1910-transform-with-default/info.txt
@@ -0,0 +1,4 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that we we can redefine classes that have default methods inherited from
+interfaces.
diff --git a/test/1910-transform-with-default/run b/test/1910-transform-with-default/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/1910-transform-with-default/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --jvmti
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/1910-transform-with-default/src/Main.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/1910-transform-with-default/src/Main.java
index 023e409..fd8b3c7 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/1910-transform-with-default/src/Main.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1910.run();
+ }
}
diff --git a/test/1910-transform-with-default/src/art/Redefinition.java b/test/1910-transform-with-default/src/art/Redefinition.java
new file mode 100644
index 0000000..56d2938
--- /dev/null
+++ b/test/1910-transform-with-default/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+ public static final class CommonClassDefinition {
+ public final Class<?> target;
+ public final byte[] class_file_bytes;
+ public final byte[] dex_file_bytes;
+
+ public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+ this.target = target;
+ this.class_file_bytes = class_file_bytes;
+ this.dex_file_bytes = dex_file_bytes;
+ }
+ }
+
+ // A set of possible test configurations. Test should set this if they need to.
+ // This must be kept in sync with the defines in ti-agent/common_helper.cc
+ public static enum Config {
+ COMMON_REDEFINE(0),
+ COMMON_RETRANSFORM(1),
+ COMMON_TRANSFORM(2);
+
+ private final int val;
+ private Config(int val) {
+ this.val = val;
+ }
+ }
+
+ public static void setTestConfiguration(Config type) {
+ nativeSetTestConfiguration(type.val);
+ }
+
+ private static native void nativeSetTestConfiguration(int type);
+
+ // Transforms the class
+ public static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+
+ public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+ ArrayList<Class<?>> classes = new ArrayList<>();
+ ArrayList<byte[]> class_files = new ArrayList<>();
+ ArrayList<byte[]> dex_files = new ArrayList<>();
+
+ for (CommonClassDefinition d : defs) {
+ classes.add(d.target);
+ class_files.add(d.class_file_bytes);
+ dex_files.add(d.dex_file_bytes);
+ }
+ doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+ class_files.toArray(new byte[0][]),
+ dex_files.toArray(new byte[0][]));
+ }
+
+ public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+ for (CommonClassDefinition d : defs) {
+ addCommonTransformationResult(d.target.getCanonicalName(),
+ d.class_file_bytes,
+ d.dex_file_bytes);
+ }
+ }
+
+ public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+ byte[][] classfiles,
+ byte[][] dexfiles);
+ public static native void doCommonClassRetransformation(Class<?>... target);
+ public static native void setPopRetransformations(boolean pop);
+ public static native void popTransformationFor(String name);
+ public static native void enableCommonRetransformation(boolean enable);
+ public static native void addCommonTransformationResult(String target_name,
+ byte[] class_bytes,
+ byte[] dex_bytes);
+}
diff --git a/test/1910-transform-with-default/src/art/Test1910.java b/test/1910-transform-with-default/src/art/Test1910.java
new file mode 100644
index 0000000..775fe63
--- /dev/null
+++ b/test/1910-transform-with-default/src/art/Test1910.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test1910 {
+ static interface TestInterface {
+ public void sayHi();
+ public default void sayHiTwice() {
+ sayHi();
+ sayHi();
+ }
+ }
+
+ static class Transform implements TestInterface {
+ public void sayHi() {
+ System.out.println("hello");
+ }
+ }
+
+ /**
+ * base64 encoded class/dex file for
+ * class Transform implements TestInterface {
+ * public void sayHi() {
+ * System.out.println("Goodbye");
+ * }
+ * }
+ */
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAIwoABgAPCQAQABEIABIKABMAFAcAFgcAGQcAGgEABjxpbml0PgEAAygpVgEABENv" +
+ "ZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA1UZXN0MTkxMC5qYXZh" +
+ "DAAIAAkHABwMAB0AHgEAB0dvb2RieWUHAB8MACAAIQcAIgEAFmFydC9UZXN0MTkxMCRUcmFuc2Zv" +
+ "cm0BAAlUcmFuc2Zvcm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQAaYXJ0L1Rl" +
+ "c3QxOTEwJFRlc3RJbnRlcmZhY2UBAA1UZXN0SW50ZXJmYWNlAQAQamF2YS9sYW5nL1N5c3RlbQEA" +
+ "A291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmlu" +
+ "dGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAMYXJ0L1Rlc3QxOTEwACAABQAGAAEABwAAAAIA" +
+ "AAAIAAkAAQAKAAAAHQABAAEAAAAFKrcAAbEAAAABAAsAAAAGAAEAAAAdAAEADAAJAAEACgAAACUA" +
+ "AgABAAAACbIAAhIDtgAEsQAAAAEACwAAAAoAAgAAAB8ACAAgAAIADQAAAAIADgAYAAAAEgACAAUA" +
+ "FQAXAAgABwAVABsGCA==");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQCimuj5gqsyBEhWaMcfKWwG9eiBycoK3JfcAwAAcAAAAHhWNBIAAAAAAAAAABgDAAAV" +
+ "AAAAcAAAAAoAAADEAAAAAgAAAOwAAAABAAAABAEAAAQAAAAMAQAAAQAAACwBAACQAgAATAEAAK4B" +
+ "AAC2AQAAvwEAAN0BAAD3AQAABwIAACsCAABLAgAAYgIAAHYCAACKAgAAngIAAK0CAAC4AgAAuwIA" +
+ "AL8CAADMAgAA0gIAANcCAADgAgAA5wIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" +
+ "CgAAAA0AAAANAAAACQAAAAAAAAAOAAAACQAAAKgBAAAIAAUAEQAAAAEAAAAAAAAAAQAAABMAAAAF" +
+ "AAEAEgAAAAYAAAAAAAAAAQAAAAAAAAAGAAAAoAEAAAsAAACQAQAACAMAAAAAAAACAAAA+QIAAP8C" +
+ "AAABAAEAAQAAAO4CAAAEAAAAcBADAAAADgADAAEAAgAAAPMCAAAIAAAAYgAAABoBAQBuIAIAEAAO" +
+ "AEwBAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAHAAY8aW5pdD4AB0dvb2RieWUAHExhcnQvVGVz" +
+ "dDE5MTAkVGVzdEludGVyZmFjZTsAGExhcnQvVGVzdDE5MTAkVHJhbnNmb3JtOwAOTGFydC9UZXN0" +
+ "MTkxMDsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3Rh" +
+ "dGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVj" +
+ "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA1UZXN0MTkxMC5qYXZh" +
+ "AAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxuAAVzYXlI" +
+ "aQAFdmFsdWUAHQAHDgAfAAcOeAACAwEUGAICBAIPBAgQFwwAAAEBAICABNgCAQHwAgAAEAAAAAAA" +
+ "AAABAAAAAAAAAAEAAAAVAAAAcAAAAAIAAAAKAAAAxAAAAAMAAAACAAAA7AAAAAQAAAABAAAABAEA" +
+ "AAUAAAAEAAAADAEAAAYAAAABAAAALAEAAAMQAAABAAAATAEAAAEgAAACAAAAWAEAAAYgAAABAAAA" +
+ "kAEAAAEQAAACAAAAoAEAAAIgAAAVAAAArgEAAAMgAAACAAAA7gIAAAQgAAACAAAA+QIAAAAgAAAB" +
+ "AAAACAMAAAAQAAABAAAAGAMAAA==");
+
+ public static void run() {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest(new Transform());
+ }
+
+ public static void doTest(TestInterface t) {
+ t.sayHiTwice();
+ Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+ t.sayHiTwice();
+ }
+}
diff --git a/test/497-inlining-and-class-loader/clear_dex_cache.cc b/test/497-inlining-and-class-loader/clear_dex_cache.cc
index 9ba05bc..c113042 100644
--- a/test/497-inlining-and-class-loader/clear_dex_cache.cc
+++ b/test/497-inlining-and-class-loader/clear_dex_cache.cc
@@ -34,22 +34,32 @@
ScopedObjectAccess soa(Thread::Current());
mirror::DexCache* dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
size_t num_methods = dex_cache->NumResolvedMethods();
- ArtMethod** methods = dex_cache->GetResolvedMethods();
+ mirror::MethodDexCacheType* methods = dex_cache->GetResolvedMethods();
CHECK_EQ(num_methods != 0u, methods != nullptr);
if (num_methods == 0u) {
return nullptr;
}
jarray array;
if (sizeof(void*) == 4) {
- array = env->NewIntArray(num_methods);
+ array = env->NewIntArray(2u * num_methods);
} else {
- array = env->NewLongArray(num_methods);
+ array = env->NewLongArray(2u * num_methods);
}
CHECK(array != nullptr);
- mirror::PointerArray* pointer_array = soa.Decode<mirror::PointerArray>(array).Ptr();
+ ObjPtr<mirror::Array> decoded_array = soa.Decode<mirror::Array>(array);
for (size_t i = 0; i != num_methods; ++i) {
- ArtMethod* method = mirror::DexCache::GetElementPtrSize(methods, i, kRuntimePointerSize);
- pointer_array->SetElementPtrSize(i, method, kRuntimePointerSize);
+ auto pair = mirror::DexCache::GetNativePairPtrSize(methods, i, kRuntimePointerSize);
+ uint32_t index = pair.index;
+ ArtMethod* method = pair.object;
+ if (sizeof(void*) == 4) {
+ ObjPtr<mirror::IntArray> int_array = down_cast<mirror::IntArray*>(decoded_array.Ptr());
+ int_array->Set(2u * i, index);
+ int_array->Set(2u * i + 1u, static_cast<jint>(reinterpret_cast<uintptr_t>(method)));
+ } else {
+ ObjPtr<mirror::LongArray> long_array = down_cast<mirror::LongArray*>(decoded_array.Ptr());
+ long_array->Set(2u * i, index);
+ long_array->Set(2u * i + 1u, reinterpret_cast64<jlong>(method));
+ }
}
return array;
}
@@ -59,14 +69,26 @@
ScopedObjectAccess soa(Thread::Current());
mirror::DexCache* dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
size_t num_methods = dex_cache->NumResolvedMethods();
- ArtMethod** methods = soa.Decode<mirror::Class>(cls)->GetDexCache()->GetResolvedMethods();
+ mirror::MethodDexCacheType* methods =
+ soa.Decode<mirror::Class>(cls)->GetDexCache()->GetResolvedMethods();
CHECK_EQ(num_methods != 0u, methods != nullptr);
- ObjPtr<mirror::PointerArray> old = soa.Decode<mirror::PointerArray>(old_cache);
+ ObjPtr<mirror::Array> old = soa.Decode<mirror::Array>(old_cache);
CHECK_EQ(methods != nullptr, old != nullptr);
CHECK_EQ(num_methods, static_cast<size_t>(old->GetLength()));
for (size_t i = 0; i != num_methods; ++i) {
- ArtMethod* method = old->GetElementPtrSize<ArtMethod*>(i, kRuntimePointerSize);
- mirror::DexCache::SetElementPtrSize(methods, i, method, kRuntimePointerSize);
+ uint32_t index;
+ ArtMethod* method;
+ if (sizeof(void*) == 4) {
+ ObjPtr<mirror::IntArray> int_array = down_cast<mirror::IntArray*>(old.Ptr());
+ index = static_cast<uint32_t>(int_array->Get(2u * i));
+ method = reinterpret_cast<ArtMethod*>(static_cast<uint32_t>(int_array->Get(2u * i + 1u)));
+ } else {
+ ObjPtr<mirror::LongArray> long_array = down_cast<mirror::LongArray*>(old.Ptr());
+ index = dchecked_integral_cast<uint32_t>(long_array->Get(2u * i));
+ method = reinterpret_cast64<ArtMethod*>(long_array->Get(2u * i + 1u));
+ }
+ mirror::MethodDexCachePair pair(method, index);
+ mirror::DexCache::SetNativePairPtrSize(methods, i, pair, kRuntimePointerSize);
}
}
diff --git a/test/551-checker-shifter-operand/src/Main.java b/test/551-checker-shifter-operand/src/Main.java
index 951889a..3177ec0 100644
--- a/test/551-checker-shifter-operand/src/Main.java
+++ b/test/551-checker-shifter-operand/src/Main.java
@@ -327,6 +327,7 @@
*/
/// CHECK-START-ARM: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
/// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm64 (after)
@@ -399,6 +400,8 @@
}
/// CHECK-START-ARM: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
/// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm64 (after)
@@ -469,6 +472,8 @@
}
/// CHECK-START-ARM: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm (after)
+ /// CHECK: DataProcWithShifterOp
+ /// CHECK: DataProcWithShifterOp
/// CHECK-NOT: DataProcWithShifterOp
/// CHECK-START-ARM64: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm64 (after)
diff --git a/test/660-clinit/expected.txt b/test/660-clinit/expected.txt
new file mode 100644
index 0000000..e103a2c
--- /dev/null
+++ b/test/660-clinit/expected.txt
@@ -0,0 +1,9 @@
+JNI_OnLoad called
+X: 4950
+Y: 5730
+str: Hello World!
+ooo: OoooooO
+Z: 11206655
+A: 100
+AA: 100
+a != 101
diff --git a/test/660-clinit/info.txt b/test/660-clinit/info.txt
new file mode 100644
index 0000000..da0193d
--- /dev/null
+++ b/test/660-clinit/info.txt
@@ -0,0 +1 @@
+Tests that class initializers are executed correctly.
diff --git a/test/660-clinit/src/Main.java b/test/660-clinit/src/Main.java
new file mode 100644
index 0000000..f547692
--- /dev/null
+++ b/test/660-clinit/src/Main.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 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.
+ */
+
+import java.util.*;
+
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ if (!checkAppImageLoaded()) {
+ System.out.println("AppImage not loaded.");
+ }
+
+ expectNotPreInit(Day.class);
+ expectNotPreInit(ClInit.class);
+
+ ClInit c = new ClInit();
+ int aa = c.a;
+
+ System.out.println("X: " + c.getX());
+ System.out.println("Y: " + c.getY());
+ System.out.println("str: " + c.str);
+ System.out.println("ooo: " + c.ooo);
+ System.out.println("Z: " + c.getZ());
+ System.out.println("A: " + c.getA());
+ System.out.println("AA: " + aa);
+
+ if (c.a != 101) {
+ System.out.println("a != 101");
+ }
+
+ return;
+ }
+
+ static void expectPreInit(Class<?> klass) {
+ if (checkInitialized(klass) == false) {
+ System.out.println(klass.getName() + " should be initialized!");
+ }
+ }
+
+ static void expectNotPreInit(Class<?> klass) {
+ if (checkInitialized(klass) == true) {
+ System.out.println(klass.getName() + " should not be initialized!");
+ }
+ }
+
+ public static native boolean checkAppImageLoaded();
+ public static native boolean checkAppImageContains(Class<?> klass);
+ public static native boolean checkInitialized(Class<?> klass);
+}
+
+enum Day {
+ SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
+ THURSDAY, FRIDAY, SATURDAY
+}
+
+class ClInit {
+
+ static String ooo = "OoooooO";
+ static String str;
+ static int z;
+ static int x, y;
+ public static volatile int a = 100;
+
+ static {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello ");
+ sb.append("World!");
+ str = sb.toString();
+
+ z = 0xFF;
+ z += 0xFF00;
+ z += 0xAA0000;
+
+ for(int i = 0; i < 100; i++) {
+ x += i;
+ }
+
+ y = x;
+ for(int i = 0; i < 40; i++) {
+ y += i;
+ }
+ }
+
+ int getX() {
+ return x;
+ }
+
+ int getZ() {
+ return z;
+ }
+
+ int getY() {
+ return y;
+ }
+
+ int getA() {
+ return a;
+ }
+}
+
diff --git a/test/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt
index fdbfbe2..f36d1a3 100644
--- a/test/921-hello-failure/expected.txt
+++ b/test/921-hello-failure/expected.txt
@@ -53,3 +53,6 @@
hello - Unmodifiable
Transformation error : java.lang.Exception(Failed to redefine class <[LTransform;> due to JVMTI_ERROR_UNMODIFIABLE_CLASS)
hello - Unmodifiable
+hello - Undefault
+Transformation error : java.lang.Exception(Failed to redefine class <LTransform5;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED)
+hello - Undefault
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/921-hello-failure/src/Iface4.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/921-hello-failure/src/Iface4.java
index 023e409..66804c2 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/921-hello-failure/src/Iface4.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,10 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+interface Iface4 {
+ default void sayHiTwice(String s) {
+ sayHi(s);
+ sayHi(s);
+ }
+ void sayHi(String s);
}
diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java
index cfdcdc2..fb481bd 100644
--- a/test/921-hello-failure/src/Main.java
+++ b/test/921-hello-failure/src/Main.java
@@ -35,6 +35,7 @@
MissingField.doTest(new Transform4("there"));
FieldChange.doTest(new Transform4("there again"));
Unmodifiable.doTest(new Transform[] { new Transform(), });
+ Undefault.doTest(new Transform5());
}
// TODO Replace this shim with a better re-write of this test.
diff --git a/test/066-mismatched-super/src/Indirect.java b/test/921-hello-failure/src/Transform5.java
similarity index 65%
copy from test/066-mismatched-super/src/Indirect.java
copy to test/921-hello-failure/src/Transform5.java
index 023e409..cf7b20a 100644
--- a/test/066-mismatched-super/src/Indirect.java
+++ b/test/921-hello-failure/src/Transform5.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-/**
- * Error indirection class.
- *
- * Some VMs will load this class and fail on the "new" call, others will
- * refuse to load this class at all.
- */
-public class Indirect {
- public static void main() {
- Base base = new Base();
- }
+class Transform5 implements Iface4 {
+ public void sayHi(String name) {
+ System.out.println("hello - " + name);
+ }
}
diff --git a/test/921-hello-failure/src/Undefault.java b/test/921-hello-failure/src/Undefault.java
new file mode 100644
index 0000000..8303a84
--- /dev/null
+++ b/test/921-hello-failure/src/Undefault.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+import java.util.Base64;
+
+class Undefault {
+ // The following is a base64 encoding of the following class.
+ // class Transform5 implements Iface4 {
+ // public void sayHiTwice(String s) {
+ // throw new Error("Should not be called");
+ // }
+ // public void sayHi(String name) {
+ // throw new Error("Should not be called!");
+ // }
+ // }
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAGgoABwASBwATCAAUCgACABUIABYHABcHABgHABkBAAY8aW5pdD4BAAMoKVYBAARD" +
+ "b2RlAQAPTGluZU51bWJlclRhYmxlAQAKc2F5SGlUd2ljZQEAFShMamF2YS9sYW5nL1N0cmluZzsp" +
+ "VgEABXNheUhpAQAKU291cmNlRmlsZQEAD1RyYW5zZm9ybTUuamF2YQwACQAKAQAPamF2YS9sYW5n" +
+ "L0Vycm9yAQAUU2hvdWxkIG5vdCBiZSBjYWxsZWQMAAkADgEAFVNob3VsZCBub3QgYmUgY2FsbGVk" +
+ "IQEAClRyYW5zZm9ybTUBABBqYXZhL2xhbmcvT2JqZWN0AQAGSWZhY2U0ACAABgAHAAEACAAAAAMA" +
+ "AAAJAAoAAQALAAAAHQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAABAAEADQAOAAEACwAAACIA" +
+ "AwACAAAACrsAAlkSA7cABL8AAAABAAwAAAAGAAEAAAADAAEADwAOAAEACwAAACIAAwACAAAACrsA" +
+ "AlkSBbcABL8AAAABAAwAAAAGAAEAAAAGAAEAEAAAAAIAEQ==");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQD5XbJiwMAcY0cucJ5gcVhFu7tMG0dZX8PsAgAAcAAAAHhWNBIAAAAAAAAAAFgCAAAN" +
+ "AAAAcAAAAAYAAACkAAAAAgAAALwAAAAAAAAAAAAAAAUAAADUAAAAAQAAAPwAAADQAQAAHAEAAIIB" +
+ "AACKAQAAlAEAAKIBAAC1AQAAyQEAAN0BAADzAQAACgIAABsCAAAeAgAAIgIAACkCAAABAAAAAgAA" +
+ "AAMAAAAEAAAABQAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAHwBAAABAAAAAAAAAAEAAQALAAAA" +
+ "AQABAAwAAAACAAEAAAAAAAMAAAAAAAAAAQAAAAAAAAADAAAAdAEAAAgAAAAAAAAARgIAAAAAAAAB" +
+ "AAEAAQAAADUCAAAEAAAAcBAEAAAADgAEAAIAAgAAADoCAAAIAAAAIgACABoBBwBwIAMAEAAnAAQA" +
+ "AgACAAAAQAIAAAgAAAAiAAIAGgEGAHAgAwAQACcAAQAAAAAAAAABAAAABAAGPGluaXQ+AAhMSWZh" +
+ "Y2U0OwAMTFRyYW5zZm9ybTU7ABFMamF2YS9sYW5nL0Vycm9yOwASTGphdmEvbGFuZy9PYmplY3Q7" +
+ "ABJMamF2YS9sYW5nL1N0cmluZzsAFFNob3VsZCBub3QgYmUgY2FsbGVkABVTaG91bGQgbm90IGJl" +
+ "IGNhbGxlZCEAD1RyYW5zZm9ybTUuamF2YQABVgACVkwABXNheUhpAApzYXlIaVR3aWNlAAEABw4A" +
+ "BgEABw4AAwEABw4AAAABAgCAgAScAgEBtAIBAdQCDAAAAAAAAAABAAAAAAAAAAEAAAANAAAAcAAA" +
+ "AAIAAAAGAAAApAAAAAMAAAACAAAAvAAAAAUAAAAFAAAA1AAAAAYAAAABAAAA/AAAAAEgAAADAAAA" +
+ "HAEAAAEQAAACAAAAdAEAAAIgAAANAAAAggEAAAMgAAADAAAANQIAAAAgAAABAAAARgIAAAAQAAAB" +
+ "AAAAWAIAAA==");
+
+ public static void doTest(Transform5 t) {
+ t.sayHi("Undefault");
+ try {
+ Main.doCommonClassRedefinition(Transform5.class, CLASS_BYTES, DEX_BYTES);
+ } catch (Exception e) {
+ System.out.println(
+ "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")");
+ }
+ t.sayHi("Undefault");
+ }
+}
diff --git a/test/924-threads/src/art/Test924.java b/test/924-threads/src/art/Test924.java
index 84b7c62..b73eb30 100644
--- a/test/924-threads/src/art/Test924.java
+++ b/test/924-threads/src/art/Test924.java
@@ -164,8 +164,10 @@
do {
Thread.yield();
} while (t.getState() != Thread.State.BLOCKED);
- Thread.sleep(10);
- printThreadState(t);
+ // Since internal thread suspension (For GC or other cases) can happen at any time and changes
+ // the thread state we just have it print the majority thread state across 11 calls over 55
+ // milliseconds.
+ printMajorityThreadState(t, 11, 5);
}
// Sleeping.
@@ -357,10 +359,32 @@
STATE_KEYS.addAll(STATE_NAMES.keySet());
Collections.sort(STATE_KEYS);
}
-
- private static void printThreadState(Thread t) {
- int state = getThreadState(t);
+ // Call getThreadState 'votes' times waiting 'wait' millis between calls and print the most common
+ // result.
+ private static void printMajorityThreadState(Thread t, int votes, int wait) throws Exception {
+ Map<Integer, Integer> states = new HashMap<>();
+ for (int i = 0; i < votes; i++) {
+ int cur_state = getThreadState(t);
+ states.put(cur_state, states.getOrDefault(cur_state, 0) + 1);
+ Thread.sleep(wait); // Wait a little bit.
+ }
+ int best_state = -1;
+ int highest_count = 0;
+ for (Map.Entry<Integer, Integer> e : states.entrySet()) {
+ if (e.getValue() > highest_count) {
+ highest_count = e.getValue();
+ best_state = e.getKey();
+ }
+ }
+ printThreadState(best_state);
+ }
+
+ private static void printThreadState(Thread t) {
+ printThreadState(getThreadState(t));
+ }
+
+ private static void printThreadState(int state) {
StringBuilder sb = new StringBuilder();
for (Integer i : STATE_KEYS) {
diff --git a/test/VerifierDeps/Main.smali b/test/VerifierDeps/Main.smali
index 74c0d03..824f0dc 100644
--- a/test/VerifierDeps/Main.smali
+++ b/test/VerifierDeps/Main.smali
@@ -405,12 +405,6 @@
return-void
.end method
-.method public static InvokeVirtual_ActuallyDirect(LMyThread;)V
- .registers 1
- invoke-virtual {p0}, LMyThread;->activeCount()I
- return-void
-.end method
-
.method public static InvokeInterface_Resolved_DeclaredInReferenced(LMyThread;)V
.registers 1
invoke-interface {p0}, Ljava/lang/Runnable;->run()V
@@ -420,7 +414,9 @@
.method public static InvokeInterface_Resolved_DeclaredInSuperclass(LMyThread;)V
.registers 1
# Method join() is declared in the superclass of MyThread. As such, it should
- # be called with invoke-virtual and will not be resolved here.
+ # be called with invoke-virtual. However, the lookup type does not depend
+ # on the invoke type, so it shall be resolved here anyway.
+ # TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
invoke-interface {p0}, LMyThread;->join()V
return-void
.end method
@@ -428,6 +424,8 @@
.method public static InvokeInterface_Resolved_DeclaredInSuperinterface1(LMyThreadSet;)V
.registers 1
# Verification will fail because the referring class is not an interface.
+ # However, the lookup type does not depend on the invoke type, so it shall be resolved here anyway.
+ # TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
invoke-interface {p0}, LMyThreadSet;->run()V
return-void
.end method
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 3edb0a8..53611a8 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -600,15 +600,6 @@
},
{
"tests": [
- "567-checker-compare",
- "988-method-trace"
- ],
- "description": "Checker tests fail because desugar lowers Long.compare to lcmp",
- "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"},
- "bug": "b/63078894"
- },
- {
- "tests": [
"536-checker-needs-access-check",
"537-checker-inline-and-unverified",
"569-checker-pattern-replacement",
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index a0c4ea8..6596ff4 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -233,8 +233,8 @@
HOST_OUT_EXECUTABLES = os.path.join(ANDROID_BUILD_TOP,
_get_build_var("HOST_OUT_EXECUTABLES"))
-os.environ['JACK'] = HOST_OUT_EXECUTABLES + '/jack'
-os.environ['DX'] = HOST_OUT_EXECUTABLES + '/dx'
-os.environ['SMALI'] = HOST_OUT_EXECUTABLES + '/smali'
-os.environ['JASMIN'] = HOST_OUT_EXECUTABLES + '/jasmin'
-os.environ['DXMERGER'] = HOST_OUT_EXECUTABLES + '/dexmerger'
+
+# Set up default values for $JACK, $DX, $SMALI, etc to the $HOST_OUT_EXECUTABLES/$name path.
+for tool in ['jack', 'dx', 'smali', 'jasmin', 'dxmerger']:
+ binary = tool if tool != 'dxmerger' else 'dexmerger'
+ os.environ.setdefault(tool.upper(), HOST_OUT_EXECUTABLES + '/' + binary)
diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py
index baf7600..e8b6f1c 100644
--- a/test/testrunner/target_config.py
+++ b/test/testrunner/target_config.py
@@ -303,7 +303,8 @@
}
},
'art-gtest-valgrind32': {
- 'make' : 'valgrind-test-art-host32',
+ # Disabled: x86 valgrind does not understand SSE4.x
+ # 'make' : 'valgrind-test-art-host32',
'env': {
'ART_USE_READ_BARRIER' : 'false'
}
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index cc55b7a..8262910 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -110,7 +110,7 @@
private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
doc.section("Fields");
AhatInstance base = inst.getBaseline();
- printFields(doc, query, INSTANCE_FIELDS_ID, !base.isPlaceHolder(),
+ printFields(doc, query, INSTANCE_FIELDS_ID, inst != base && !base.isPlaceHolder(),
inst.asClassInstance().getInstanceFields(),
base.isPlaceHolder() ? null : base.asClassInstance().getInstanceFields());
}
@@ -211,7 +211,7 @@
doc.section("Static Fields");
AhatInstance base = clsobj.getBaseline();
- printFields(doc, query, STATIC_FIELDS_ID, !base.isPlaceHolder(),
+ printFields(doc, query, STATIC_FIELDS_ID, clsobj != base && !base.isPlaceHolder(),
clsobj.getStaticFieldValues(),
base.isPlaceHolder() ? null : base.asClassObj().getStaticFieldValues());
}
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index 86d48f1..fd226c2 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -43,13 +43,7 @@
Site site = mSnapshot.getSite(id, depth);
List<AhatInstance> insts = new ArrayList<AhatInstance>();
- for (AhatInstance inst : site.getObjects()) {
- if ((heapName == null || inst.getHeap().getName().equals(heapName))
- && (className == null || inst.getClassName().equals(className))) {
- insts.add(inst);
- }
- }
-
+ site.getObjects(heapName, className, insts);
Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot));
doc.title("Objects");
diff --git a/tools/ahat/src/Summarizer.java b/tools/ahat/src/Summarizer.java
index 016eab4..3e9da31 100644
--- a/tools/ahat/src/Summarizer.java
+++ b/tools/ahat/src/Summarizer.java
@@ -60,14 +60,7 @@
formatted.append("root ");
}
- // Annotate classes as classes.
- DocString linkText = new DocString();
- if (inst.isClassObj()) {
- linkText.append("class ");
- }
-
- linkText.append(inst.toString());
-
+ DocString linkText = DocString.text(inst.toString());
if (inst.isPlaceHolder()) {
// Don't make links to placeholder objects.
formatted.append(linkText);
diff --git a/tools/ahat/src/dominators/DominatorsComputation.java b/tools/ahat/src/dominators/DominatorsComputation.java
new file mode 100644
index 0000000..9a2a272
--- /dev/null
+++ b/tools/ahat/src/dominators/DominatorsComputation.java
@@ -0,0 +1,260 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.dominators;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Generic DominatorsComputation.
+ *
+ * To use the dominators computation, have your graph nodes implement the
+ * DominatorsComputation.Node interface, then call
+ * DominatorsComputation.computeDominators on the single root node.
+ */
+public class DominatorsComputation {
+ /**
+ * Interface for a directed graph to perform the dominators computation on.
+ */
+ public interface Node {
+ /**
+ * Associate the given dominator state with this node.
+ */
+ void setDominatorsComputationState(Object state);
+
+ /**
+ * Get the most recent dominator state associated with this node using
+ * setDominatorsComputationState. If setDominatorsComputationState has not
+ * yet been called, this should return null.
+ */
+ Object getDominatorsComputationState();
+
+ /**
+ * Return a collection of nodes referenced from this node, for the
+ * purposes of computing dominators.
+ */
+ Iterable<? extends Node> getReferencesForDominators();
+
+ /**
+ * Update this node's dominator based on the results of the dominators
+ * computation.
+ */
+ void setDominator(Node dominator);
+ }
+
+ // NodeS is information associated with a particular node for the
+ // purposes of computing dominators.
+ // By convention we use the suffix 'S' to name instances of NodeS.
+ private static class NodeS {
+ // The node that this NodeS is associated with.
+ public Node node;
+
+ // Unique identifier for this node, in increasing order based on the order
+ // this node was visited in a depth first search from the root. In
+ // particular, given nodes A and B, if A.id > B.id, then A cannot be a
+ // dominator of B.
+ public long id;
+
+ // Upper bound on the id of this node's dominator.
+ // The true immediate dominator of this node must have id <= domid.
+ // This upper bound is slowly tightened as part of the dominators
+ // computation.
+ public long domid;
+
+ // The current candidate dominator for this node.
+ // Invariant: (domid < domS.id) implies this node is on the queue of
+ // nodes to be revisited.
+ public NodeS domS;
+
+ // A node with a reference to this node that is one step closer to the
+ // root than this node.
+ // Invariant: srcS.id < this.id
+ public NodeS srcS;
+
+ // The set of nodes X reachable by 'this' on a path of nodes from the
+ // root with increasing ids (possibly excluding X) that this node does not
+ // dominate (this.id > X.domid).
+ // We can use a List instead of a Set for this because we guarentee that
+ // we don't add the same node more than once to the list (see below).
+ public List<NodeS> undom = new ArrayList<NodeS>();
+
+ // The largest id of the node X for which we did X.undom.add(this).
+ // This is an optimization to avoid adding duplicate node entries to the
+ // undom set.
+ //
+ // The first time we see this node, we reach it through a path of nodes
+ // with IDs 0,...,a,this. These form our src chain to the root.
+ //
+ // The next time we see this node, we reach it through a path of nodes
+ // with IDS 0,...,b,c,...,d,this. Where all 0,...,b < a and all c,...,d > a.
+ //
+ // The next time we see this node, we reach it through a path of nodes
+ // with IDS 0,...,e,f,...,g,this. With all 0,...,e < d and all f,...,g > d.
+ // And so on.
+ //
+ // The first time we see this node, we set undomid to a.id. Nodes 0,...,a
+ // will be added as undom in the 'revisit' phase of the node.
+ //
+ // The next times we see this node, we mark a+,...,d as undom and
+ // change undomid to d. And so on.
+ public long undomid;
+ }
+
+ private static class Link {
+ public NodeS srcS;
+ public Node dst;
+
+ public Link(NodeS srcS, Node dst) {
+ this.srcS = srcS;
+ this.dst = dst;
+ }
+ }
+
+ /**
+ * Compute the dominator tree rooted at the given node.
+ * There must not be any incoming references to the root node.
+ */
+ public static void computeDominators(Node root) {
+ long id = 0;
+
+ // List of all nodes seen. We keep track of this here to update all the
+ // dominators once we are done.
+ List<NodeS> nodes = new ArrayList<NodeS>();
+
+ // The set of nodes N such that N.domid < N.domS.id. These nodes need
+ // to be revisisted because their dominator is clearly wrong.
+ // Use a Queue instead of a Set because performance will be better. We
+ // avoid adding nodes already on the queue by checking whether it was
+ // already true that N.domid < N.domS.id, in which case the node is
+ // already on the queue.
+ Queue<NodeS> revisit = new ArrayDeque<NodeS>();
+
+ // Set up the root node specially.
+ NodeS rootS = new NodeS();
+ rootS.node = root;
+ rootS.id = id++;
+ root.setDominatorsComputationState(rootS);
+
+ // 1. Do a depth first search of the nodes, label them with ids and come
+ // up with intial candidate dominators for them.
+ Deque<Link> dfs = new ArrayDeque<Link>();
+ for (Node child : root.getReferencesForDominators()) {
+ dfs.push(new Link(rootS, child));
+ }
+
+ while (!dfs.isEmpty()) {
+ Link link = dfs.pop();
+ NodeS dstS = (NodeS)link.dst.getDominatorsComputationState();
+ if (dstS == null) {
+ // This is the first time we have seen the node. The candidate
+ // dominator is link src.
+ dstS = new NodeS();
+ dstS.node = link.dst;
+ dstS.id = id++;
+ dstS.domid = link.srcS.id;
+ dstS.domS = link.srcS;
+ dstS.srcS = link.srcS;
+ dstS.undomid = dstS.domid;
+ nodes.add(dstS);
+ link.dst.setDominatorsComputationState(dstS);
+
+ for (Node child : link.dst.getReferencesForDominators()) {
+ dfs.push(new Link(dstS, child));
+ }
+ } else {
+ // We have seen the node already. Update the state based on the new
+ // potential dominator.
+ NodeS srcS = link.srcS;
+ boolean revisiting = dstS.domid < dstS.domS.id;
+
+ while (srcS.id > dstS.domid) {
+ if (srcS.id > dstS.undomid) {
+ srcS.undom.add(dstS);
+ }
+ srcS = srcS.srcS;
+ }
+ dstS.undomid = link.srcS.id;
+
+ if (srcS.id < dstS.domid) {
+ // In this case, dstS.domid must be wrong, because we just found a
+ // path to dstS that does not go through dstS.domid:
+ // All nodes from root to srcS have id < domid, and all nodes from
+ // srcS to dstS had id > domid, so dstS.domid cannot be on this path
+ // from root to dstS.
+ dstS.domid = srcS.id;
+ if (!revisiting) {
+ revisit.add(dstS);
+ }
+ }
+ }
+ }
+
+ // 2. Continue revisiting nodes until they all satisfy the requirement
+ // that domS.id <= domid.
+ while (!revisit.isEmpty()) {
+ NodeS nodeS = revisit.poll();
+ NodeS domS = nodeS.domS;
+ assert nodeS.domid < domS.id;
+ while (domS.id > nodeS.domid) {
+ if (domS.domS.id < nodeS.domid) {
+ // In this case, nodeS.domid must be wrong, because there is a path
+ // from root to nodeS that does not go through nodeS.domid:
+ // * We can go from root to domS without going through nodeS.domid,
+ // because otherwise nodeS.domid would dominate domS, not
+ // domS.domS.
+ // * We can go from domS to nodeS without going through nodeS.domid
+ // because we know nodeS is reachable from domS on a path of nodes
+ // with increases ids, which cannot include nodeS.domid, which
+ // has a smaller id than domS.
+ nodeS.domid = domS.domS.id;
+ }
+ domS.undom.add(nodeS);
+ domS = domS.srcS;
+ }
+ nodeS.domS = domS;
+ nodeS.domid = domS.id;
+
+ for (NodeS xS : nodeS.undom) {
+ if (domS.id < xS.domid) {
+ // In this case, xS.domid must be wrong, because there is a path
+ // from the root to xX that does not go through xS.domid:
+ // * We can go from root to nodeS without going through xS.domid,
+ // because otherwise xS.domid would dominate nodeS, not domS.
+ // * We can go from nodeS to xS without going through xS.domid
+ // because we know xS is reachable from nodeS on a path of nodes
+ // with increasing ids, which cannot include xS.domid, which has
+ // a smaller id than nodeS.
+ boolean revisiting = xS.domid < xS.domS.id;
+ xS.domid = domS.id;
+ if (!revisiting) {
+ revisit.add(xS);
+ }
+ }
+ }
+ }
+
+ // 3. Update the dominators of the nodes.
+ root.setDominatorsComputationState(null);
+ for (NodeS nodeS : nodes) {
+ nodeS.node.setDominator(nodeS.domS.node);
+ nodeS.node.setDominatorsComputationState(null);
+ }
+ }
+}
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
index d88cf94..6d4485d 100644
--- a/tools/ahat/src/heapdump/AhatArrayInstance.java
+++ b/tools/ahat/src/heapdump/AhatArrayInstance.java
@@ -20,6 +20,7 @@
import com.android.tools.perflib.heap.Instance;
import java.nio.charset.StandardCharsets;
import java.util.AbstractList;
+import java.util.Collections;
import java.util.List;
public class AhatArrayInstance extends AhatInstance {
@@ -37,8 +38,8 @@
super(id);
}
- @Override void initialize(AhatSnapshot snapshot, Instance inst) {
- super.initialize(snapshot, inst);
+ @Override void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
+ super.initialize(snapshot, inst, site);
ArrayInstance array = (ArrayInstance)inst;
switch (array.getArrayType()) {
@@ -49,10 +50,6 @@
if (objects[i] != null) {
Instance ref = (Instance)objects[i];
insts[i] = snapshot.findInstance(ref.getId());
- if (ref.getNextInstanceToGcRoot() == inst) {
- String field = "[" + Integer.toString(i) + "]";
- insts[i].setNextInstanceToGcRoot(this, field);
- }
}
}
mValues = new AbstractList<Value>() {
@@ -132,6 +129,35 @@
return mValues.get(index);
}
+ @Override
+ ReferenceIterator getReferences() {
+ // The list of references will be empty if this is a primitive array.
+ List<Reference> refs = Collections.emptyList();
+ if (!mValues.isEmpty()) {
+ Value first = mValues.get(0);
+ if (first == null || first.isAhatInstance()) {
+ refs = new AbstractList<Reference>() {
+ @Override
+ public int size() {
+ return mValues.size();
+ }
+
+ @Override
+ public Reference get(int index) {
+ Value value = mValues.get(index);
+ if (value != null) {
+ assert value.isAhatInstance();
+ String field = "[" + Integer.toString(index) + "]";
+ return new Reference(AhatArrayInstance.this, field, value.asAhatInstance(), true);
+ }
+ return null;
+ }
+ };
+ }
+ }
+ return new ReferenceIterator(refs);
+ }
+
@Override public boolean isArrayInstance() {
return true;
}
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
index 158de52..2115923 100644
--- a/tools/ahat/src/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -19,6 +19,7 @@
import com.android.tools.perflib.heap.ClassInstance;
import com.android.tools.perflib.heap.Instance;
import java.awt.image.BufferedImage;
+import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
@@ -29,8 +30,8 @@
super(id);
}
- @Override void initialize(AhatSnapshot snapshot, Instance inst) {
- super.initialize(snapshot, inst);
+ @Override void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
+ super.initialize(snapshot, inst, site);
ClassInstance classInst = (ClassInstance)inst;
List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
@@ -40,15 +41,7 @@
String name = field.getField().getName();
String type = field.getField().getType().toString();
Value value = snapshot.getValue(field.getValue());
-
mFieldValues[i] = new FieldValue(name, type, value);
-
- if (field.getValue() instanceof Instance) {
- Instance ref = (Instance)field.getValue();
- if (ref.getNextInstanceToGcRoot() == inst) {
- value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
- }
- }
}
}
@@ -101,6 +94,30 @@
return Arrays.asList(mFieldValues);
}
+ @Override
+ ReferenceIterator getReferences() {
+ List<Reference> refs = new AbstractList<Reference>() {
+ @Override
+ public int size() {
+ return mFieldValues.length;
+ }
+
+ @Override
+ public Reference get(int index) {
+ FieldValue field = mFieldValues[index];
+ Value value = field.value;
+ if (value != null && value.isAhatInstance()) {
+ boolean strong = !field.name.equals("referent")
+ || !isInstanceOfClass("java.lang.ref.Reference");
+ AhatInstance ref = value.asAhatInstance();
+ return new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
+ }
+ return null;
+ }
+ };
+ return new ReferenceIterator(refs);
+ }
+
/**
* Returns true if this is an instance of a class with the given name.
*/
diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java
index c5ade1d..052d7a8 100644
--- a/tools/ahat/src/heapdump/AhatClassObj.java
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -19,6 +19,7 @@
import com.android.tools.perflib.heap.ClassObj;
import com.android.tools.perflib.heap.Field;
import com.android.tools.perflib.heap.Instance;
+import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -34,8 +35,8 @@
super(id);
}
- @Override void initialize(AhatSnapshot snapshot, Instance inst) {
- super.initialize(snapshot, inst);
+ @Override void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
+ super.initialize(snapshot, inst, site);
ClassObj classObj = (ClassObj)inst;
mClassName = classObj.getClassName();
@@ -58,13 +59,6 @@
String type = field.getKey().getType().toString();
Value value = snapshot.getValue(field.getValue());
mStaticFieldValues[index++] = new FieldValue(name, type, value);
-
- if (field.getValue() instanceof Instance) {
- Instance ref = (Instance)field.getValue();
- if (ref.getNextInstanceToGcRoot() == inst) {
- value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
- }
- }
}
}
@@ -96,6 +90,27 @@
return Arrays.asList(mStaticFieldValues);
}
+ @Override
+ ReferenceIterator getReferences() {
+ List<Reference> refs = new AbstractList<Reference>() {
+ @Override
+ public int size() {
+ return mStaticFieldValues.length;
+ }
+
+ @Override
+ public Reference get(int index) {
+ FieldValue field = mStaticFieldValues[index];
+ Value value = field.value;
+ if (value != null && value.isAhatInstance()) {
+ return new Reference(AhatClassObj.this, "." + field.name, value.asAhatInstance(), true);
+ }
+ return null;
+ }
+ };
+ return new ReferenceIterator(refs);
+ }
+
@Override public boolean isClassObj() {
return true;
}
@@ -105,11 +120,10 @@
}
@Override public String toString() {
- return mClassName;
+ return "class " + mClassName;
}
@Override AhatInstance newPlaceHolderInstance() {
return new AhatPlaceHolderClassObj(this);
}
}
-
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
index af369d9..8905b76 100644
--- a/tools/ahat/src/heapdump/AhatInstance.java
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -16,39 +16,48 @@
package com.android.ahat.heapdump;
+import com.android.ahat.dominators.DominatorsComputation;
import com.android.tools.perflib.heap.ClassObj;
import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.RootObj;
import java.awt.image.BufferedImage;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
+import java.util.Queue;
-public abstract class AhatInstance implements Diffable<AhatInstance> {
- private long mId;
+public abstract class AhatInstance implements Diffable<AhatInstance>,
+ DominatorsComputation.Node {
+ // The id of this instance from the heap dump.
+ private final long mId;
+
+ // Fields initialized in initialize().
private Size mSize;
- private Size[] mRetainedSizes; // Retained size indexed by heap index
- private boolean mIsReachable;
private AhatHeap mHeap;
- private AhatInstance mImmediateDominator;
- private AhatInstance mNextInstanceToGcRoot;
- private String mNextInstanceToGcRootField = "???";
private AhatClassObj mClassObj;
- private AhatInstance[] mHardReverseReferences;
- private AhatInstance[] mSoftReverseReferences;
private Site mSite;
// If this instance is a root, mRootTypes contains a set of the root types.
// If this instance is not a root, mRootTypes is null.
private List<String> mRootTypes;
- // List of instances this instance immediately dominates.
- private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
+ // Fields initialized in computeReverseReferences().
+ private AhatInstance mNextInstanceToGcRoot;
+ private String mNextInstanceToGcRootField;
+ private ArrayList<AhatInstance> mHardReverseReferences;
+ private ArrayList<AhatInstance> mSoftReverseReferences;
+ // Fields initialized in DominatorsComputation.computeDominators().
+ // mDominated - the list of instances immediately dominated by this instance.
+ // mRetainedSizes - retained size indexed by heap index.
+ private AhatInstance mImmediateDominator;
+ private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
+ private Size[] mRetainedSizes;
+ private Object mDominatorsComputationState;
+
+ // The baseline instance for purposes of diff.
private AhatInstance mBaseline;
public AhatInstance(long id) {
@@ -62,58 +71,16 @@
* There is no guarantee that the AhatInstances returned by
* snapshot.findInstance have been initialized yet.
*/
- void initialize(AhatSnapshot snapshot, Instance inst) {
- mId = inst.getId();
+ void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
mSize = new Size(inst.getSize(), 0);
- mIsReachable = inst.isReachable();
-
- List<AhatHeap> heaps = snapshot.getHeaps();
-
mHeap = snapshot.getHeap(inst.getHeap().getName());
- Instance dom = inst.getImmediateDominator();
- if (dom == null || dom instanceof RootObj) {
- mImmediateDominator = null;
- } else {
- mImmediateDominator = snapshot.findInstance(dom.getId());
- mImmediateDominator.mDominated.add(this);
- }
-
ClassObj clsObj = inst.getClassObj();
if (clsObj != null) {
mClassObj = snapshot.findClassObj(clsObj.getId());
}
- // A couple notes about reverse references:
- // * perflib sometimes returns unreachable reverse references. If
- // snapshot.findInstance returns null, it means the reverse reference is
- // not reachable, so we filter it out.
- // * We store the references as AhatInstance[] instead of
- // ArrayList<AhatInstance> because it saves a lot of space and helps
- // with performance when there are a lot of AhatInstances.
- ArrayList<AhatInstance> ahatRefs = new ArrayList<AhatInstance>();
- ahatRefs = new ArrayList<AhatInstance>();
- for (Instance ref : inst.getHardReverseReferences()) {
- AhatInstance ahat = snapshot.findInstance(ref.getId());
- if (ahat != null) {
- ahatRefs.add(ahat);
- }
- }
- mHardReverseReferences = new AhatInstance[ahatRefs.size()];
- ahatRefs.toArray(mHardReverseReferences);
-
- List<Instance> refs = inst.getSoftReverseReferences();
- ahatRefs.clear();
- if (refs != null) {
- for (Instance ref : refs) {
- AhatInstance ahat = snapshot.findInstance(ref.getId());
- if (ahat != null) {
- ahatRefs.add(ahat);
- }
- }
- }
- mSoftReverseReferences = new AhatInstance[ahatRefs.size()];
- ahatRefs.toArray(mSoftReverseReferences);
+ mSite = site;
}
/**
@@ -166,7 +133,7 @@
* Returns whether this object is strongly-reachable.
*/
public boolean isReachable() {
- return mIsReachable;
+ return mImmediateDominator != null;
}
/**
@@ -177,6 +144,12 @@
}
/**
+ * Returns an iterator over the references this AhatInstance has to other
+ * AhatInstances.
+ */
+ abstract ReferenceIterator getReferences();
+
+ /**
* Returns true if this instance is marked as a root instance.
*/
public boolean isRoot() {
@@ -227,13 +200,6 @@
}
/**
- * Sets the allocation site of this instance.
- */
- void setSite(Site site) {
- mSite = site;
- }
-
- /**
* Returns true if the given instance is a class object
*/
public boolean isClassObj() {
@@ -311,14 +277,20 @@
* Returns a list of objects with hard references to this object.
*/
public List<AhatInstance> getHardReverseReferences() {
- return Arrays.asList(mHardReverseReferences);
+ if (mHardReverseReferences != null) {
+ return mHardReverseReferences;
+ }
+ return Collections.emptyList();
}
/**
* Returns a list of objects with soft references to this object.
*/
public List<AhatInstance> getSoftReverseReferences() {
- return Arrays.asList(mSoftReverseReferences);
+ if (mSoftReverseReferences != null) {
+ return mSoftReverseReferences;
+ }
+ return Collections.emptyList();
}
/**
@@ -425,8 +397,10 @@
}
void setNextInstanceToGcRoot(AhatInstance inst, String field) {
- mNextInstanceToGcRoot = inst;
- mNextInstanceToGcRootField = field;
+ if (mNextInstanceToGcRoot == null && !isRoot()) {
+ mNextInstanceToGcRoot = inst;
+ mNextInstanceToGcRootField = field;
+ }
}
/** Returns a human-readable identifier for this object.
@@ -466,6 +440,47 @@
}
/**
+ * Initialize the reverse reference fields of this instance and all other
+ * instances reachable from it. Initializes the following fields:
+ * mNextInstanceToGcRoot
+ * mNextInstanceToGcRootField
+ * mHardReverseReferences
+ * mSoftReverseReferences
+ */
+ static void computeReverseReferences(AhatInstance root) {
+ // Do a breadth first search to visit the nodes.
+ Queue<Reference> bfs = new ArrayDeque<Reference>();
+ for (Reference ref : root.getReferences()) {
+ bfs.add(ref);
+ }
+ while (!bfs.isEmpty()) {
+ Reference ref = bfs.poll();
+
+ if (ref.ref.mHardReverseReferences == null) {
+ // This is the first time we are seeing ref.ref.
+ ref.ref.mNextInstanceToGcRoot = ref.src;
+ ref.ref.mNextInstanceToGcRootField = ref.field;
+ ref.ref.mHardReverseReferences = new ArrayList<AhatInstance>();
+ for (Reference childRef : ref.ref.getReferences()) {
+ bfs.add(childRef);
+ }
+ }
+
+ // Note: ref.src is null when the src is the SuperRoot.
+ if (ref.src != null) {
+ if (ref.strong) {
+ ref.ref.mHardReverseReferences.add(ref.src);
+ } else {
+ if (ref.ref.mSoftReverseReferences == null) {
+ ref.ref.mSoftReverseReferences = new ArrayList<AhatInstance>();
+ }
+ ref.ref.mSoftReverseReferences.add(ref.src);
+ }
+ }
+ }
+ }
+
+ /**
* Recursively compute the retained size of the given instance and all
* other instances it dominates.
*/
@@ -486,8 +501,10 @@
for (int i = 0; i < numHeaps; i++) {
inst.mRetainedSizes[i] = Size.ZERO;
}
- inst.mRetainedSizes[inst.mHeap.getIndex()] =
- inst.mRetainedSizes[inst.mHeap.getIndex()].plus(inst.mSize);
+ if (!(inst instanceof SuperRoot)) {
+ inst.mRetainedSizes[inst.mHeap.getIndex()] =
+ inst.mRetainedSizes[inst.mHeap.getIndex()].plus(inst.mSize);
+ }
deque.push(inst);
for (AhatInstance dominated : inst.mDominated) {
deque.push(dominated);
@@ -501,4 +518,25 @@
}
}
}
+
+ @Override
+ public void setDominatorsComputationState(Object state) {
+ mDominatorsComputationState = state;
+ }
+
+ @Override
+ public Object getDominatorsComputationState() {
+ return mDominatorsComputationState;
+ }
+
+ @Override
+ public Iterable<? extends DominatorsComputation.Node> getReferencesForDominators() {
+ return new DominatorReferenceIterator(getReferences());
+ }
+
+ @Override
+ public void setDominator(DominatorsComputation.Node dominator) {
+ mImmediateDominator = (AhatInstance)dominator;
+ mImmediateDominator.mDominated.add(this);
+ }
}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
index 4aac804..d797b11 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
@@ -16,6 +16,9 @@
package com.android.ahat.heapdump;
+import java.util.Collections;
+import java.util.List;
+
/**
* Generic PlaceHolder instance to take the place of a real AhatInstance for
* the purposes of displaying diffs.
@@ -60,4 +63,10 @@
@Override public boolean isPlaceHolder() {
return true;
}
+
+ @Override
+ ReferenceIterator getReferences() {
+ List<Reference> refs = Collections.emptyList();
+ return new ReferenceIterator(refs);
+ }
}
diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java
index 35d6c8a..7df78c5 100644
--- a/tools/ahat/src/heapdump/AhatSnapshot.java
+++ b/tools/ahat/src/heapdump/AhatSnapshot.java
@@ -16,6 +16,7 @@
package com.android.ahat.heapdump;
+import com.android.ahat.dominators.DominatorsComputation;
import com.android.tools.perflib.captures.DataBuffer;
import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
import com.android.tools.perflib.heap.ArrayInstance;
@@ -42,7 +43,7 @@
private final Site mRootSite = new Site("ROOT");
// Collection of objects whose immediate dominator is the SENTINEL_ROOT.
- private final List<AhatInstance> mRooted = new ArrayList<AhatInstance>();
+ private final List<AhatInstance> mRooted;
// List of all ahat instances stored in increasing order by id.
private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>();
@@ -80,7 +81,6 @@
*/
private AhatSnapshot(DataBuffer buffer, ProguardMap map) throws IOException {
Snapshot snapshot = Snapshot.createSnapshot(buffer, map);
- snapshot.computeDominators();
// Properly label the class of class objects in the perflib snapshot.
final ClassObj javaLangClass = snapshot.findClass("java.lang.Class");
@@ -139,46 +139,45 @@
// and instances.
for (AhatInstance ahat : mInstances) {
Instance inst = snapshot.findInstance(ahat.getId());
- ahat.initialize(this, inst);
- Long registeredNativeSize = registeredNative.get(inst);
- if (registeredNativeSize != null) {
- ahat.addRegisteredNativeSize(registeredNativeSize);
- }
-
- if (inst.getImmediateDominator() == Snapshot.SENTINEL_ROOT) {
- mRooted.add(ahat);
- }
-
- if (inst.isReachable()) {
- ahat.getHeap().addToSize(ahat.getSize());
- }
-
- // Update sites.
StackFrame[] frames = null;
StackTrace stack = inst.getStack();
if (stack != null) {
frames = stack.getFrames();
}
Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat);
- ahat.setSite(site);
+ ahat.initialize(this, inst, site);
+
+ Long registeredNativeSize = registeredNative.get(inst);
+ if (registeredNativeSize != null) {
+ ahat.addRegisteredNativeSize(registeredNativeSize);
+ }
}
// Record the roots and their types.
+ SuperRoot superRoot = new SuperRoot();
for (RootObj root : snapshot.getGCRoots()) {
Instance inst = root.getReferredInstance();
if (inst != null) {
- findInstance(inst.getId()).addRootType(root.getRootType().toString());
+ AhatInstance ahat = findInstance(inst.getId());
+ if (!ahat.isRoot()) {
+ superRoot.addRoot(ahat);
+ }
+ ahat.addRootType(root.getRootType().toString());
}
}
snapshot.dispose();
- // Compute the retained sizes of objects. We do this explicitly now rather
- // than relying on the retained sizes computed by perflib so that
- // registered native sizes are included.
- for (AhatInstance inst : mRooted) {
- AhatInstance.computeRetainedSize(inst, mHeaps.size());
+ AhatInstance.computeReverseReferences(superRoot);
+ DominatorsComputation.computeDominators(superRoot);
+ AhatInstance.computeRetainedSize(superRoot, mHeaps.size());
+
+ mRooted = superRoot.getDominated();
+ for (AhatHeap heap : mHeaps) {
+ heap.addToSize(superRoot.getRetainedSize(heap));
}
+
+ mRootSite.computeObjectsInfos(mHeaps.size());
}
/**
diff --git a/tools/ahat/src/heapdump/DominatorReferenceIterator.java b/tools/ahat/src/heapdump/DominatorReferenceIterator.java
new file mode 100644
index 0000000..ce2e6ef
--- /dev/null
+++ b/tools/ahat/src/heapdump/DominatorReferenceIterator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Reference iterator used for the dominators computation.
+ * This visits only strong references.
+ */
+class DominatorReferenceIterator implements Iterator<AhatInstance>,
+ Iterable<AhatInstance> {
+ private ReferenceIterator mIter;
+ private AhatInstance mNext;
+
+ public DominatorReferenceIterator(ReferenceIterator iter) {
+ mIter = iter;
+ mNext = null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ while (mNext == null && mIter.hasNext()) {
+ Reference ref = mIter.next();
+ if (ref.strong) {
+ mNext = ref.ref;
+ }
+ }
+ return mNext != null;
+ }
+
+ @Override
+ public AhatInstance next() {
+ if (hasNext()) {
+ AhatInstance next = mNext;
+ mNext = null;
+ return next;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public Iterator<AhatInstance> iterator() {
+ return this;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Reference.java b/tools/ahat/src/heapdump/Reference.java
new file mode 100644
index 0000000..980f278
--- /dev/null
+++ b/tools/ahat/src/heapdump/Reference.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * Reference represents a reference from 'src' to 'ref' through 'field'.
+ * Field is a string description for human consumption. This is typically
+ * either "." followed by the field name or an array subscript such as "[4]".
+ * 'strong' is true if this is a strong reference, false if it is a
+ * weak/soft/other reference.
+ */
+public class Reference {
+ public final AhatInstance src;
+ public final String field;
+ public final AhatInstance ref;
+ public final boolean strong;
+
+ public Reference(AhatInstance src, String field, AhatInstance ref, boolean strong) {
+ this.src = src;
+ this.field = field;
+ this.ref = ref;
+ this.strong = strong;
+ }
+}
diff --git a/tools/ahat/src/heapdump/ReferenceIterator.java b/tools/ahat/src/heapdump/ReferenceIterator.java
new file mode 100644
index 0000000..a707fb2
--- /dev/null
+++ b/tools/ahat/src/heapdump/ReferenceIterator.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+class ReferenceIterator implements Iterator<Reference>,
+ Iterable<Reference> {
+ private List<Reference> mRefs;
+ private int mLength;
+ private int mNextIndex;
+ private Reference mNext;
+
+ /**
+ * Construct a ReferenceIterator that iterators over the given list of
+ * references. Elements of the given list of references may be null, in
+ * which case the ReferenceIterator will skip over them.
+ */
+ public ReferenceIterator(List<Reference> refs) {
+ mRefs = refs;
+ mLength = refs.size();
+ mNextIndex = 0;
+ mNext = null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ while (mNext == null && mNextIndex < mLength) {
+ mNext = mRefs.get(mNextIndex);
+ mNextIndex++;
+ }
+ return mNext != null;
+ }
+
+ @Override
+ public Reference next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ Reference next = mNext;
+ mNext = null;
+ return next;
+ }
+
+ @Override
+ public Iterator<Reference> iterator() {
+ return this;
+ }
+}
diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java
index fdd4eea..f0fc5d2 100644
--- a/tools/ahat/src/heapdump/Site.java
+++ b/tools/ahat/src/heapdump/Site.java
@@ -42,15 +42,15 @@
private int mDepth;
// The total size of objects allocated in this site (including child sites),
- // organized by heap index. Heap indices outside the range of mSizesByHeap
- // implicitly have size 0.
+ // organized by heap index. Computed as part of computeObjectsInfos.
private Size[] mSizesByHeap;
// List of child sites.
private List<Site> mChildren;
- // List of all objects allocated in this site (including child sites).
+ // List of objects allocated at this site (not including child sites).
private List<AhatInstance> mObjects;
+
private List<ObjectsInfo> mObjectsInfos;
private Map<AhatHeap, Map<AhatClassObj, ObjectsInfo>> mObjectsInfoMap;
@@ -111,7 +111,6 @@
mLineNumber = line;
mId = id;
mDepth = depth;
- mSizesByHeap = new Size[0];
mChildren = new ArrayList<Site>();
mObjects = new ArrayList<AhatInstance>();
mObjectsInfos = new ArrayList<ObjectsInfo>();
@@ -130,67 +129,102 @@
}
private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) {
- while (true) {
- site.mObjects.add(inst);
+ while (depth > 0) {
+ StackFrame next = frames[depth - 1];
+ Site child = null;
+ for (int i = 0; i < site.mChildren.size(); i++) {
+ Site curr = site.mChildren.get(i);
+ if (curr.mLineNumber == next.getLineNumber()
+ && curr.mMethodName.equals(next.getMethodName())
+ && curr.mSignature.equals(next.getSignature())
+ && curr.mFilename.equals(next.getFilename())) {
+ child = curr;
+ break;
+ }
+ }
+ if (child == null) {
+ child = new Site(site, next.getMethodName(), next.getSignature(),
+ next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
+ site.mChildren.add(child);
+ }
+ depth = depth - 1;
+ site = child;
+ }
+ site.mObjects.add(inst);
+ return site;
+ }
- ObjectsInfo info = site.getObjectsInfo(inst.getHeap(), inst.getClassObj());
+ /**
+ * Recompute the ObjectsInfos for this and all child sites.
+ * This should be done after the sites tree has been formed. It should also
+ * be done after dominators computation has been performed to ensure only
+ * reachable objects are included in the ObjectsInfos.
+ *
+ * @param numHeaps - The number of heaps in the heap dump.
+ */
+ void computeObjectsInfos(int numHeaps) {
+ // Count up the total sizes by heap.
+ mSizesByHeap = new Size[numHeaps];
+ for (int i = 0; i < numHeaps; ++i) {
+ mSizesByHeap[i] = Size.ZERO;
+ }
+
+ // Add all reachable objects allocated at this site.
+ for (AhatInstance inst : mObjects) {
if (inst.isReachable()) {
AhatHeap heap = inst.getHeap();
- if (heap.getIndex() >= site.mSizesByHeap.length) {
- Size[] newSizes = new Size[heap.getIndex() + 1];
- for (int i = 0; i < site.mSizesByHeap.length; i++) {
- newSizes[i] = site.mSizesByHeap[i];
- }
- for (int i = site.mSizesByHeap.length; i < heap.getIndex() + 1; i++) {
- newSizes[i] = Size.ZERO;
- }
- site.mSizesByHeap = newSizes;
- }
- site.mSizesByHeap[heap.getIndex()]
- = site.mSizesByHeap[heap.getIndex()].plus(inst.getSize());
-
+ Size size = inst.getSize();
+ ObjectsInfo info = getObjectsInfo(heap, inst.getClassObj());
info.numInstances++;
- info.numBytes = info.numBytes.plus(inst.getSize());
+ info.numBytes = info.numBytes.plus(size);
+ mSizesByHeap[heap.getIndex()] = mSizesByHeap[heap.getIndex()].plus(size);
}
+ }
- if (depth > 0) {
- StackFrame next = frames[depth - 1];
- Site child = null;
- for (int i = 0; i < site.mChildren.size(); i++) {
- Site curr = site.mChildren.get(i);
- if (curr.mLineNumber == next.getLineNumber()
- && curr.mMethodName.equals(next.getMethodName())
- && curr.mSignature.equals(next.getSignature())
- && curr.mFilename.equals(next.getFilename())) {
- child = curr;
- break;
- }
- }
- if (child == null) {
- child = new Site(site, next.getMethodName(), next.getSignature(),
- next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
- site.mChildren.add(child);
- }
- depth = depth - 1;
- site = child;
- } else {
- return site;
+ // Add objects allocated in child sites.
+ for (Site child : mChildren) {
+ child.computeObjectsInfos(numHeaps);
+ for (ObjectsInfo childInfo : child.mObjectsInfos) {
+ ObjectsInfo info = getObjectsInfo(childInfo.heap, childInfo.classObj);
+ info.numInstances += childInfo.numInstances;
+ info.numBytes = info.numBytes.plus(childInfo.numBytes);
+ }
+ for (int i = 0; i < numHeaps; ++i) {
+ mSizesByHeap[i] = mSizesByHeap[i].plus(child.mSizesByHeap[i]);
}
}
}
// Get the size of a site for a specific heap.
public Size getSize(AhatHeap heap) {
- int index = heap.getIndex();
- return index >= 0 && index < mSizesByHeap.length ? mSizesByHeap[index] : Size.ZERO;
+ return mSizesByHeap[heap.getIndex()];
}
/**
- * Get the list of objects allocated under this site. Includes objects
- * allocated in children sites.
+ * Collect the objects allocated under this site, optionally filtered by
+ * heap name or class name. Includes objects allocated in children sites.
+ * @param heapName - The name of the heap the collected objects should
+ * belong to. This may be null to indicate objects of
+ * every heap should be collected.
+ * @param className - The name of the class the collected objects should
+ * belong to. This may be null to indicate objects of
+ * every class should be collected.
+ * @param objects - Out parameter. A collection of objects that all
+ * collected objects should be added to.
*/
- public Collection<AhatInstance> getObjects() {
- return mObjects;
+ public void getObjects(String heapName, String className, Collection<AhatInstance> objects) {
+ for (AhatInstance inst : mObjects) {
+ if ((heapName == null || inst.getHeap().getName().equals(heapName))
+ && (className == null || inst.getClassName().equals(className))) {
+ objects.add(inst);
+ }
+ }
+
+ // Recursively visit children. Recursion should be okay here because the
+ // stack depth is limited by a reasonable amount (128 frames or so).
+ for (Site child : mChildren) {
+ child.getObjects(heapName, className, objects);
+ }
}
/**
@@ -220,8 +254,8 @@
// Get the combined size of the site for all heaps.
public Size getTotalSize() {
Size total = Size.ZERO;
- for (int i = 0; i < mSizesByHeap.length; i++) {
- total = total.plus(mSizesByHeap[i]);
+ for (Size size : mSizesByHeap) {
+ total = total.plus(size);
}
return total;
}
diff --git a/tools/ahat/src/heapdump/SuperRoot.java b/tools/ahat/src/heapdump/SuperRoot.java
new file mode 100644
index 0000000..54410cf
--- /dev/null
+++ b/tools/ahat/src/heapdump/SuperRoot.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.ahat.dominators.DominatorsComputation;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SuperRoot extends AhatInstance implements DominatorsComputation.Node {
+ private List<AhatInstance> mRoots = new ArrayList<AhatInstance>();
+ private Object mDominatorsComputationState;
+
+ public SuperRoot() {
+ super(0);
+ }
+
+ public void addRoot(AhatInstance root) {
+ mRoots.add(root);
+ }
+
+ @Override
+ public String toString() {
+ return "SUPER_ROOT";
+ }
+
+ @Override
+ ReferenceIterator getReferences() {
+ List<Reference> refs = new AbstractList<Reference>() {
+ @Override
+ public int size() {
+ return mRoots.size();
+ }
+
+ @Override
+ public Reference get(int index) {
+ String field = ".roots[" + Integer.toString(index) + "]";
+ return new Reference(null, field, mRoots.get(index), true);
+ }
+ };
+ return new ReferenceIterator(refs);
+ }
+}
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 3d3de78..13fd102 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -60,6 +60,14 @@
public StackSmasher child;
}
+ public static class Reference {
+ public Object referent;
+
+ public Reference(Object referent) {
+ this.referent = referent;
+ }
+ }
+
// We will take a heap dump that includes a single instance of this
// DumpedStuff class. Objects stored as fields in this class can be easily
// found in the hprof dump by searching for the instance of the DumpedStuff
@@ -71,6 +79,7 @@
public char[] charArray = "char thing".toCharArray();
public String nullString = null;
public Object anObject = new Object();
+ public Reference aReference = new Reference(anObject);
public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public PhantomReference aPhantomReference = new PhantomReference(anObject, referenceQueue);
public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
diff --git a/tools/ahat/test/DominatorsTest.java b/tools/ahat/test/DominatorsTest.java
new file mode 100644
index 0000000..0424e10
--- /dev/null
+++ b/tools/ahat/test/DominatorsTest.java
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.dominators.DominatorsComputation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class DominatorsTest {
+ private static class Node implements DominatorsComputation.Node {
+ public String name;
+ public List<Node> depends = new ArrayList<Node>();
+ public Node dominator;
+ private Object dominatorsComputationState;
+
+ public Node(String name) {
+ this.name = name;
+ }
+
+ public void computeDominators() {
+ DominatorsComputation.computeDominators(this);
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public void setDominatorsComputationState(Object state) {
+ dominatorsComputationState = state;
+ }
+
+ @Override
+ public Object getDominatorsComputationState() {
+ return dominatorsComputationState;
+ }
+
+ @Override
+ public Collection<Node> getReferencesForDominators() {
+ return depends;
+ }
+
+ @Override
+ public void setDominator(DominatorsComputation.Node dominator) {
+ this.dominator = (Node)dominator;
+ }
+ }
+
+ @Test
+ public void singleNode() {
+ // --> n
+ // Trivial case.
+ Node n = new Node("n");
+ n.computeDominators();
+ }
+
+ @Test
+ public void parentWithChild() {
+ // --> parent --> child
+ // The child node is dominated by the parent.
+ Node parent = new Node("parent");
+ Node child = new Node("child");
+ parent.depends = Arrays.asList(child);
+
+ parent.computeDominators();
+ assertEquals(parent, child.dominator);
+ }
+
+ @Test
+ public void reachableTwoWays() {
+ // /-> right -->\
+ // --> parent child
+ // \-> left --->/
+ // The child node can be reached either by right or by left.
+ Node parent = new Node("parent");
+ Node right = new Node("right");
+ Node left = new Node("left");
+ Node child = new Node("child");
+ parent.depends = Arrays.asList(left, right);
+ right.depends = Arrays.asList(child);
+ left.depends = Arrays.asList(child);
+
+ parent.computeDominators();
+ assertEquals(parent, left.dominator);
+ assertEquals(parent, right.dominator);
+ assertEquals(parent, child.dominator);
+ }
+
+ @Test
+ public void reachableDirectAndIndirect() {
+ // /-> right -->\
+ // --> parent -----------> child
+ // The child node can be reached either by right or parent.
+ Node parent = new Node("parent");
+ Node right = new Node("right");
+ Node child = new Node("child");
+ parent.depends = Arrays.asList(right, child);
+ right.depends = Arrays.asList(child);
+
+ parent.computeDominators();
+ assertEquals(parent, child.dominator);
+ assertEquals(parent, right.dominator);
+ }
+
+ @Test
+ public void subDominator() {
+ // --> parent --> middle --> child
+ // The child is dominated by an internal node.
+ Node parent = new Node("parent");
+ Node middle = new Node("middle");
+ Node child = new Node("child");
+ parent.depends = Arrays.asList(middle);
+ middle.depends = Arrays.asList(child);
+
+ parent.computeDominators();
+ assertEquals(parent, middle.dominator);
+ assertEquals(middle, child.dominator);
+ }
+
+ @Test
+ public void childSelfLoop() {
+ // --> parent --> child -\
+ // \<---/
+ // The child points back to itself.
+ Node parent = new Node("parent");
+ Node child = new Node("child");
+ parent.depends = Arrays.asList(child);
+ child.depends = Arrays.asList(child);
+
+ parent.computeDominators();
+ assertEquals(parent, child.dominator);
+ }
+
+ @Test
+ public void singleEntryLoop() {
+ // --> parent --> a --> b --> c -\
+ // \<------------/
+ // There is a loop in the graph, with only one way into the loop.
+ Node parent = new Node("parent");
+ Node a = new Node("a");
+ Node b = new Node("b");
+ Node c = new Node("c");
+ parent.depends = Arrays.asList(a);
+ a.depends = Arrays.asList(b);
+ b.depends = Arrays.asList(c);
+ c.depends = Arrays.asList(a);
+
+ parent.computeDominators();
+ assertEquals(parent, a.dominator);
+ assertEquals(a, b.dominator);
+ assertEquals(b, c.dominator);
+ }
+
+ @Test
+ public void multiEntryLoop() {
+ // --> parent --> right --> a --> b ----\
+ // \ \<-- c <---/
+ // \--> left --->--------/
+ // There is a loop in the graph, with two different ways to enter the
+ // loop.
+ Node parent = new Node("parent");
+ Node left = new Node("left");
+ Node right = new Node("right");
+ Node a = new Node("a");
+ Node b = new Node("b");
+ Node c = new Node("c");
+ parent.depends = Arrays.asList(left, right);
+ right.depends = Arrays.asList(a);
+ left.depends = Arrays.asList(c);
+ a.depends = Arrays.asList(b);
+ b.depends = Arrays.asList(c);
+ c.depends = Arrays.asList(a);
+
+ parent.computeDominators();
+ assertEquals(parent, right.dominator);
+ assertEquals(parent, left.dominator);
+ assertEquals(parent, a.dominator);
+ assertEquals(parent, c.dominator);
+ assertEquals(a, b.dominator);
+ }
+
+ @Test
+ public void dominatorOverwrite() {
+ // /---------> right <--\
+ // --> parent --> child <--/ /
+ // \---> left ---------/
+ // Test a strange case where we have had trouble in the past with a
+ // dominator getting improperly overwritten. The relevant features of this
+ // case are: 'child' is visited after 'right', 'child' is dominated by
+ // 'parent', and 'parent' revisits 'right' after visiting 'child'.
+ Node parent = new Node("parent");
+ Node right = new Node("right");
+ Node left = new Node("left");
+ Node child = new Node("child");
+ parent.depends = Arrays.asList(left, child, right);
+ left.depends = Arrays.asList(right);
+ right.depends = Arrays.asList(child);
+
+ parent.computeDominators();
+ assertEquals(parent, left.dominator);
+ assertEquals(parent, child.dominator);
+ assertEquals(parent, right.dominator);
+ }
+
+ @Test
+ public void stackOverflow() {
+ // --> a --> b --> ... --> N
+ // Verify we don't smash the stack for deep chains.
+ Node root = new Node("root");
+ Node curr = root;
+ for (int i = 0; i < 10000; ++i) {
+ Node node = new Node("n" + i);
+ curr.depends.add(node);
+ curr = node;
+ }
+
+ root.computeDominators();
+ }
+
+ @Test
+ public void hiddenRevisit() {
+ // /-> left ---->---------\
+ // --> parent \---> a --> b --> c
+ // \-> right -/
+ // Test a case we have had trouble in the past.
+ // When a's dominator is updated from left to parent, that should trigger
+ // all reachable children's dominators to be updated too. In particular,
+ // c's dominator should be updated, even though b's dominator is
+ // unchanged.
+ Node parent = new Node("parent");
+ Node right = new Node("right");
+ Node left = new Node("left");
+ Node a = new Node("a");
+ Node b = new Node("b");
+ Node c = new Node("c");
+ parent.depends = Arrays.asList(right, left);
+ left.depends = Arrays.asList(a, c);
+ right.depends = Arrays.asList(a);
+ a.depends = Arrays.asList(b);
+ b.depends = Arrays.asList(c);
+
+ parent.computeDominators();
+ assertEquals(parent, left.dominator);
+ assertEquals(parent, right.dominator);
+ assertEquals(parent, a.dominator);
+ assertEquals(parent, c.dominator);
+ assertEquals(a, b.dominator);
+ }
+
+ @Test
+ public void preUndominatedUpdate() {
+ // /--------->--------\
+ // / /---->----\
+ // --> p -> a --> b --> c --> d --> e
+ // \---------->----------/
+ // Test a case we have had trouble in the past.
+ // The candidate dominator for e is revised from d to a, then d is shown
+ // to be reachable from p. Make sure that causes e's dominator to be
+ // refined again from a to p. The extra nodes are there to ensure the
+ // necessary scheduling to expose the bug we had.
+ Node p = new Node("p");
+ Node a = new Node("a");
+ Node b = new Node("b");
+ Node c = new Node("c");
+ Node d = new Node("d");
+ Node e = new Node("e");
+ p.depends = Arrays.asList(d, a);
+ a.depends = Arrays.asList(e, b);
+ b.depends = Arrays.asList(d, c);
+ c.depends = Arrays.asList(d);
+ d.depends = Arrays.asList(e);
+
+ p.computeDominators();
+ assertEquals(p, a.dominator);
+ assertEquals(a, b.dominator);
+ assertEquals(b, c.dominator);
+ assertEquals(p, d.dominator);
+ assertEquals(p, e.dominator);
+ }
+}
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
index 71b081c..f0e7f44 100644
--- a/tools/ahat/test/InstanceTest.java
+++ b/tools/ahat/test/InstanceTest.java
@@ -337,7 +337,7 @@
public void classObjToString() throws IOException {
TestDump dump = TestDump.getTestDump();
AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
- assertEquals("Main", obj.toString());
+ assertEquals("class Main", obj.toString());
}
@Test
@@ -370,6 +370,18 @@
}
@Test
+ public void reverseReferences() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ AhatInstance ref = dump.getDumpedAhatInstance("aReference");
+ AhatInstance weak = dump.getDumpedAhatInstance("aWeakReference");
+ assertTrue(obj.getHardReverseReferences().contains(ref));
+ assertFalse(obj.getHardReverseReferences().contains(weak));
+ assertFalse(obj.getSoftReverseReferences().contains(ref));
+ assertTrue(obj.getSoftReverseReferences().contains(weak));
+ }
+
+ @Test
public void asStringEmbedded() throws IOException {
// Set up a heap dump with an instance of java.lang.String of
// "hello" with instance id 0x42 that is backed by a char array that is
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index a95788e..a1e3246 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -24,6 +24,7 @@
args = new String[]{
"com.android.ahat.DiffFieldsTest",
"com.android.ahat.DiffTest",
+ "com.android.ahat.DominatorsTest",
"com.android.ahat.InstanceTest",
"com.android.ahat.NativeAllocationTest",
"com.android.ahat.ObjectHandlerTest",
diff --git a/tools/art b/tools/art
index 077dc4a..bc0c85e 100644
--- a/tools/art
+++ b/tools/art
@@ -278,7 +278,7 @@
-Xps-profile-path:$PROFILE_PATH \
-Xusejit:true \
"${ARGS_WITH_QUICKEN[@]}" \
- "&>" "$ANDROID_DATA/profile_gen.log"
+ &> "$ANDROID_DATA/profile_gen.log"
EXIT_STATUS=$?
if [ $EXIT_STATUS != 0 ]; then
diff --git a/tools/cpp-define-generator/constant_dexcache.def b/tools/cpp-define-generator/constant_dexcache.def
index ede16d2..743ebb7 100644
--- a/tools/cpp-define-generator/constant_dexcache.def
+++ b/tools/cpp-define-generator/constant_dexcache.def
@@ -25,4 +25,8 @@
DEFINE_EXPR(STRING_DEX_CACHE_HASH_BITS, int32_t,
art::LeastSignificantBit(art::mirror::DexCache::kDexCacheStringCacheSize))
DEFINE_EXPR(STRING_DEX_CACHE_ELEMENT_SIZE, int32_t,
- sizeof(art::mirror::StringDexCachePair))
\ No newline at end of file
+ sizeof(art::mirror::StringDexCachePair))
+DEFINE_EXPR(METHOD_DEX_CACHE_SIZE_MINUS_ONE, int32_t,
+ art::mirror::DexCache::kDexCacheMethodCacheSize - 1)
+DEFINE_EXPR(METHOD_DEX_CACHE_HASH_BITS, int32_t,
+ art::LeastSignificantBit(art::mirror::DexCache::kDexCacheMethodCacheSize))
diff --git a/tools/cpp-define-generator/constant_globals.def b/tools/cpp-define-generator/constant_globals.def
index a3ccc72..dbaf33c 100644
--- a/tools/cpp-define-generator/constant_globals.def
+++ b/tools/cpp-define-generator/constant_globals.def
@@ -17,9 +17,12 @@
// Export global values.
#if defined(DEFINE_INCLUDE_DEPENDENCIES)
+#include <atomic> // std::memory_order_relaxed
#include "globals.h" // art::kObjectAlignment
#endif
+DEFINE_EXPR(STD_MEMORY_ORDER_RELAXED, int32_t, std::memory_order_relaxed)
+
#define DEFINE_OBJECT_EXPR(macro_name, type, constant_field_name) \
DEFINE_EXPR(OBJECT_ ## macro_name, type, constant_field_name)
diff --git a/tools/dexfuzz/README b/tools/dexfuzz/README
index 271ac2b..a635fe9 100644
--- a/tools/dexfuzz/README
+++ b/tools/dexfuzz/README
@@ -145,6 +145,7 @@
PoolIndexChanger 30
RandomBranchChanger 30
RandomInstructionGenerator 30
+RegisterClobber 40
SwitchBranchShifter 30
TryBlockShifter 40
ValuePrinter 40
diff --git a/tools/dexfuzz/src/dexfuzz/DexFuzz.java b/tools/dexfuzz/src/dexfuzz/DexFuzz.java
index d7a5325..d37bd34 100644
--- a/tools/dexfuzz/src/dexfuzz/DexFuzz.java
+++ b/tools/dexfuzz/src/dexfuzz/DexFuzz.java
@@ -33,9 +33,9 @@
* Entrypoint class for dexfuzz.
*/
public class DexFuzz {
- // Last version update 1.4: added array length mutator.
+ // Last version update 1.5: added register clobber mutator.
private static int majorVersion = 1;
- private static int minorVersion = 4;
+ private static int minorVersion = 5;
private static int seedChangeVersion = 0;
/**
diff --git a/tools/dexfuzz/src/dexfuzz/program/Program.java b/tools/dexfuzz/src/dexfuzz/program/Program.java
index b0a06fc..bb2f4c0 100644
--- a/tools/dexfuzz/src/dexfuzz/program/Program.java
+++ b/tools/dexfuzz/src/dexfuzz/program/Program.java
@@ -38,6 +38,7 @@
import dexfuzz.program.mutators.PoolIndexChanger;
import dexfuzz.program.mutators.RandomBranchChanger;
import dexfuzz.program.mutators.RandomInstructionGenerator;
+import dexfuzz.program.mutators.RegisterClobber;
import dexfuzz.program.mutators.SwitchBranchShifter;
import dexfuzz.program.mutators.TryBlockShifter;
import dexfuzz.program.mutators.ValuePrinter;
@@ -209,6 +210,7 @@
registerMutator(new PoolIndexChanger(rng, mutationStats, mutations));
registerMutator(new RandomBranchChanger(rng, mutationStats, mutations));
registerMutator(new RandomInstructionGenerator(rng, mutationStats, mutations));
+ registerMutator(new RegisterClobber(rng, mutationStats, mutations));
registerMutator(new SwitchBranchShifter(rng, mutationStats, mutations));
registerMutator(new TryBlockShifter(rng, mutationStats, mutations));
registerMutator(new ValuePrinter(rng, mutationStats, mutations));
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/RegisterClobber.java b/tools/dexfuzz/src/dexfuzz/program/mutators/RegisterClobber.java
new file mode 100644
index 0000000..11da1d4
--- /dev/null
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/RegisterClobber.java
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+package dexfuzz.program.mutators;
+
+import dexfuzz.Log;
+import dexfuzz.MutationStats;
+import dexfuzz.program.MInsn;
+import dexfuzz.program.MutatableCode;
+import dexfuzz.program.Mutation;
+import dexfuzz.rawdex.Instruction;
+import dexfuzz.rawdex.Opcode;
+
+import java.util.List;
+import java.util.Random;
+
+public class RegisterClobber extends CodeMutator{
+
+ /**
+ * Every CodeMutator has an AssociatedMutation, representing the
+ * mutation that this CodeMutator can perform, to allow separate
+ * generateMutation() and applyMutation() phases, allowing serialization.
+ */
+ public static class AssociatedMutation extends Mutation{
+
+ int regClobberIdx;
+
+ @Override
+ public String getString() {
+ return Integer.toString(regClobberIdx);
+ }
+
+ @Override
+ public void parseString(String[] elements) {
+ Integer.parseInt(elements[2]);
+ }
+ }
+
+ // The following two methods are here for the benefit of MutationSerializer,
+ // so it can create a CodeMutator and get the correct associated Mutation, as it
+ // reads in mutations from a dump of mutations.
+ @Override
+ public Mutation getNewMutation() {
+ return new AssociatedMutation();
+ }
+
+ public RegisterClobber() {}
+
+ public RegisterClobber(Random rng, MutationStats stats, List<Mutation> mutations) {
+ super(rng, stats, mutations);
+ likelihood = 40;
+ }
+
+ @Override
+ protected boolean canMutate(MutatableCode mutatableCode) {
+ return mutatableCode.registersSize > 0;
+ }
+
+ @Override
+ protected Mutation generateMutation(MutatableCode mutatableCode) {
+ int insertionIdx = rng.nextInt(mutatableCode.getInstructionCount());
+
+ AssociatedMutation mutation = new AssociatedMutation();
+ mutation.setup(this.getClass(), mutatableCode);
+ mutation.regClobberIdx = insertionIdx;
+ return mutation;
+ }
+
+ @Override
+ protected void applyMutation(Mutation uncastMutation) {
+ AssociatedMutation mutation = (AssociatedMutation) uncastMutation;
+ MutatableCode mutatableCode = mutation.mutatableCode;
+
+ int totalRegUsed = mutatableCode.registersSize;
+ for (int i = 0; i < totalRegUsed; i++) {
+ MInsn newInsn = new MInsn();
+ newInsn.insn = new Instruction();
+ newInsn.insn.info = Instruction.getOpcodeInfo(Opcode.CONST_16);
+ newInsn.insn.vregA = i;
+ newInsn.insn.vregB = 0;
+ mutatableCode.insertInstructionAt(newInsn, mutation.regClobberIdx + i);
+ }
+
+ Log.info("Assigned zero to the registers from 0 to " + (totalRegUsed - 1) +
+ " at index " + mutation.regClobberIdx);
+ stats.incrementStat("Clobbered the registers");
+ }
+}
\ No newline at end of file