diff options
author | 2024-02-21 18:28:06 +0800 | |
---|---|---|
committer | 2024-03-07 12:40:19 +0000 | |
commit | c8b6e26aa56bb6761bb781d1095b36f84c4c65d4 (patch) | |
tree | ff692b41c6022bdb576bcf5090b89a452608db9b | |
parent | 9e6eca71dc3aeeb7f92c40fb7a51d582af0610f0 (diff) |
Reuse boot JNI stub for native methods
Different native methods can share the same JNI stub as long as they
have the same flag and shorty. Boot images loaded by zygote contain lots
of JNI stubs that already compiled, so we can reuse them for native
methods loaded later. For those methods having a matching JNI stub, we
no longer need the GenericJNI and following JIT/AOT, which will bring an
increase in program speed.
Since there are many optimizations in JniCompile, we also optimize the
"shorty equals" criteria for some archs to let more methods find their
matching stubs.
Test performance improvement: run a simple addOne(Object, int) native
method for multiple times at startup (microsecond, lower is better):
Number of runs before after
5000 398.70 124.94
10000 792.21 234.23
50000 3919.20 1065.30
Test feature coverage: start and run the app for 30 seconds (top 10 apps
in Chinese market, higher percentage is better):
(count of native methods that reuse boot JNI stub / total count of app
native methods = percentage)
app1: 1055/1206 = 87.48%
app2: 765/884 = 86.54%
app3: 1267/1414 = 89.60%
app4: 1577/1759 = 89.65%
app5: 1698/1860 = 91.29%
app6: 2528/2787 = 90.71%
app7: 1058/1218 = 86.86%
app8: 952/1092 = 87.18%
app9: 1343/1483 = 90.56%
app10: 2990/3492 = 85.62%
Test: m test-art-host-gtest
Test: testrunner.py --host
Test: run-gtest.sh
Test: testrunner.py --target
Bug: 288983053
Change-Id: I72f27bcfcd4d4a360bd5d9478b8f7687f087e431
-rw-r--r-- | compiler/common_compiler_test.h | 2 | ||||
-rw-r--r-- | dex2oat/driver/compiler_driver.cc | 16 | ||||
-rw-r--r-- | dex2oat/linker/image_writer.cc | 68 | ||||
-rw-r--r-- | dex2oat/linker/image_writer.h | 16 | ||||
-rw-r--r-- | libartbase/base/hash_map.h | 7 | ||||
-rw-r--r-- | runtime/Android.bp | 2 | ||||
-rw-r--r-- | runtime/class_linker.cc | 38 | ||||
-rw-r--r-- | runtime/class_linker.h | 16 | ||||
-rw-r--r-- | runtime/gc/space/image_space.cc | 3 | ||||
-rw-r--r-- | runtime/oat/image-inl.h | 30 | ||||
-rw-r--r-- | runtime/oat/image.cc | 7 | ||||
-rw-r--r-- | runtime/oat/image.h | 12 | ||||
-rw-r--r-- | runtime/oat/jni_stub_hash_map.cc | 355 | ||||
-rw-r--r-- | runtime/oat/jni_stub_hash_map.h | 112 | ||||
-rw-r--r-- | runtime/oat/jni_stub_hash_map_test.cc | 304 | ||||
-rw-r--r-- | runtime/runtime_image.cc | 11 | ||||
-rw-r--r-- | runtime/stack.cc | 23 | ||||
-rw-r--r-- | test/667-jit-jni-stub/src/Main.java | 9 | ||||
-rw-r--r-- | test/MyClassNatives/MyClassNatives.java | 119 | ||||
-rw-r--r-- | test/common/runtime_state.cc | 16 |
20 files changed, 1144 insertions, 22 deletions
diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h index 9b49c93ea6..89f3fb40ad 100644 --- a/compiler/common_compiler_test.h +++ b/compiler/common_compiler_test.h @@ -89,10 +89,10 @@ class EXPORT CommonCompilerTestImpl { protected: virtual ClassLinker* GetClassLinker() = 0; virtual Runtime* GetRuntime() = 0; + class OneCompiledMethodStorage; private: class CodeAndMetadata; - class OneCompiledMethodStorage; std::vector<CodeAndMetadata> code_and_metadata_; }; diff --git a/dex2oat/driver/compiler_driver.cc b/dex2oat/driver/compiler_driver.cc index 824319b299..39a68537e2 100644 --- a/dex2oat/driver/compiler_driver.cc +++ b/dex2oat/driver/compiler_driver.cc @@ -488,10 +488,18 @@ static void CompileMethodQuick( // Query any JNI optimization annotations such as @FastNative or @CriticalNative. access_flags |= annotations::GetNativeMethodAnnotationAccessFlags( dex_file, dex_file.GetClassDef(class_def_idx), method_idx); - - compiled_method = driver->GetCompiler()->JniCompile( - access_flags, method_idx, dex_file, dex_cache); - CHECK(compiled_method != nullptr); + const void* boot_jni_stub = nullptr; + if (!Runtime::Current()->GetHeap()->GetBootImageSpaces().empty()) { + // Skip the compilation for native method if found an usable boot JNI stub. + ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); + std::string_view shorty = dex_file.GetMethodShortyView(dex_file.GetMethodId(method_idx)); + boot_jni_stub = class_linker->FindBootJniStub(access_flags, shorty); + } + if (boot_jni_stub == nullptr) { + compiled_method = + driver->GetCompiler()->JniCompile(access_flags, method_idx, dex_file, dex_cache); + CHECK(compiled_method != nullptr); + } } } else if ((access_flags & kAccAbstract) != 0) { // Abstract methods don't have code. diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc index b663d32e2e..d5ab5d3345 100644 --- a/dex2oat/linker/image_writer.cc +++ b/dex2oat/linker/image_writer.cc @@ -571,6 +571,7 @@ bool ImageWriter::Write(int image_fd, for (size_t i = 0; i < oat_filenames_.size(); ++i) { CreateHeader(i, component_count); CopyAndFixupNativeData(i); + CopyAndFixupJniStubMethods(i); } } @@ -1463,6 +1464,14 @@ void ImageWriter::RecordNativeRelocations(ObjPtr<mirror::Class> klass, size_t oa for (auto& m : klass->GetMethods(target_ptr_size_)) { AssignMethodOffset(&m, type, oat_index); } + // Only write JNI stub methods in boot images, but not in boot image extensions and app images. + if (compiler_options_.IsBootImage() && compiler_options_.IsJniCompilationEnabled()) { + for (auto& m : klass->GetMethods(target_ptr_size_)) { + if (m.IsNative() && !m.IsIntrinsic()) { + AssignJniStubMethodOffset(&m, oat_index); + } + } + } (any_dirty ? dirty_methods_ : clean_methods_) += num_methods; } // Assign offsets for all runtime methods in the IMT since these may hold conflict tables @@ -1544,6 +1553,20 @@ void ImageWriter::AssignMethodOffset(ArtMethod* method, image_info.IncrementBinSlotSize(bin_type, ArtMethod::Size(target_ptr_size_)); } +void ImageWriter::AssignJniStubMethodOffset(ArtMethod* method, size_t oat_index) { + CHECK(method->IsNative()); + auto it = jni_stub_map_.find(JniStubKey(method)); + if (it == jni_stub_map_.end()) { + ImageInfo& image_info = GetImageInfo(oat_index); + constexpr Bin bin_type = Bin::kJniStubMethod; + size_t offset = image_info.GetBinSlotSize(bin_type); + jni_stub_map_.Put(std::make_pair( + JniStubKey(method), + std::make_pair(method, JniStubMethodRelocation{oat_index, offset}))); + image_info.IncrementBinSlotSize(bin_type, static_cast<size_t>(target_ptr_size_)); + } +} + class ImageWriter::LayoutHelper { public: explicit LayoutHelper(ImageWriter* image_writer) @@ -2607,6 +2630,14 @@ void ImageWriter::CalculateNewObjectOffsets() { ImageInfo& image_info = GetImageInfo(relocation.oat_index); relocation.offset += image_info.GetBinSlotOffset(bin_type); } + + // Update the JNI stub methods by adding their bin sums. + for (auto& pair : jni_stub_map_) { + JniStubMethodRelocation& relocation = pair.second.second; + constexpr Bin bin_type = Bin::kJniStubMethod; + ImageInfo& image_info = GetImageInfo(relocation.oat_index); + relocation.offset += image_info.GetBinSlotOffset(bin_type); + } } std::pair<size_t, dchecked_vector<ImageSection>> @@ -2655,11 +2686,17 @@ ImageWriter::ImageInfo::CreateImageSections() const { ImageSection(GetBinSlotOffset(Bin::kRuntimeMethod), GetBinSlotSize(Bin::kRuntimeMethod)); /* + * JNI Stub Methods section + */ + sections[ImageHeader::kSectionJniStubMethods] = + ImageSection(GetBinSlotOffset(Bin::kJniStubMethod), GetBinSlotSize(Bin::kJniStubMethod)); + + /* * Interned Strings section */ // Round up to the alignment the string table expects. See HashSet::WriteToMemory. - size_t cur_pos = RoundUp(sections[ImageHeader::kSectionRuntimeMethods].End(), sizeof(uint64_t)); + size_t cur_pos = RoundUp(sections[ImageHeader::kSectionJniStubMethods].End(), sizeof(uint64_t)); const ImageSection& interned_strings_section = sections[ImageHeader::kSectionInternedStrings] = @@ -3030,6 +3067,21 @@ void ImageWriter::CopyAndFixupNativeData(size_t oat_index) { } } +void ImageWriter::CopyAndFixupJniStubMethods(size_t oat_index) { + const ImageInfo& image_info = GetImageInfo(oat_index); + // Copy method's address to JNI stub methods section. + for (auto& pair : jni_stub_map_) { + JniStubMethodRelocation& relocation = pair.second.second; + // Only work with JNI stubs that are in the current oat file. + if (relocation.oat_index != oat_index) { + continue; + } + void** address = reinterpret_cast<void**>(image_info.image_.Begin() + relocation.offset); + ArtMethod* method = pair.second.first; + CopyAndFixupPointer(address, method); + } +} + void ImageWriter::CopyAndFixupMethodPointerArray(mirror::PointerArray* arr) { // Pointer arrays are processed early and each is visited just once. // Therefore we know that this array has not been copied yet. @@ -3514,6 +3566,18 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, // JNI entrypoint: if (orig->IsNative()) { + // Find boot JNI stub for those methods that skipped AOT compilation and don't need + // clinit check. + bool still_needs_clinit_check = orig->StillNeedsClinitCheck<kWithoutReadBarrier>(); + if (!still_needs_clinit_check && + !compiler_options_.IsBootImage() && + quick_code == GetOatAddress(StubType::kQuickGenericJNITrampoline)) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + const void* boot_jni_stub = class_linker->FindBootJniStub(orig); + if (boot_jni_stub != nullptr) { + quick_code = boot_jni_stub; + } + } // The native method's pointer is set to a stub to lookup via dlsym. // Note this is not the code_ pointer, that is handled above. StubType stub_type = orig->IsCriticalNative() ? StubType::kJNIDlsymLookupCriticalTrampoline @@ -3684,6 +3748,8 @@ ImageWriter::ImageWriter(const CompilerOptions& compiler_options, image_objects_offset_begin_(0), target_ptr_size_(InstructionSetPointerSize(compiler_options.GetInstructionSet())), image_infos_(oat_filenames.size()), + jni_stub_map_(JniStubKeyHash(compiler_options.GetInstructionSet()), + JniStubKeyEquals(compiler_options.GetInstructionSet())), dirty_methods_(0u), clean_methods_(0u), app_class_loader_(class_loader), diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h index 9dd6c7850e..20509a9ed1 100644 --- a/dex2oat/linker/image_writer.h +++ b/dex2oat/linker/image_writer.h @@ -45,6 +45,7 @@ #include "lock_word.h" #include "mirror/dex_cache.h" #include "oat/image.h" +#include "oat/jni_stub_hash_map.h" #include "oat/oat.h" #include "oat/oat_file.h" #include "obj_ptr.h" @@ -209,6 +210,8 @@ class ImageWriter final { kIMTConflictTable, // Runtime methods (always clean, do not have a length prefix array). kRuntimeMethod, + // Methods with unique JNI stubs. + kJniStubMethod, // Metadata bin for data that is temporary during image lifetime. kMetadata, kLast = kMetadata, @@ -450,6 +453,7 @@ class ImageWriter final { // Creates the contiguous image in memory and adjusts pointers. void CopyAndFixupNativeData(size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_); + void CopyAndFixupJniStubMethods(size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_); void CopyAndFixupObjects() REQUIRES_SHARED(Locks::mutator_lock_); void CopyAndFixupObject(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_); template <bool kCheckIfDone> @@ -495,6 +499,10 @@ class ImageWriter final { size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_); + // Assign the offset for a method with unique JNI stub. + void AssignJniStubMethodOffset(ArtMethod* method, size_t oat_index) + REQUIRES_SHARED(Locks::mutator_lock_); + // Return true if imt was newly inserted. bool TryAssignImTableOffset(ImTable* imt, size_t oat_index) REQUIRES_SHARED(Locks::mutator_lock_); @@ -530,6 +538,11 @@ class ImageWriter final { NativeObjectRelocationType type; }; + struct JniStubMethodRelocation { + size_t oat_index; + uintptr_t offset; + }; + NativeObjectRelocation GetNativeRelocation(void* obj) REQUIRES_SHARED(Locks::mutator_lock_); // Location of where the object will be when the image is loaded at runtime. @@ -645,6 +658,9 @@ class ImageWriter final { // image objects (aka sum of bin_slot_sizes_). ArtMethods are placed right after the ArtFields. HashMap<void*, NativeObjectRelocation> native_object_relocations_; + // HashMap used for generating JniStubMethodsSection. + JniStubHashMap<std::pair<ArtMethod*, JniStubMethodRelocation>> jni_stub_map_; + // Runtime ArtMethods which aren't reachable from any Class but need to be copied into the image. ArtMethod* image_methods_[ImageHeader::kImageMethodsCount]; diff --git a/libartbase/base/hash_map.h b/libartbase/base/hash_map.h index 8823c8bb4d..07245e5816 100644 --- a/libartbase/base/hash_map.h +++ b/libartbase/base/hash_map.h @@ -26,6 +26,8 @@ namespace art { template <typename Key, typename Value, typename HashFn> class HashMapHashWrapper { public: + HashMapHashWrapper() : hash_fn_(HashFn()) {} + explicit HashMapHashWrapper(const HashFn& hashfn) : hash_fn_(hashfn) {} size_t operator()(const Key& key) const { return hash_fn_(key); } @@ -41,6 +43,8 @@ class HashMapHashWrapper { template <typename Key, typename Value, typename PredFn> class HashMapPredWrapper { public: + HashMapPredWrapper() : pred_fn_(PredFn()) {} + explicit HashMapPredWrapper(const PredFn& predfn) : pred_fn_(predfn) {} bool operator()(const std::pair<Key, Value>& a, const std::pair<Key, Value>& b) const { return pred_fn_(a.first, b.first); } @@ -86,6 +90,9 @@ class HashMap : public HashSet<std::pair<Key, Value>, public: // Inherit constructors. using Base::Base; + HashMap(HashFn hashfn, Pred pred) + : Base(HashMapHashWrapper<Key, Value, HashFn>(hashfn), + HashMapPredWrapper<Key, Value, Pred>(pred)) {} // Used to insert a new mapping. typename Base::iterator Overwrite(const Key& k, const Value& v) { diff --git a/runtime/Android.bp b/runtime/Android.bp index 17f09cf814..72879e8929 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -382,6 +382,7 @@ cc_defaults { "oat/elf_file.cc", "oat/image.cc", "oat/index_bss_mapping.cc", + "oat/jni_stub_hash_map.cc", "oat/oat.cc", "oat/oat_file.cc", "oat/oat_file_assistant.cc", @@ -1077,6 +1078,7 @@ art_cc_defaults { "monitor_pool_test.cc", "monitor_test.cc", "native_stack_dump_test.cc", + "oat/jni_stub_hash_map_test.cc", "oat/oat_file_assistant_test.cc", "oat/oat_file_test.cc", "parsed_options_test.cc", diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 57dbd6d259..5a94f57bb6 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -408,6 +408,15 @@ void ClassLinker::ForceClassInitialized(Thread* self, Handle<mirror::Class> klas MakeInitializedClassesVisiblyInitialized(self, /*wait=*/true); } +const void* ClassLinker::FindBootJniStub(JniStubKey key) { + auto it = boot_image_jni_stubs_.find(key); + if (it == boot_image_jni_stubs_.end()) { + return nullptr; + } else { + return it->second; + } +} + ClassLinker::VisiblyInitializedCallback* ClassLinker::MarkClassInitialized( Thread* self, Handle<mirror::Class> klass) { if (kRuntimeISA == InstructionSet::kX86 || kRuntimeISA == InstructionSet::kX86_64) { @@ -616,6 +625,8 @@ ClassLinker::ClassLinker(InternTable* intern_table, bool fast_class_not_found_ex visibly_initialize_classes_with_membarier_(RegisterMemBarrierForClassInitialization()), critical_native_code_with_clinit_check_lock_("critical native code with clinit check lock"), critical_native_code_with_clinit_check_(), + boot_image_jni_stubs_(JniStubKeyHash(Runtime::Current()->GetInstructionSet()), + JniStubKeyEquals(Runtime::Current()->GetInstructionSet())), cha_(Runtime::Current()->IsAotCompiler() ? nullptr : new ClassHierarchyAnalysis()) { // For CHA disabled during Aot, see b/34193647. @@ -1437,6 +1448,16 @@ bool ClassLinker::InitFromBootImage(std::string* error_msg) { error_msg)) { return false; } + for (gc::space::ImageSpace* space : spaces) { + const ImageHeader& header = space->GetImageHeader(); + header.VisitJniStubMethods([&](ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_) { + const void* stub = method->GetOatMethodQuickCode(image_pointer_size_); + boot_image_jni_stubs_.Put(std::make_pair(JniStubKey(method), stub)); + return method; + }, space->Begin(), image_pointer_size_); + } + InitializeObjectVirtualMethodHashes(GetClassRoot<mirror::Object>(this), image_pointer_size_, ArrayRef<uint32_t>(object_virtual_method_hashes_)); @@ -3678,7 +3699,15 @@ void ClassLinker::FixupStaticTrampolines(Thread* self, ObjPtr<mirror::Class> kla for (size_t method_index = 0; method_index < num_direct_methods; ++method_index) { ArtMethod* method = klass->GetDirectMethod(method_index, pointer_size); if (method->NeedsClinitCheckBeforeCall()) { - instrumentation->UpdateMethodsCode(method, instrumentation->GetCodeForInvoke(method)); + const void* quick_code = instrumentation->GetCodeForInvoke(method); + if (method->IsNative() && IsQuickGenericJniStub(quick_code)) { + const void* boot_jni_stub = FindBootJniStub(method); + if (boot_jni_stub != nullptr) { + // Use boot JNI stub if found. + quick_code = boot_jni_stub; + } + } + instrumentation->UpdateMethodsCode(method, quick_code); } } // Ignore virtual methods on the iterator. @@ -3722,6 +3751,13 @@ static void LinkCode(ClassLinker* class_linker, const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index); quick_code = oat_method.GetQuickCode(); } + if (method->IsNative() && quick_code == nullptr) { + const void* boot_jni_stub = class_linker->FindBootJniStub(method); + if (boot_jni_stub != nullptr) { + // Use boot JNI stub if found. + quick_code = boot_jni_stub; + } + } runtime->GetInstrumentation()->InitializeMethodsCode(method, quick_code); if (method->IsNative()) { diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 5597149178..73b3153c45 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -41,6 +41,7 @@ #include "jni.h" #include "mirror/class.h" #include "mirror/object.h" +#include "oat/jni_stub_hash_map.h" #include "oat/oat_file.h" #include "verifier/verifier_enums.h" @@ -888,6 +889,17 @@ class EXPORT ClassLinker { ClassTable* GetBootClassTable() REQUIRES_SHARED(Locks::classlinker_classes_lock_) { return boot_class_table_.get(); } + // Find a matching JNI stub from boot images that we could reuse as entrypoint. + const void* FindBootJniStub(ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_) { + return FindBootJniStub(JniStubKey(method)); + } + + const void* FindBootJniStub(uint32_t flags, std::string_view shorty) { + return FindBootJniStub(JniStubKey(flags, shorty)); + } + + const void* FindBootJniStub(JniStubKey key); protected: virtual bool InitializeClass(Thread* self, @@ -1410,6 +1422,10 @@ class EXPORT ClassLinker { std::map<ArtMethod*, void*> critical_native_code_with_clinit_check_ GUARDED_BY(critical_native_code_with_clinit_check_lock_); + // Load unique JNI stubs from boot images. If the subsequently loaded native methods could find a + // matching stub, then reuse it without JIT/AOT compilation. + JniStubHashMap<const void*> boot_image_jni_stubs_; + std::unique_ptr<ClassHierarchyAnalysis> cha_; class FindVirtualMethodHolderVisitor; diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index e3e14cae76..6b1aef9721 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -2615,6 +2615,9 @@ class ImageSpace::BootImageLoader { }; image_header.VisitPackedImTables(method_table_visitor, space->Begin(), kPointerSize); image_header.VisitPackedImtConflictTables(method_table_visitor, space->Begin(), kPointerSize); + image_header.VisitJniStubMethods</*kUpdate=*/ true>(method_table_visitor, + space->Begin(), + kPointerSize); // Patch the intern table. if (image_header.GetInternedStringsSection().Size() != 0u) { diff --git a/runtime/oat/image-inl.h b/runtime/oat/image-inl.h index 5995600c27..b87bcb2313 100644 --- a/runtime/oat/image-inl.h +++ b/runtime/oat/image-inl.h @@ -115,6 +115,36 @@ inline void ImageHeader::VisitPackedImtConflictTables(const Visitor& visitor, } } +template <bool kUpdate, typename Visitor> +inline void ImageHeader::VisitJniStubMethods(const Visitor& visitor, + uint8_t* base, + PointerSize pointer_size) + const REQUIRES_SHARED(Locks::mutator_lock_) { + const ImageSection& section = GetJniStubMethodsSection(); + uint8_t* updated_base = base + section.Offset(); + for (size_t pos = 0; pos < section.Size(); pos += static_cast<size_t>(pointer_size)) { + uint8_t* ptr = updated_base + pos; + ArtMethod* orig; + if (pointer_size == PointerSize::k32) { + uint32_t value = *reinterpret_cast<uint32_t*>(ptr); + orig = reinterpret_cast32<ArtMethod*>(value); + } else { + uint64_t value = *reinterpret_cast<uint64_t*>(ptr); + orig = reinterpret_cast64<ArtMethod*>(value); + } + ArtMethod* updated = visitor(orig); + if (kUpdate){ + if (pointer_size == PointerSize::k32) { + *reinterpret_cast<uint32_t*>(ptr) = reinterpret_cast32<uint32_t>(updated); + } else { + *reinterpret_cast<uint64_t*>(ptr) = reinterpret_cast64<uint64_t>(updated); + } + } else { + DCHECK_EQ(updated, orig); + } + } +} + } // namespace art #endif // ART_RUNTIME_OAT_IMAGE_INL_H_ diff --git a/runtime/oat/image.cc b/runtime/oat/image.cc index a7ac8e0ebd..7bdc4f8e50 100644 --- a/runtime/oat/image.cc +++ b/runtime/oat/image.cc @@ -34,8 +34,8 @@ namespace art HIDDEN { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -// Last change: Split intrinsics list - with and without HIR. -const uint8_t ImageHeader::kImageVersion[] = { '1', '0', '9', '\0' }; +// Last change: Add JniStubMethodsSection. +const uint8_t ImageHeader::kImageVersion[] = { '1', '1', '0', '\0' }; ImageHeader::ImageHeader(uint32_t image_reservation_size, uint32_t component_count, @@ -264,9 +264,10 @@ const char* ImageHeader::GetImageSectionName(ImageSections index) { case kSectionObjects: return "Objects"; case kSectionArtFields: return "ArtFields"; case kSectionArtMethods: return "ArtMethods"; - case kSectionRuntimeMethods: return "RuntimeMethods"; case kSectionImTables: return "ImTables"; case kSectionIMTConflictTables: return "IMTConflictTables"; + case kSectionRuntimeMethods: return "RuntimeMethods"; + case kSectionJniStubMethods: return "JniStubMethods"; case kSectionInternedStrings: return "InternedStrings"; case kSectionClassTable: return "ClassTable"; case kSectionStringReferenceOffsets: return "StringReferenceOffsets"; diff --git a/runtime/oat/image.h b/runtime/oat/image.h index 23c92a1aa8..cd479f1a81 100644 --- a/runtime/oat/image.h +++ b/runtime/oat/image.h @@ -263,9 +263,10 @@ class PACKED(8) ImageHeader { kSectionObjects, kSectionArtFields, kSectionArtMethods, - kSectionRuntimeMethods, kSectionImTables, kSectionIMTConflictTables, + kSectionRuntimeMethods, + kSectionJniStubMethods, kSectionInternedStrings, kSectionClassTable, kSectionStringReferenceOffsets, @@ -335,6 +336,10 @@ class PACKED(8) ImageHeader { return GetImageSection(kSectionMetadata); } + const ImageSection& GetJniStubMethodsSection() const { + return GetImageSection(kSectionJniStubMethods); + } + const ImageSection& GetImageBitmapSection() const { return GetImageSection(kSectionImageBitmap); } @@ -403,6 +408,11 @@ class PACKED(8) ImageHeader { uint8_t* base, PointerSize pointer_size) const; + template <bool kUpdate = false, typename Visitor> + void VisitJniStubMethods(const Visitor& visitor, + uint8_t* base, + PointerSize pointer_size) const REQUIRES_SHARED(Locks::mutator_lock_); + IterationRange<const Block*> GetBlocks() const { return GetBlocks(GetImageBegin()); } diff --git a/runtime/oat/jni_stub_hash_map.cc b/runtime/oat/jni_stub_hash_map.cc new file mode 100644 index 0000000000..fccf697b7d --- /dev/null +++ b/runtime/oat/jni_stub_hash_map.cc @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni_stub_hash_map.h" + +#include "arch/arm64/jni_frame_arm64.h" +#include "arch/instruction_set.h" +#include "arch/riscv64/jni_frame_riscv64.h" +#include "arch/x86_64/jni_frame_x86_64.h" +#include "base/macros.h" + +namespace art HIDDEN { + +static char TranslateArgToJniShorty(char ch) { + // Byte, char, int, short, boolean are treated the same(e.g., Wx registers for arm64) when + // generating JNI stub, so their JNI shorty characters are same. + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + static constexpr char kTranslations[] = ".PPD.F..PJ.L......P......P"; + DCHECK_GE(ch, 'A'); + DCHECK_LE(ch, 'Z'); + DCHECK_NE(kTranslations[ch - 'A'], '.'); + return kTranslations[ch - 'A']; +} + +static char TranslateReturnTypeToJniShorty(char ch, InstructionSet isa = InstructionSet::kNone) { + // For all archs, reference type has a different JNI shorty character than others as it needs to + // be decoded in stub. + // For arm64, small return types need sign-/zero-extended. + // For x86_64, small return types need sign-/zero-extended, and RAX needs to be preserved and + // restored when thread state changes. + // Other archs keeps untranslated for simplicity. + // TODO: support riscv64 with an optimized version. + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + static constexpr char kArm64Translations[] = ".BCP.P..PP.L......S..P...Z"; + static constexpr char kX86_64Translations[] = ".BCP.P..RR.L......S..P...Z"; + static constexpr char kOtherTranslations[] = ".BCD.F..IJ.L......S..V...Z"; + DCHECK_GE(ch, 'A'); + DCHECK_LE(ch, 'Z'); + switch (isa) { + case InstructionSet::kArm64: + DCHECK_NE(kArm64Translations[ch - 'A'], '.'); + return kArm64Translations[ch - 'A']; + case InstructionSet::kX86_64: + DCHECK_NE(kX86_64Translations[ch - 'A'], '.'); + return kX86_64Translations[ch - 'A']; + default: + DCHECK_NE(kOtherTranslations[ch - 'A'], '.'); + return kOtherTranslations[ch - 'A']; + } +} + +static constexpr size_t GetMaxIntLikeRegisterArgs(InstructionSet isa) { + switch (isa) { + case InstructionSet::kArm64: + return arm64::kMaxIntLikeRegisterArguments; + case InstructionSet::kX86_64: + return x86_64::kMaxIntLikeRegisterArguments; + default: + LOG(FATAL) << "Unrecognized isa: " << isa << " for " << __FUNCTION__; + UNREACHABLE(); + } +} + +static constexpr size_t GetMaxFloatOrDoubleRegisterArgs(InstructionSet isa) { + switch (isa) { + case InstructionSet::kArm64: + return arm64::kMaxFloatOrDoubleRegisterArguments; + case InstructionSet::kX86_64: + return x86_64::kMaxFloatOrDoubleRegisterArguments; + default: + LOG(FATAL) << "Unrecognized isa: " << isa << " for " << __FUNCTION__; + UNREACHABLE(); + } +} + +static size_t StackOffset(char ch) { + if (ch == 'J' || ch == 'D') { + return 8; + } else { + return 4; + } +} + +static bool IsFloatOrDoubleArg(char ch) { + return ch == 'F' || ch == 'D'; +} + +static bool IsIntegralArg(char ch) { + return ch == 'B' || ch == 'C' || ch == 'I' || ch == 'J' || ch == 'S' || ch == 'Z'; +} + +static bool IsReferenceArg(char ch) { + return ch == 'L'; +} + +template<InstructionSet kIsa> +size_t JniStubKeyOptimizedHash(const JniStubKey& key) { + bool is_static = key.Flags() & kAccStatic; + std::string_view shorty = key.Shorty(); + size_t result = key.Flags(); + result ^= TranslateReturnTypeToJniShorty(shorty[0], kIsa); + constexpr size_t kMaxFloatOrDoubleRegisterArgs = GetMaxFloatOrDoubleRegisterArgs(kIsa); + constexpr size_t kMaxIntLikeRegisterArgs = GetMaxIntLikeRegisterArgs(kIsa); + size_t float_or_double_args = 0; + // ArtMethod* and 'Object* this' for non-static method. + // ArtMethod* for static method. + size_t int_like_args = is_static ? 1 : 2; + size_t stack_offset = 0; + for (char c : shorty.substr(1u)) { + bool stack_offset_matters = false; + stack_offset += StackOffset(c); + if (IsFloatOrDoubleArg(c)) { + ++float_or_double_args; + if (float_or_double_args > kMaxFloatOrDoubleRegisterArgs) { + // Stack offset matters if we run out of float-like (float, double) argument registers + // because the subsequent float-like args should be passed on the stack. + stack_offset_matters = true; + } else { + // Floating-point register arguments are not touched when generating JNI stub, so could be + // ignored when calculating hash value. + continue; + } + } else { + ++int_like_args; + if (int_like_args > kMaxIntLikeRegisterArgs || IsReferenceArg(c)) { + // Stack offset matters if we run out of integer-like (pointer, object, long, int, short, + // bool, etc) argument registers because the subsequent integer-like args should be passed + // on the stack. It also matters if current arg is reference type because it needs to be + // spilled as raw data even if it's in a register. + stack_offset_matters = true; + } else if (!is_static) { + // For non-static method, two managed arguments 'ArtMethod*' and 'Object* this' correspond + // to two native arguments 'JNIEnv*' and 'jobject'. So trailing integral (long, int, short, + // bool, etc) arguments will remain in the same registers, which do not need any generated + // code. + // But for static method, we have only one leading managed argument 'ArtMethod*' but two + // native arguments 'JNIEnv*' and 'jclass'. So trailing integral arguments are always + // shuffled around and affect the generated code. + continue; + } + } + // int_like_args is needed for reference type because it will determine from which register + // we take the value to construct jobject. + if (IsReferenceArg(c)) { + result = result * 31u * int_like_args ^ TranslateArgToJniShorty(c); + } else { + result = result * 31u ^ TranslateArgToJniShorty(c); + } + if (stack_offset_matters) { + result += stack_offset; + } + } + return result; +} + +size_t JniStubKeyGenericHash(const JniStubKey& key) { + std::string_view shorty = key.Shorty(); + size_t result = key.Flags(); + result ^= TranslateReturnTypeToJniShorty(shorty[0]); + for (char c : shorty.substr(1u)) { + result = result * 31u ^ TranslateArgToJniShorty(c); + } + return result; +} + +JniStubKeyHash::JniStubKeyHash(InstructionSet isa) { + switch (isa) { + case InstructionSet::kArm: + case InstructionSet::kThumb2: + case InstructionSet::kRiscv64: + case InstructionSet::kX86: + hash_func_ = JniStubKeyGenericHash; + break; + case InstructionSet::kArm64: + hash_func_ = JniStubKeyOptimizedHash<InstructionSet::kArm64>; + break; + case InstructionSet::kX86_64: + hash_func_ = JniStubKeyOptimizedHash<InstructionSet::kX86_64>; + break; + case InstructionSet::kNone: + LOG(FATAL) << "No instruction set given for " << __FUNCTION__; + UNREACHABLE(); + } +} + +template<InstructionSet kIsa> +bool JniStubKeyOptimizedEquals(const JniStubKey& lhs, const JniStubKey& rhs) { + if (lhs.Flags() != rhs.Flags()) { + return false; + } + std::string_view shorty_lhs = lhs.Shorty(); + std::string_view shorty_rhs = rhs.Shorty(); + if (TranslateReturnTypeToJniShorty(shorty_lhs[0], kIsa) != + TranslateReturnTypeToJniShorty(shorty_rhs[0], kIsa)) { + return false; + } + bool is_static = lhs.Flags() & kAccStatic; + constexpr size_t kMaxFloatOrDoubleRegisterArgs = GetMaxFloatOrDoubleRegisterArgs(kIsa); + constexpr size_t kMaxIntLikeRegisterArgs = GetMaxIntLikeRegisterArgs(kIsa); + size_t float_or_double_args_lhs = 0; + size_t float_or_double_args_rhs = 0; + size_t int_like_args_lhs = is_static ? 1 : 2; + size_t int_like_args_rhs = is_static ? 1 : 2; + size_t stack_offset_lhs = 0; + size_t stack_offset_rhs = 0; + size_t i = 1; + size_t j = 1; + while (i < shorty_lhs.length() && j < shorty_rhs.length()) { + bool should_skip = false; + bool stack_offset_matters = false; + char ch_lhs = shorty_lhs[i]; + char ch_rhs = shorty_rhs[j]; + + if (IsFloatOrDoubleArg(ch_lhs) && + float_or_double_args_lhs < kMaxFloatOrDoubleRegisterArgs) { + // Skip float-like register arguments. + ++i; + ++float_or_double_args_lhs; + stack_offset_lhs += StackOffset(ch_lhs); + should_skip = true; + } else if (IsIntegralArg(ch_lhs) && + int_like_args_lhs < kMaxIntLikeRegisterArgs) { + if (!is_static) { + // Skip integral register arguments for non-static method. + ++i; + ++int_like_args_lhs; + stack_offset_lhs += StackOffset(ch_lhs); + should_skip = true; + } + } else { + stack_offset_matters = true; + } + + if (IsFloatOrDoubleArg(ch_rhs) && + float_or_double_args_rhs < kMaxFloatOrDoubleRegisterArgs) { + // Skip float-like register arguments. + ++j; + ++float_or_double_args_rhs; + stack_offset_rhs += StackOffset(ch_rhs); + should_skip = true; + } else if (IsIntegralArg(ch_rhs) && + int_like_args_rhs < kMaxIntLikeRegisterArgs) { + if (!is_static) { + // Skip integral register arguments for non-static method. + ++j; + ++int_like_args_rhs; + stack_offset_rhs += StackOffset(ch_rhs); + should_skip = true; + } + } else { + stack_offset_matters = true; + } + + if (should_skip) { + continue; + } + if (TranslateArgToJniShorty(ch_lhs) != TranslateArgToJniShorty(ch_rhs)) { + return false; + } + if (stack_offset_matters && stack_offset_lhs != stack_offset_rhs) { + return false; + } + // int_like_args needs to be compared for reference type because it will determine from + // which register we take the value to construct jobject. + if (IsReferenceArg(ch_lhs) && int_like_args_lhs != int_like_args_rhs) { + return false; + } + // Passed character comparison. + ++i; + ++j; + stack_offset_lhs += StackOffset(ch_lhs); + stack_offset_rhs += StackOffset(ch_rhs); + DCHECK_EQ(IsFloatOrDoubleArg(ch_lhs), IsFloatOrDoubleArg(ch_rhs)); + if (IsFloatOrDoubleArg(ch_lhs)) { + ++float_or_double_args_lhs; + ++float_or_double_args_rhs; + } else { + ++int_like_args_lhs; + ++int_like_args_rhs; + } + } + auto remaining_shorty = + i < shorty_lhs.length() ? shorty_lhs.substr(i) : shorty_rhs.substr(j); + size_t float_or_double_args = + i < shorty_lhs.length() ? float_or_double_args_lhs : float_or_double_args_rhs; + size_t int_like_args = i < shorty_lhs.length() ? int_like_args_lhs : int_like_args_rhs; + for (char c : remaining_shorty) { + if (IsFloatOrDoubleArg(c) && float_or_double_args < kMaxFloatOrDoubleRegisterArgs) { + ++float_or_double_args; + continue; + } + if (!is_static && IsIntegralArg(c) && int_like_args < kMaxIntLikeRegisterArgs) { + ++int_like_args; + continue; + } + return false; + } + return true; +} + +bool JniStubKeyGenericEquals(const JniStubKey& lhs, const JniStubKey& rhs) { + if (lhs.Flags() != rhs.Flags()) { + return false; + } + std::string_view shorty_lhs = lhs.Shorty(); + std::string_view shorty_rhs = rhs.Shorty(); + if (TranslateReturnTypeToJniShorty(shorty_lhs[0]) != + TranslateReturnTypeToJniShorty(shorty_rhs[0])) { + return false; + } + if (shorty_lhs.length() != shorty_rhs.length()) { + return false; + } + for (size_t i = 1; i < shorty_lhs.length(); ++i) { + if (TranslateArgToJniShorty(shorty_lhs[i]) != TranslateArgToJniShorty(shorty_rhs[i])) { + return false; + } + } + return true; +} + +JniStubKeyEquals::JniStubKeyEquals(InstructionSet isa) { + switch (isa) { + case InstructionSet::kArm: + case InstructionSet::kThumb2: + case InstructionSet::kRiscv64: + case InstructionSet::kX86: + equals_func_ = JniStubKeyGenericEquals; + break; + case InstructionSet::kArm64: + equals_func_ = JniStubKeyOptimizedEquals<InstructionSet::kArm64>; + break; + case InstructionSet::kX86_64: + equals_func_ = JniStubKeyOptimizedEquals<InstructionSet::kX86_64>; + break; + case InstructionSet::kNone: + LOG(FATAL) << "No instruction set given for " << __FUNCTION__; + UNREACHABLE(); + } +} + +} // namespace art diff --git a/runtime/oat/jni_stub_hash_map.h b/runtime/oat/jni_stub_hash_map.h new file mode 100644 index 0000000000..6e4603d3fb --- /dev/null +++ b/runtime/oat/jni_stub_hash_map.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 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_OAT_JNI_STUB_HASH_MAP_H_ +#define ART_RUNTIME_OAT_JNI_STUB_HASH_MAP_H_ + +#include <memory> +#include <string_view> + +#include "arch/instruction_set.h" +#include "art_method.h" +#include "base/hash_map.h" + +namespace art HIDDEN { + +class JniStubKey { + public: + JniStubKey() = default; + JniStubKey(const JniStubKey& other) = default; + JniStubKey& operator=(const JniStubKey& other) = default; + + JniStubKey(uint32_t flags, std::string_view shorty) + : flags_(flags & (kAccStatic | kAccSynchronized | kAccFastNative | kAccCriticalNative)), + shorty_(shorty) { + DCHECK(ArtMethod::IsNative(flags)); + } + + explicit JniStubKey(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) + : JniStubKey(method->GetAccessFlags(), method->GetShortyView()) {} + + uint32_t Flags() const { + return flags_; + } + + std::string_view Shorty() const { + return shorty_; + } + + bool IsEmpty() const { + return Shorty().empty(); + } + + void MakeEmpty() { + shorty_ = {}; + } + + private: + uint32_t flags_; + std::string_view shorty_; +}; + +template <typename Value> +class JniStubKeyEmpty { + public: + bool IsEmpty(const std::pair<JniStubKey, Value>& pair) const { + return pair.first.IsEmpty(); + } + + void MakeEmpty(std::pair<JniStubKey, Value>& pair) { + pair.first.MakeEmpty(); + } +}; + +using JniStubKeyHashFunction = size_t (*)(const JniStubKey& key); + +class JniStubKeyHash { + public: + EXPORT explicit JniStubKeyHash(InstructionSet isa); + + size_t operator()(const JniStubKey& key) const { + return hash_func_(key); + } + + private: + JniStubKeyHashFunction hash_func_; +}; + +using JniStubKeyEqualsFunction = bool (*)(const JniStubKey& lhs, const JniStubKey& rhs); + +class JniStubKeyEquals { + public: + EXPORT explicit JniStubKeyEquals(InstructionSet isa); + + bool operator()(const JniStubKey& lhs, const JniStubKey& rhs) const { + return equals_func_(lhs, rhs); + } + + private: + JniStubKeyEqualsFunction equals_func_; +}; + +template <typename Value, + typename Alloc = std::allocator<std::pair<JniStubKey, Value>>> +using JniStubHashMap = + HashMap<JniStubKey, Value, JniStubKeyEmpty<Value>, JniStubKeyHash, JniStubKeyEquals>; + +} // namespace art + +#endif // ART_RUNTIME_OAT_JNI_STUB_HASH_MAP_H_ diff --git a/runtime/oat/jni_stub_hash_map_test.cc b/runtime/oat/jni_stub_hash_map_test.cc new file mode 100644 index 0000000000..010a25d860 --- /dev/null +++ b/runtime/oat/jni_stub_hash_map_test.cc @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni_stub_hash_map.h" + +#include <gtest/gtest.h> + +#include <memory> +#include <ostream> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" +#include "arch/instruction_set.h" +#include "art_method.h" +#include "base/array_ref.h" +#include "base/locks.h" +#include "base/utils.h" +#include "class_linker.h" +#include "common_compiler_test.h" +#include "common_compiler_test.cc" +#include "compiler.h" +#include "gc/heap.h" +#include "gc/space/image_space.h" +#include "handle.h" +#include "handle_scope.h" +#include "handle_scope-inl.h" +#include "image.h" +#include "image-inl.h" +#include "jni.h" +#include "mirror/class.h" +#include "mirror/class_loader.h" +#include "mirror/dex_cache.h" +#include "obj_ptr.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" +#include "strstream" + +namespace art HIDDEN { + +// Teach gtest how to print the ArrayRef<const uint8_t>. The customized output is easier used +// for converting to assembly instructions. +static void PrintTo(const ArrayRef<const uint8_t>& array, std::ostream* os) { + *os << "[[["; + for (const uint8_t& element : array) { + *os << " "; + *os << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(element); + } + *os << " ]]]"; +} + +class JniStubHashMapTest : public CommonCompilerTest { + protected: + JniStubHashMapTest() + : jni_stub_hash_map_(JniStubKeyHash(kRuntimeISA), JniStubKeyEquals(kRuntimeISA)) { + if (kRuntimeISA == InstructionSet::kArm64 || kRuntimeISA == InstructionSet::kX86_64) { + // Only arm64 and x86_64 use strict check. + strict_check_ = true; + } else { + // Other archs use loose check. + strict_check_ = false; + } + } + + void SetStrictCheck(bool value) { + strict_check_ = value; + } + + void SetUpForTest() { + ScopedObjectAccess soa(Thread::Current()); + jobject jclass_loader = LoadDex("MyClassNatives"); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader))); + pointer_size_ = class_linker_->GetImagePointerSize(); + ObjPtr<mirror::Class> klass = + class_linker_->FindClass(soa.Self(), "LMyClassNatives;", class_loader); + ASSERT_TRUE(klass != nullptr); + jklass_ = soa.AddLocalReference<jclass>(klass); + } + + void SetBaseMethod(std::string_view base_method_name, std::string_view base_method_sig) { + jni_stub_hash_map_.clear(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + ObjPtr<mirror::Class> klass = soa.Decode<mirror::Class>(jklass_); + base_method_ = klass->FindClassMethod(base_method_name, base_method_sig, pointer_size_); + ASSERT_TRUE(base_method_ != nullptr); + ASSERT_TRUE(base_method_->IsNative()); + + OneCompiledMethodStorage base_method_storage; + StackHandleScope<1> hs(self); + std::unique_ptr<Compiler> compiler( + Compiler::Create(*compiler_options_, &base_method_storage, compiler_kind_)); + const DexFile& dex_file = *base_method_->GetDexFile(); + Handle<mirror::DexCache> dex_cache = + hs.NewHandle(GetClassLinker()->FindDexCache(self, dex_file)); + compiler->JniCompile(base_method_->GetAccessFlags(), + base_method_->GetDexMethodIndex(), + dex_file, + dex_cache); + ArrayRef<const uint8_t> code = base_method_storage.GetCode(); + base_method_code_.assign(code.begin(), code.end()); + + jni_stub_hash_map_.insert(std::make_pair(JniStubKey(base_method_), base_method_)); + } + + void CompareMethod(std::string_view cmp_method_name, std::string_view cmp_method_sig) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + ObjPtr<mirror::Class> klass = soa.Decode<mirror::Class>(jklass_); + ArtMethod* cmp_method = klass->FindClassMethod(cmp_method_name, cmp_method_sig, pointer_size_); + ASSERT_TRUE(cmp_method != nullptr); + ASSERT_TRUE(cmp_method->IsNative()); + + OneCompiledMethodStorage cmp_method_storage; + StackHandleScope<1> hs(self); + std::unique_ptr<Compiler> compiler( + Compiler::Create(*compiler_options_, &cmp_method_storage, compiler_kind_)); + const DexFile& dex_file = *cmp_method->GetDexFile(); + Handle<mirror::DexCache> dex_cache = + hs.NewHandle(GetClassLinker()->FindDexCache(self, dex_file)); + compiler->JniCompile(cmp_method->GetAccessFlags(), + cmp_method->GetDexMethodIndex(), + dex_file, + dex_cache); + + ArrayRef<const uint8_t> method_code = ArrayRef<const uint8_t>(base_method_code_); + ArrayRef<const uint8_t> cmp_method_code = cmp_method_storage.GetCode(); + auto it = jni_stub_hash_map_.find(JniStubKey(cmp_method)); + if (it != jni_stub_hash_map_.end()) { + ASSERT_EQ(method_code, cmp_method_code) + << "base method: " << base_method_->PrettyMethod() << ", compared method: " + << cmp_method->PrettyMethod(); + } else if (strict_check_){ + // If the compared method maps to a different entry, then its compiled JNI stub should be + // also different from the base one. + ASSERT_NE(method_code, cmp_method_code) + << "base method: " << base_method_->PrettyMethod() << ", compared method: " + << cmp_method->PrettyMethod(); + } + } + + bool strict_check_; + JniStubHashMap<ArtMethod*> jni_stub_hash_map_; + PointerSize pointer_size_; + jclass jklass_; + ArtMethod* base_method_; + std::vector<uint8_t> base_method_code_; +}; + +class JniStubHashMapBootImageTest : public CommonRuntimeTest { + protected: + void SetUpRuntimeOptions(RuntimeOptions* options) override { + std::string runtime_args_image; + runtime_args_image = android::base::StringPrintf("-Ximage:%s", GetCoreArtLocation().c_str()); + options->push_back(std::make_pair(runtime_args_image, nullptr)); + } +}; + +TEST_F(JniStubHashMapTest, ReturnType) { + SetUpForTest(); + SetBaseMethod("fooI", "(I)I"); + CompareMethod("fooI_V", "(I)V"); + CompareMethod("fooI_B", "(I)B"); + CompareMethod("fooI_C", "(I)C"); + CompareMethod("fooI_S", "(I)S"); + CompareMethod("fooI_Z", "(I)Z"); + CompareMethod("fooI_J", "(I)J"); + CompareMethod("fooI_F", "(I)F"); + CompareMethod("fooI_D", "(I)D"); + CompareMethod("fooI_L", "(I)Ljava/lang/Object;"); +} + +TEST_F(JniStubHashMapTest, ArgType) { + SetUpForTest(); + SetBaseMethod("sfooI", "(I)I"); + CompareMethod("sfooB", "(B)I"); + CompareMethod("sfooC", "(C)I"); + CompareMethod("sfooS", "(S)I"); + CompareMethod("sfooZ", "(Z)I"); + CompareMethod("sfooL", "(Ljava/lang/Object;)I"); +} + +TEST_F(JniStubHashMapTest, FloatingPointArg) { + SetUpForTest(); + SetBaseMethod("sfooI", "(I)I"); + CompareMethod("sfoo7FI", "(FFFFFFFI)I"); + CompareMethod("sfoo3F5DI", "(FFFDDDDDI)I"); + CompareMethod("sfoo3F6DI", "(FFFDDDDDDI)I"); +} + +TEST_F(JniStubHashMapTest, IntegralArg) { + SetUpForTest(); + SetBaseMethod("fooL", "(Ljava/lang/Object;)I"); + CompareMethod("fooL4I", "(Ljava/lang/Object;IIII)I"); + CompareMethod("fooL5I", "(Ljava/lang/Object;IIIII)I"); + CompareMethod("fooL3IJC", "(Ljava/lang/Object;IIIJC)I"); + CompareMethod("fooL3IJCS", "(Ljava/lang/Object;IIIJCS)I"); +} + +TEST_F(JniStubHashMapTest, StackOffsetMatters) { + SetUpForTest(); + SetBaseMethod("foo7FDF", "(FFFFFFFDF)I"); + CompareMethod("foo9F", "(FFFFFFFFF)I"); + CompareMethod("foo7FIFF", "(FFFFFFFIFF)I"); + SetBaseMethod("foo5IJI", "(IIIIIJI)I"); + CompareMethod("foo7I", "(IIIIIII)I"); + CompareMethod("foo5IFII", "(IIIIIFII)I"); + SetBaseMethod("fooFDL", "(FDLjava/lang/Object;)I"); + CompareMethod("foo2FL", "(FFLjava/lang/Object;)I"); + CompareMethod("foo3FL", "(FFFLjava/lang/Object;)I"); + CompareMethod("foo2FIL", "(FFILjava/lang/Object;)I"); +} + +TEST_F(JniStubHashMapTest, IntLikeRegsMatters) { + SetUpForTest(); + SetBaseMethod("fooICFL", "(ICFLjava/lang/Object;)I"); + CompareMethod("foo2IFL", "(IIFLjava/lang/Object;)I"); + CompareMethod("fooICIL", "(ICILjava/lang/Object;)I"); +} + +TEST_F(JniStubHashMapTest, FastNative) { + SetUpForTest(); + SetBaseMethod("fooI_Fast", "(I)I"); + CompareMethod("fooI_Z_Fast", "(I)Z"); + CompareMethod("fooI_J_Fast", "(I)J"); + SetBaseMethod("fooICFL_Fast", "(ICFLjava/lang/Object;)I"); + CompareMethod("foo2IFL_Fast", "(IIFLjava/lang/Object;)I"); + CompareMethod("fooICIL_Fast", "(ICILjava/lang/Object;)I"); + SetBaseMethod("fooFDL_Fast", "(FDLjava/lang/Object;)I"); + CompareMethod("foo2FL_Fast", "(FFLjava/lang/Object;)I"); + CompareMethod("foo3FL_Fast", "(FFFLjava/lang/Object;)I"); + CompareMethod("foo2FIL_Fast", "(FFILjava/lang/Object;)I"); + SetBaseMethod("foo7F_Fast", "(FFFFFFF)I"); + CompareMethod("foo3F5D_Fast", "(FFFDDDDD)I"); + CompareMethod("foo3F6D_Fast", "(FFFDDDDDD)I"); + SetBaseMethod("fooL5I_Fast", "(Ljava/lang/Object;IIIII)I"); + CompareMethod("fooL3IJC_Fast", "(Ljava/lang/Object;IIIJC)I"); + CompareMethod("fooL3IJCS_Fast", "(Ljava/lang/Object;IIIJCS)I"); +} + +TEST_F(JniStubHashMapTest, CriticalNative) { + SetUpForTest(); + if (kRuntimeISA == InstructionSet::kX86_64) { + // In x86_64, the return type seems be ignored in critical function. + SetStrictCheck(false); + } + SetBaseMethod("returnInt_Critical", "()I"); + CompareMethod("returnDouble_Critical", "()D"); + CompareMethod("returnLong_Critical", "()J"); + SetBaseMethod("foo7F_Critical", "(FFFFFFF)I"); + CompareMethod("foo3F5D_Critical", "(FFFDDDDD)I"); + CompareMethod("foo3F6D_Critical", "(FFFDDDDDD)I"); +} + +TEST_F(JniStubHashMapBootImageTest, BootImageSelfCheck) { + std::vector<gc::space::ImageSpace*> image_spaces = + Runtime::Current()->GetHeap()->GetBootImageSpaces(); + ASSERT_TRUE(!image_spaces.empty()); + for (gc::space::ImageSpace* space : image_spaces) { + const ImageHeader& header = space->GetImageHeader(); + PointerSize ptr_size = class_linker_->GetImagePointerSize(); + auto visitor = [&](ArtMethod& method) REQUIRES_SHARED(Locks::mutator_lock_) { + if (method.IsNative() && !method.IsIntrinsic()) { + const void* boot_jni_stub = class_linker_->FindBootJniStub(JniStubKey(&method)); + if (boot_jni_stub != nullptr) { + const void* cmp_jni_stub = method.GetOatMethodQuickCode(ptr_size); + size_t boot_jni_stub_size = + OatQuickMethodHeader::FromEntryPoint(boot_jni_stub)->GetCodeSize(); + size_t cmp_jni_stub_size = + OatQuickMethodHeader::FromEntryPoint(cmp_jni_stub)->GetCodeSize(); + ArrayRef<const uint8_t> boot_jni_stub_array = ArrayRef( + reinterpret_cast<const uint8_t*>(EntryPointToCodePointer(boot_jni_stub)), + boot_jni_stub_size); + ArrayRef<const uint8_t> cmp_jni_stub_array = ArrayRef( + reinterpret_cast<const uint8_t*>(EntryPointToCodePointer(cmp_jni_stub)), + cmp_jni_stub_size); + ASSERT_EQ(boot_jni_stub_array, cmp_jni_stub_array) + << "method: " << method.PrettyMethod() << ", size = " << cmp_jni_stub_size; + } + } + }; + header.VisitPackedArtMethods(visitor, space->Begin(), ptr_size); + } +} + +} // namespace art diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc index 5d304698bf..83e020090e 100644 --- a/runtime/runtime_image.cc +++ b/runtime/runtime_image.cc @@ -955,7 +955,16 @@ class RuntimeImageHelper { const OatFile* oat_file = image_spaces[0]->GetOatFile(); DCHECK(oat_file != nullptr); const OatHeader& header = oat_file->GetOatHeader(); - copy->SetEntryPointFromQuickCompiledCode(header.GetOatAddress(stub)); + const void* entrypoint = header.GetOatAddress(stub); + if (method->IsNative() && (is_class_initialized || !method->NeedsClinitCheckBeforeCall())) { + // Use boot JNI stub if found. + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + const void* boot_jni_stub = class_linker->FindBootJniStub(method); + if (boot_jni_stub != nullptr) { + entrypoint = boot_jni_stub; + } + } + copy->SetEntryPointFromQuickCompiledCode(entrypoint); if (method->IsNative()) { StubType stub_type = method->IsCriticalNative() diff --git a/runtime/stack.cc b/runtime/stack.cc index 942b155261..81c8a8a9e0 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -840,7 +840,7 @@ void StackVisitor::WalkStack(bool include_transitions) { cur_oat_quick_method_header_ = OatQuickMethodHeader::FromCodePointer(code); } else { // We are sure we are not running GenericJni here. Though the entry point could still be - // GenericJnistub. The entry point is usually JITed or AOT code. It could be lso a + // GenericJnistub. The entry point is usually JITed or AOT code. It could be also a // resolution stub if the class isn't visibly initialized yet. const void* existing_entry_point = method->GetEntryPointFromQuickCompiledCode(); CHECK(existing_entry_point != nullptr); @@ -856,13 +856,22 @@ void StackVisitor::WalkStack(bool include_transitions) { if (code != nullptr) { cur_oat_quick_method_header_ = OatQuickMethodHeader::FromEntryPoint(code); } else { - // This must be a JITted JNI stub frame. For non-debuggable runtimes we only generate - // JIT stubs if there are no AOT stubs for native methods. Since we checked for AOT - // code earlier, we must be running JITed code. For debuggable runtimes we might have - // JIT code even when AOT code is present but we tag SP in JITed JNI stubs + // This may be either a JITed JNI stub frame or boot JNI stub frame. For + // non-debuggable runtimes we will generate JIT stubs if there are no AOT stubs or + // boot stubs for native methods. Since we checked for AOT code earlier, we must be + // running JITed code or boot stub code. For debuggable runtimes we might have JIT + // code even when AOT stub or boot stub is present but we tag SP in JITed JNI stubs // in debuggable runtimes. This case is handled earlier. - CHECK(runtime->GetJit() != nullptr); - code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method); + if (runtime->GetJit() != nullptr) { + code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method); + } + if (code == nullptr) { + // Check if current method uses the boot JNI stub. + const void* boot_jni_stub = class_linker->FindBootJniStub(method); + if (boot_jni_stub != nullptr) { + code = boot_jni_stub; + } + } CHECK(code != nullptr) << method->PrettyMethod(); cur_oat_quick_method_header_ = OatQuickMethodHeader::FromCodePointer(code); } diff --git a/test/667-jit-jni-stub/src/Main.java b/test/667-jit-jni-stub/src/Main.java index 179e186f6a..c0829b5100 100644 --- a/test/667-jit-jni-stub/src/Main.java +++ b/test/667-jit-jni-stub/src/Main.java @@ -24,6 +24,8 @@ public class Main { return; } + // Deoptimize callThrough() to ensure it can be JITed afterwards. + deoptimizeNativeMethod(Main.class, "callThrough"); testCompilationUseAndCollection(); testMixedFramesOnStack(); } @@ -136,7 +138,7 @@ public class Main { if (++count == 50) { throw new Error("TIMEOUT"); } - }; + } } public static void assertTrue(boolean value) { @@ -154,9 +156,10 @@ public class Main { public static void doNothing() { } public static void throwError() { throw new Error(); } - // Note that the callThrough()'s shorty differs from shorties of the other - // native methods used in this test because of the return type `void.` + // Note that the callThrough()'s shorty differs from shorties of the other native methods used + // in this test (except deoptimizeNativeMethod) because of the return type `void.` public native static void callThrough(Class<?> cls, String methodName); + public native static void deoptimizeNativeMethod(Class<?> cls, String methodName); public native static void jitGc(); public native static boolean isNextJitGcFull(); diff --git a/test/MyClassNatives/MyClassNatives.java b/test/MyClassNatives/MyClassNatives.java index d3bf5ff98e..787f53362a 100644 --- a/test/MyClassNatives/MyClassNatives.java +++ b/test/MyClassNatives/MyClassNatives.java @@ -37,6 +37,38 @@ class MyClassNatives { // Normal native native int fooI(int x); // Normal native + native int fooL(Object x); + // Normal native + native void fooI_V(int x); + // Normal native + native byte fooI_B(int x); + // Normal native + native char fooI_C(int x); + // Normal native + native short fooI_S(int x); + // Normal native + native boolean fooI_Z(int x); + // Normal native + native long fooI_J(int x); + // Normal native + native float fooI_F(int x); + // Normal native + native double fooI_D(int x); + // Normal native + native Object fooI_L(int x); + // Normal native + static native int sfooI(int x); + // Normal native + static native int sfooB(byte x); + // Normal native + static native int sfooC(char x); + // Normal native + static native int sfooS(short x); + // Normal native + static native int sfooZ(boolean x); + // Normal native + static native int sfooL(Object x); + // Normal native native int fooII(int x, int y); // Normal native native long fooJJ(long x, long y); @@ -156,6 +188,52 @@ class MyClassNatives { // Normal native static native long returnLong(); + // Normal native + static native int sfoo7FI(float f1, float f2, float f3, float f4, float f5, float f6, + float f7, int i1); + // Normal native + static native int sfoo3F5DI(float f1, float f2, float f3, double d1, double d2, double d3, + double d4, double d5, int i1); + // Normal native + static native int sfoo3F6DI(float f1, float f2, float f3, double d1, double d2, double d3, + double d4, double d5, double d6, int i1); + // Normal native + native int fooL4I(Object o1, int i1, int i2, int i3, int i4); + // Normal native + native int fooL5I(Object o1, int i1, int i2, int i3, int i4, int i5); + // Normal native + native int fooL3IJC(Object o1, int i1, int i2, int i3, long l1, char c1); + // Normal native + native int fooL3IJCS(Object o1, int i1, int i2, int i3, long l1, char c1, short s1); + // Normal native + native int foo9F(float f1, float f2, float f3, float f4, float f5, float f6, float f7, + float f8, float f9); + // Normal native + native int foo7FDF(float f1, float f2, float f3, float f4, float f5, float f6, float f7, + double d1, float f8); + // Normal native + native int foo7FIFF(float f1, float f2, float f3, float f4, float f5, float f6, float f7, + int i1, float f8, float f9); + // Normal native + native int foo7I(int i1, int i2, int i3, int i4, int i5, int i6, int i7); + // Normal native + native int foo5IJI(int i1, int i2, int i3, int i4, int i5, long l1, int i6); + // Normal native + native int foo5IFII(int i1, int i2, int i3, int i4, int i5, float f1, int i6, int i7); + // Normal native + native int foo2FL(float f1, float f2, Object o1); + // Normal native + native int fooFDL(float f1, double f2, Object o1); + // Normal native + native int foo3FL(float f1, float f2, float f3, Object o1); + // Normal native + native int foo2FIL(float f1, float f2, int i1, Object o1); + // Normal native + native int foo2IFL(int i1, int i2, float f1, Object o1); + // Normal native + native int fooICFL(int i1, char c1, float f1, Object o1); + // Normal native + native int fooICIL(int i1, char c1, int i2, Object o1); @FastNative @@ -284,6 +362,38 @@ class MyClassNatives { @FastNative static native long returnLong_Fast(); + @FastNative + native boolean fooI_Z_Fast(int i); + @FastNative + native long fooI_J_Fast(int i); + @FastNative + native int fooICFL_Fast(int i1, char c1, float f1, Object o1); + @FastNative + native int foo2IFL_Fast(int i1, int i2, float f1, Object o1); + @FastNative + native int fooICIL_Fast(int i1, char c1, int i2, Object o1); + @FastNative + native int fooFDL_Fast(float f1, double d1, Object o1); + @FastNative + native int foo2FL_Fast(float f1, float f2, Object o1); + @FastNative + native int foo3FL_Fast(float f1, float f2, float f3, Object o1); + @FastNative + native int foo2FIL_Fast(float f1, float f2, int i1, Object o1); + @FastNative + native int foo7F_Fast(float f1, float f2, float f3, float f4, float f5, float f6, float f7); + @FastNative + native int foo3F5D_Fast(float f1, float f2, float f3, double d1, double d2, double d3, + double d4, double d5); + @FastNative + native int foo3F6D_Fast(float f1, float f2, float f3, double d1, double d2, double d3, + double d4, double d5, double d6); + @FastNative + native int fooL5I_Fast(Object o1, int i1, int i2, int i3, int i4, int i5); + @FastNative + native int fooL3IJC_Fast(Object o1, int i1, int i2, int i3, long l1, char c1); + @FastNative + native int fooL3IJCS_Fast(Object o1, int i1, int i2, int i3, long l1, char c1, short s1); @CriticalNative @@ -326,6 +436,15 @@ class MyClassNatives { @CriticalNative static native long returnLong_Critical(); + @CriticalNative + static native int foo7F_Critical(float f1, float f2, float f3, float f4, float f5, float f6, + float f7); + @CriticalNative + static native int foo3F5D_Critical(float f1, float f2, float f3, double d1, double d2, + double d3, double d4, double d5); + @CriticalNative + static native int foo3F6D_Critical(float f1, float f2, float f3, double d1, double d2, + double d3, double d4, double d5, double d6); // Check for @FastNative/@CriticalNative annotation presence [or lack of presence]. diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc index a385f4c8db..ed0cc3e1c4 100644 --- a/test/common/runtime_state.cc +++ b/test/common/runtime_state.cc @@ -443,6 +443,22 @@ extern "C" JNIEXPORT void JNICALL Java_Main_deoptimizeBootImage(JNIEnv*, jclass) Runtime::Current()->DeoptimizeBootImage(); } +extern "C" JNIEXPORT void JNICALL Java_Main_deoptimizeNativeMethod(JNIEnv* env, + jclass, + jclass cls, + jstring method_name) { + Thread* self = Thread::Current(); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + // Make initialized classes visibly initialized to avoid entrypoint being set to boot JNI stub + // after deoptimize. + class_linker->MakeInitializedClassesVisiblyInitialized(self, /*wait=*/ true); + ScopedObjectAccess soa(self); + ScopedUtfChars chars(env, method_name); + ArtMethod* method = GetMethod(soa, cls, chars); + CHECK(method->IsNative()); + Runtime::Current()->GetInstrumentation()->InitializeMethodsCode(method, /*aot_code=*/ nullptr); +} + extern "C" JNIEXPORT jboolean JNICALL Java_Main_isDebuggable(JNIEnv*, jclass) { return Runtime::Current()->IsJavaDebuggable() ? JNI_TRUE : JNI_FALSE; } |