diff options
author | 2024-04-12 07:15:38 +0000 | |
---|---|---|
committer | 2024-07-02 13:36:08 +0000 | |
commit | 55f1fed0c404146429d38c41b9dd1647db238ba5 (patch) | |
tree | 882436a95c9b9932b7717710c8ce778fa30785c9 | |
parent | 6a44606e6b27eb04e911cab0adec627014e678ff (diff) |
Revert^4 "x86_64: Add JIT support for LoadMethodType."
This reverts commit b63adc919ba9a53f4fbad476356c702845821149.
Bringing back map from ArtMethod to code pointers.
Bug: 297147201
Test: CtsPerfettoTestCases
Test: ./art/test/testrunner/testrunner.py --host --64 --jit -b
Test: ./art/test/testrunner/testrunner.py --host --64 --jit --cms -b
Test: ./art/test/testrunner/testrunner.py --host --64 -b
Test: ./art/test.py --host -b
Change-Id: I6a1c50598ec878393edf8ef895274da79d4ab42d
29 files changed, 960 insertions, 27 deletions
diff --git a/compiler/optimizing/code_generation_data.cc b/compiler/optimizing/code_generation_data.cc index 7b23d46dc5..afc4f62f0f 100644 --- a/compiler/optimizing/code_generation_data.cc +++ b/compiler/optimizing/code_generation_data.cc @@ -20,6 +20,7 @@ #include "intern_table.h" #include "mirror/object-inl.h" #include "runtime.h" +#include "well_known_classes-inl.h" namespace art HIDDEN { @@ -52,6 +53,16 @@ void CodeGenerationData::EmitJitRoots( entry.second = index; ++index; } + for (auto& entry : jit_method_type_roots_) { + // Update the `roots` with the MethodType, and replace the address temporarily + // stored to the index in the table. + uint64_t address = entry.second; + roots->emplace_back(reinterpret_cast<StackReference<mirror::Object>*>(address)); + DCHECK(roots->back() != nullptr); + DCHECK(roots->back()->InstanceOf(WellKnownClasses::java_lang_invoke_MethodType.Get())); + entry.second = index; + ++index; + } } } // namespace art diff --git a/compiler/optimizing/code_generation_data.h b/compiler/optimizing/code_generation_data.h index e78ba8f574..0d4db66ab4 100644 --- a/compiler/optimizing/code_generation_data.h +++ b/compiler/optimizing/code_generation_data.h @@ -23,10 +23,12 @@ #include "base/scoped_arena_allocator.h" #include "base/scoped_arena_containers.h" #include "code_generator.h" +#include "dex/proto_reference.h" #include "dex/string_reference.h" #include "dex/type_reference.h" #include "handle.h" #include "mirror/class.h" +#include "mirror/method_type.h" #include "mirror/object.h" #include "mirror/string.h" #include "stack_map_stream.h" @@ -82,8 +84,24 @@ class CodeGenerationData : public DeletableArenaObject<kArenaAllocCodeGenerator> return jit_class_roots_.size(); } + void ReserveJitMethodTypeRoot(ProtoReference proto_reference, + Handle<mirror::MethodType> method_type) { + jit_method_type_roots_.Overwrite(proto_reference, + reinterpret_cast64<uint64_t>(method_type.GetReference())); + } + + uint64_t GetJitMethodTypeRootIndex(ProtoReference proto_reference) const { + return jit_method_type_roots_.Get(proto_reference); + } + + size_t GetNumberOfJitMethodTypeRoots() const { + return jit_method_type_roots_.size(); + } + size_t GetNumberOfJitRoots() const { - return GetNumberOfJitStringRoots() + GetNumberOfJitClassRoots(); + return GetNumberOfJitStringRoots() + + GetNumberOfJitClassRoots() + + GetNumberOfJitMethodTypeRoots(); } void EmitJitRoots(/*out*/std::vector<Handle<mirror::Object>>* roots) @@ -97,7 +115,9 @@ class CodeGenerationData : public DeletableArenaObject<kArenaAllocCodeGenerator> jit_string_roots_(StringReferenceValueComparator(), allocator_.Adapter(kArenaAllocCodeGenerator)), jit_class_roots_(TypeReferenceValueComparator(), - allocator_.Adapter(kArenaAllocCodeGenerator)) { + allocator_.Adapter(kArenaAllocCodeGenerator)), + jit_method_type_roots_(ProtoReferenceValueComparator(), + allocator_.Adapter(kArenaAllocCodeGenerator)) { slow_paths_.reserve(kDefaultSlowPathsCapacity); } @@ -116,6 +136,12 @@ class CodeGenerationData : public DeletableArenaObject<kArenaAllocCodeGenerator> // Entries are initially added with a pointer in the handle zone, and `EmitJitRoots` // will compute all the indices. ScopedArenaSafeMap<TypeReference, uint64_t, TypeReferenceValueComparator> jit_class_roots_; + + // Maps a ProtoReference (dex_file, proto_index) to the index in the literal table. + // Entries are initially added with a pointer in the handle zone, and `EmitJitRoots` + // will compute all the indices. + ScopedArenaSafeMap<ProtoReference, uint64_t, ProtoReferenceValueComparator> + jit_method_type_roots_; }; } // namespace art diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index 88bd818b0c..51714ef548 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -16,6 +16,7 @@ #include "code_generator.h" #include "base/globals.h" +#include "mirror/method_type.h" #ifdef ART_ENABLE_CODEGEN_arm #include "code_generator_arm_vixl.h" @@ -209,11 +210,23 @@ uint64_t CodeGenerator::GetJitClassRootIndex(TypeReference type_reference) { return code_generation_data_->GetJitClassRootIndex(type_reference); } +void CodeGenerator::ReserveJitMethodTypeRoot(ProtoReference proto_reference, + Handle<mirror::MethodType> method_type) { + DCHECK(code_generation_data_ != nullptr); + code_generation_data_->ReserveJitMethodTypeRoot(proto_reference, method_type); +} + +uint64_t CodeGenerator::GetJitMethodTypeRootIndex(ProtoReference proto_reference) { + DCHECK(code_generation_data_ != nullptr); + return code_generation_data_->GetJitMethodTypeRootIndex(proto_reference); +} + void CodeGenerator::EmitJitRootPatches([[maybe_unused]] uint8_t* code, [[maybe_unused]] const uint8_t* roots_data) { DCHECK(code_generation_data_ != nullptr); DCHECK_EQ(code_generation_data_->GetNumberOfJitStringRoots(), 0u); DCHECK_EQ(code_generation_data_->GetNumberOfJitClassRoots(), 0u); + DCHECK_EQ(code_generation_data_->GetNumberOfJitMethodTypeRoots(), 0u); } uint32_t CodeGenerator::GetArrayLengthOffset(HArrayLength* array_length) { diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index aec7b45a1a..950bae5c8f 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -29,10 +29,12 @@ #include "base/memory_region.h" #include "base/pointer_size.h" #include "class_root.h" +#include "dex/proto_reference.h" #include "dex/string_reference.h" #include "dex/type_reference.h" #include "graph_visualizer.h" #include "locations.h" +#include "mirror/method_type.h" #include "nodes.h" #include "oat/oat_quick_method_header.h" #include "optimizing_compiler_stats.h" @@ -834,6 +836,9 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { uint64_t GetJitStringRootIndex(StringReference string_reference); void ReserveJitClassRoot(TypeReference type_reference, Handle<mirror::Class> klass); uint64_t GetJitClassRootIndex(TypeReference type_reference); + void ReserveJitMethodTypeRoot(ProtoReference proto_reference, + Handle<mirror::MethodType> method_type); + uint64_t GetJitMethodTypeRootIndex(ProtoReference proto_reference); // Emit the patches assocatied with JIT roots. Only applies to JIT compiled code. virtual void EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data); diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index e2b4344be9..f61bb39ccc 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -35,6 +35,7 @@ #include "lock_word.h" #include "mirror/array-inl.h" #include "mirror/class-inl.h" +#include "mirror/method_type.h" #include "mirror/object_reference.h" #include "mirror/var_handle.h" #include "optimizing/nodes.h" @@ -1628,6 +1629,7 @@ CodeGeneratorX86_64::CodeGeneratorX86_64(HGraph* graph, boot_image_other_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)), jit_string_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)), jit_class_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)), + jit_method_type_patches_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)), fixups_to_jump_tables_(graph->GetAllocator()->Adapter(kArenaAllocCodeGenerator)) { AddAllocatedRegister(Location::RegisterLocation(kFakeReturnRegister)); } @@ -6824,20 +6826,31 @@ void InstructionCodeGeneratorX86_64::VisitLoadMethodHandle(HLoadMethodHandle* lo codegen_->GenerateLoadMethodHandleRuntimeCall(load); } +Label* CodeGeneratorX86_64::NewJitRootMethodTypePatch(const DexFile& dex_file, + dex::ProtoIndex proto_index, + Handle<mirror::MethodType> handle) { + ReserveJitMethodTypeRoot(ProtoReference(&dex_file, proto_index), handle); + // Add a patch entry and return the label. + jit_method_type_patches_.emplace_back(&dex_file, proto_index.index_); + PatchInfo<Label>* info = &jit_method_type_patches_.back(); + return &info->label; +} + void LocationsBuilderX86_64::VisitLoadMethodType(HLoadMethodType* load) { LocationSummary* locations = new (GetGraph()->GetAllocator()) LocationSummary(load, LocationSummary::kCallOnSlowPath); if (load->GetLoadKind() == HLoadMethodType::LoadKind::kRuntimeCall) { - Location location = Location::RegisterLocation(RAX); - CodeGenerator::CreateLoadMethodTypeRuntimeCallLocationSummary(load, location, location); + Location location = Location::RegisterLocation(RAX); + CodeGenerator::CreateLoadMethodTypeRuntimeCallLocationSummary(load, location, location); } else { - DCHECK_EQ(load->GetLoadKind(), HLoadMethodType::LoadKind::kBssEntry); locations->SetOut(Location::RequiresRegister()); - if (codegen_->EmitNonBakerReadBarrier()) { - // For non-Baker read barrier we have a temp-clobbering call. - } else { - // Rely on the pResolveMethodType to save everything. - locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves()); + if (load->GetLoadKind() == HLoadMethodType::LoadKind::kBssEntry) { + if (codegen_->EmitNonBakerReadBarrier()) { + // For non-Baker read barrier we have a temp-clobbering call. + } else { + // Rely on the pResolveMethodType to save everything. + locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves()); + } } } } @@ -6864,6 +6877,17 @@ void InstructionCodeGeneratorX86_64::VisitLoadMethodType(HLoadMethodType* load) __ Bind(slow_path->GetExitLabel()); return; } + case HLoadMethodType::LoadKind::kJitTableAddress: { + Address address = Address::Absolute(CodeGeneratorX86_64::kPlaceholder32BitOffset, + /* no_rip= */ true); + Handle<mirror::MethodType> method_type = load->GetMethodType(); + DCHECK(method_type != nullptr); + Label* fixup_label = codegen_->NewJitRootMethodTypePatch( + load->GetDexFile(), load->GetProtoIndex(), method_type); + GenerateGcRootFieldLoad( + load, out_loc, address, fixup_label, codegen_->GetCompilerReadBarrierOption()); + return; + } default: DCHECK_EQ(load->GetLoadKind(), HLoadMethodType::LoadKind::kRuntimeCall); codegen_->GenerateLoadMethodTypeRuntimeCall(load); @@ -8543,6 +8567,12 @@ void CodeGeneratorX86_64::EmitJitRootPatches(uint8_t* code, const uint8_t* roots uint64_t index_in_table = GetJitClassRootIndex(type_reference); PatchJitRootUse(code, roots_data, info, index_in_table); } + + for (const PatchInfo<Label>& info : jit_method_type_patches_) { + ProtoReference proto_reference(info.target_dex_file, dex::ProtoIndex(info.offset_or_index)); + uint64_t index_in_table = GetJitMethodTypeRootIndex(proto_reference); + PatchJitRootUse(code, roots_data, info, index_in_table); + } } bool LocationsBuilderX86_64::CpuHasAvxFeatureFlag() { diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index 81c8ead32e..ddeb33a261 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -546,6 +546,9 @@ class CodeGeneratorX86_64 : public CodeGenerator { Label* NewJitRootClassPatch(const DexFile& dex_file, dex::TypeIndex type_index, Handle<mirror::Class> handle); + Label* NewJitRootMethodTypePatch(const DexFile& dex_file, + dex::ProtoIndex proto_index, + Handle<mirror::MethodType> method_type); void LoadBootImageAddress(CpuRegister reg, uint32_t boot_image_reference); void LoadIntrinsicDeclaringClass(CpuRegister reg, HInvoke* invoke); @@ -765,6 +768,8 @@ class CodeGeneratorX86_64 : public CodeGenerator { ArenaDeque<PatchInfo<Label>> jit_string_patches_; // Patches for class literals in JIT compiled code. ArenaDeque<PatchInfo<Label>> jit_class_patches_; + // Patches for method type in JIT compiled code. + ArenaDeque<PatchInfo<Label>> jit_method_type_patches_; // Fixups for jump tables need to be handled specially. ArenaVector<JumpTableRIPFixup*> fixups_to_jump_tables_; diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index 3a64769a8b..c97c78ca17 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -2720,9 +2720,10 @@ void HInstructionBuilder::BuildLoadMethodType(dex::ProtoIndex proto_index, uint3 const DexFile& dex_file = *dex_compilation_unit_->GetDexFile(); HLoadMethodType* load_method_type = new (allocator_) HLoadMethodType(graph_->GetCurrentMethod(), proto_index, dex_file, dex_pc); - if (!code_generator_->GetCompilerOptions().IsJitCompiler()) { - load_method_type->SetLoadKind(HLoadMethodType::LoadKind::kBssEntry); - } + HSharpening::ProcessLoadMethodType(load_method_type, + code_generator_, + *dex_compilation_unit_, + graph_->GetHandleCache()->GetHandles()); AppendInstruction(load_method_type); } diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 1e3aca64db..825134497d 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -7218,6 +7218,8 @@ class HLoadMethodType final : public HInstruction { enum class LoadKind { // Load from an entry in the .bss section using a PC-relative load. kBssEntry, + // Load from the root table associated with the JIT compiled method. + kJitTableAddress, // Load using a single runtime call. kRuntimeCall, @@ -7254,6 +7256,10 @@ class HLoadMethodType final : public HInstruction { dex::ProtoIndex GetProtoIndex() const { return proto_index_; } + Handle<mirror::MethodType> GetMethodType() const { return method_type_; } + + void SetMethodType(Handle<mirror::MethodType> method_type) { method_type_ = method_type; } + const DexFile& GetDexFile() const { return dex_file_; } static SideEffects SideEffectsForArchRuntimeCalls() { @@ -7283,6 +7289,8 @@ class HLoadMethodType final : public HInstruction { const dex::ProtoIndex proto_index_; const DexFile& dex_file_; + + Handle<mirror::MethodType> method_type_; }; std::ostream& operator<<(std::ostream& os, HLoadMethodType::LoadKind rhs); @@ -7293,6 +7301,7 @@ inline void HLoadMethodType::SetLoadKind(LoadKind load_kind) { DCHECK(GetBlock() == nullptr); DCHECK(GetEnvironment() == nullptr); DCHECK_EQ(GetLoadKind(), LoadKind::kRuntimeCall); + DCHECK_IMPLIES(GetLoadKind() == LoadKind::kJitTableAddress, GetMethodType() != nullptr); SetPackedField<LoadKindField>(load_kind); } diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc index cb94491b8e..1b6a9fb601 100644 --- a/compiler/optimizing/sharpening.cc +++ b/compiler/optimizing/sharpening.cc @@ -471,4 +471,45 @@ void HSharpening::ProcessLoadString( load_string->SetLoadKind(load_kind); } +void HSharpening::ProcessLoadMethodType( + HLoadMethodType* load_method_type, + CodeGenerator* codegen, + const DexCompilationUnit& dex_compilation_unit, + VariableSizedHandleScope* handles) { + const CompilerOptions& compiler_options = codegen->GetCompilerOptions(); + + HLoadMethodType::LoadKind desired_load_kind = static_cast<HLoadMethodType::LoadKind>(-1); + + if (compiler_options.IsJitCompiler()) { + DCHECK(!compiler_options.GetCompilePic()); + Runtime* runtime = Runtime::Current(); + ClassLinker* class_linker = runtime->GetClassLinker(); + ScopedObjectAccess soa(Thread::Current()); + ObjPtr<mirror::MethodType> method_type = + class_linker->ResolveMethodType(Thread::Current(), + load_method_type->GetProtoIndex(), + dex_compilation_unit.GetDexCache(), + dex_compilation_unit.GetClassLoader()); + + if (method_type != nullptr) { + load_method_type->SetMethodType(handles->NewHandle(method_type)); + desired_load_kind = HLoadMethodType::LoadKind::kJitTableAddress; + } else { + DCHECK_EQ(load_method_type->GetLoadKind(), HLoadMethodType::LoadKind::kRuntimeCall); + desired_load_kind = HLoadMethodType::LoadKind::kRuntimeCall; + Thread::Current()->ClearException(); + } + } else { + if (compiler_options.GetCompilePic()) { + desired_load_kind = HLoadMethodType::LoadKind::kBssEntry; + } else { + // Test configuration, do not sharpen. + desired_load_kind = HLoadMethodType::LoadKind::kRuntimeCall; + } + } + + DCHECK_NE(desired_load_kind, static_cast<HLoadMethodType::LoadKind>(-1)); + load_method_type->SetLoadKind(desired_load_kind); +} + } // namespace art diff --git a/compiler/optimizing/sharpening.h b/compiler/optimizing/sharpening.h index 6dfe904f27..88d3b2f604 100644 --- a/compiler/optimizing/sharpening.h +++ b/compiler/optimizing/sharpening.h @@ -27,7 +27,7 @@ class CodeGenerator; class DexCompilationUnit; // Utility methods that try to improve the way we dispatch methods, and access -// types and strings. +// types, strings and method types. class HSharpening { public: // Used by the builder and InstructionSimplifier. @@ -54,6 +54,12 @@ class HSharpening { CodeGenerator* codegen, const DexCompilationUnit& dex_compilation_unit, VariableSizedHandleScope* handles); + + // Used by the builder. + static void ProcessLoadMethodType(HLoadMethodType* load_method_type, + CodeGenerator* codegen, + const DexCompilationUnit& dex_compilation_unit, + VariableSizedHandleScope* handles); }; } // namespace art diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index a05d9e5cee..2b88b733f7 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -31,6 +31,8 @@ #include "dex/signature.h" #include "gc_root-inl.h" #include "imtable-inl.h" +#include "jit/jit.h" +#include "jit/jit_code_cache-inl.h" #include "jit/jit_options.h" #include "mirror/class-inl.h" #include "mirror/dex_cache-inl.h" @@ -619,6 +621,13 @@ void ArtMethod::VisitRoots(RootVisitorType& visitor, PointerSize pointer_size) { } } } + + // JIT-ted code can hold references to heap objects like MethodType-s. Visiting them here to + // treat them as strongly reachable. + Runtime* runtime = Runtime::Current(); + if (runtime->GetJit() != nullptr) { + runtime->GetJit()->GetCodeCache()->VisitRootTables(this, visitor); + } } template<typename RootVisitorType> diff --git a/runtime/jit/jit_code_cache-inl.h b/runtime/jit/jit_code_cache-inl.h new file mode 100644 index 0000000000..fab2073e55 --- /dev/null +++ b/runtime/jit/jit_code_cache-inl.h @@ -0,0 +1,86 @@ +/* + * Copyright 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_JIT_JIT_CODE_CACHE_INL_H_ +#define ART_RUNTIME_JIT_JIT_CODE_CACHE_INL_H_ + +#include "jit/jit_code_cache.h" + +#include "base/macros.h" +#include "read_barrier.h" +#include "thread.h" +#include "well_known_classes-inl.h" + +namespace art HIDDEN { + +class ArtMethod; + +namespace jit { + +template<typename RootVisitorType> +EXPORT void JitCodeCache::VisitRootTables(ArtMethod* method, RootVisitorType& visitor) { + if (method->IsNative()) { + return; + } + + Thread* self = Thread::Current(); + ScopedDebugDisallowReadBarriers sddrb(self); + MutexLock mu(self, *Locks::jit_lock_); + + auto code_ptrs_it = method_code_map_reversed_.find(method); + if (code_ptrs_it == method_code_map_reversed_.end()) { + return; + } + + const std::vector<const void*>& code_ptrs = code_ptrs_it->second; + + for (const void* code_ptr : code_ptrs) { + uint32_t number_of_roots = 0; + const uint8_t* root_table = GetRootTable(code_ptr, &number_of_roots); + uint8_t* roots_data = private_region_.IsInDataSpace(root_table) + ? private_region_.GetWritableDataAddress(root_table) + : shared_region_.GetWritableDataAddress(root_table); + GcRoot<mirror::Object>* roots = reinterpret_cast<GcRoot<mirror::Object>*>(roots_data); + for (uint32_t i = 0; i < number_of_roots; ++i) { + // This does not need a read barrier because this is called by GC. + mirror::Object* object = roots[i].Read<kWithoutReadBarrier>(); + if (object == nullptr || + object == Runtime::GetWeakClassSentinel() || + object->IsString<kDefaultVerifyFlags>() || + object->IsClass<kDefaultVerifyFlags>()) { + continue; + } + // We don't need to visit j.l.Class and j.l.String and the only remaining possible + // objects are MethodType-s. + ObjPtr<mirror::Class> method_type_class = + WellKnownClasses::java_lang_invoke_MethodType.Get<kWithoutReadBarrier>(); + ObjPtr<mirror::Class> klass = + object->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>(); + DCHECK(klass == method_type_class || + klass == ReadBarrier::IsMarked(method_type_class.Ptr()) || + ReadBarrier::IsMarked(klass.Ptr()) == method_type_class); + + visitor.VisitRoot(roots[i].AddressWithoutBarrier()); + } + } +} + +} // namespace jit +} // namespace art + +#endif // ART_RUNTIME_JIT_JIT_CODE_CACHE_INL_H_ + + diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 4b69dc5c01..f6c1bf7d67 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -50,6 +50,7 @@ #include "jit/profiling_info.h" #include "jit/jit_scoped_code_cache_write.h" #include "linear_alloc.h" +#include "mirror/method_type.h" #include "oat/oat_file-inl.h" #include "oat/oat_quick_method_header.h" #include "object_callbacks.h" @@ -59,6 +60,7 @@ #include "thread-current-inl.h" #include "thread-inl.h" #include "thread_list.h" +#include "well_known_classes-inl.h" namespace art HIDDEN { namespace jit { @@ -400,16 +402,6 @@ static void DCheckRootsAreValid(const std::vector<Handle<mirror::Object>>& roots } } -static const uint8_t* GetRootTable(const void* code_ptr, uint32_t* number_of_roots = nullptr) { - OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); - uint8_t* data = method_header->GetOptimizedCodeInfoPtr(); - uint32_t roots = GetNumberOfRoots(data); - if (number_of_roots != nullptr) { - *number_of_roots = roots; - } - return data - ComputeRootTableSize(roots); -} - void JitCodeCache::SweepRootTables(IsMarkedVisitor* visitor) { Thread* self = Thread::Current(); ScopedDebugDisallowReadBarriers sddrb(self); @@ -436,13 +428,35 @@ void JitCodeCache::SweepRootTables(IsMarkedVisitor* visitor) { if (new_object != object) { roots[i] = GcRoot<mirror::Object>(new_object); } - } else { + } else if (object->IsClass<kDefaultVerifyFlags>()) { mirror::Object* new_klass = visitor->IsMarked(object); if (new_klass == nullptr) { roots[i] = GcRoot<mirror::Object>(Runtime::GetWeakClassSentinel()); } else if (new_klass != object) { roots[i] = GcRoot<mirror::Object>(new_klass); } + } else { + mirror::Object* new_method_type = visitor->IsMarked(object); + if (kIsDebugBuild) { + if (new_method_type != nullptr) { + // SweepSystemWeaks() is happening in the compaction pause. At that point + // IsMarked(object) returns the moved address, but the content is not there yet. + if (!Runtime::Current()->GetHeap()->IsPerformingUffdCompaction()) { + ObjPtr<mirror::Class> method_type_class = + WellKnownClasses::java_lang_invoke_MethodType.Get<kWithoutReadBarrier>(); + + CHECK_EQ((new_method_type->GetClass<kVerifyNone, kWithoutReadBarrier>()), + method_type_class.Ptr()); + } + } + } + if (new_method_type == nullptr) { + roots[i] = nullptr; + } else if (new_method_type != object) { + // References are updated in VisitRootTables. Reaching this means that ArtMethod is no + // longer reachable. + roots[i] = GcRoot<mirror::Object>(new_method_type); + } } } } @@ -569,6 +583,7 @@ void JitCodeCache::RemoveMethodsIn(Thread* self, const LinearAlloc& alloc) { VLOG(jit) << "JIT removed " << it->second->PrettyMethod() << ": " << it->first; zombie_code_.erase(it->first); processed_zombie_code_.erase(it->first); + method_code_map_reversed_.erase(it->second); it = method_code_map_.erase(it); } else { ++it; @@ -614,6 +629,16 @@ void JitCodeCache::WaitUntilInlineCacheAccessible(Thread* self) { } } +const uint8_t* JitCodeCache::GetRootTable(const void* code_ptr, uint32_t* number_of_roots) { + OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); + uint8_t* data = method_header->GetOptimizedCodeInfoPtr(); + uint32_t num_roots = GetNumberOfRoots(data); + if (number_of_roots != nullptr) { + *number_of_roots = num_roots; + } + return data - ComputeRootTableSize(num_roots); +} + void JitCodeCache::BroadcastForInlineCacheAccess() { Thread* self = Thread::Current(); MutexLock mu(self, *Locks::jit_lock_); @@ -755,6 +780,25 @@ bool JitCodeCache::Commit(Thread* self, } else { ScopedDebugDisallowReadBarriers sddrb(self); method_code_map_.Put(code_ptr, method); + + // Searching for MethodType-s in roots. They need to be treated as strongly reachable while + // the corresponding ArtMethod is not removed. + ObjPtr<mirror::Class> method_type_class = + WellKnownClasses::java_lang_invoke_MethodType.Get<kWithoutReadBarrier>(); + + for (const Handle<mirror::Object>& root : roots) { + ObjPtr<mirror::Class> klass = root->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>(); + if (klass == method_type_class || + klass == ReadBarrier::IsMarked(method_type_class.Ptr()) || + ReadBarrier::IsMarked(klass.Ptr()) == method_type_class) { + auto it = method_code_map_reversed_.FindOrAdd(method, std::vector<const void*>()); + std::vector<const void*>& code_ptrs = it->second; + + DCHECK(std::find(code_ptrs.begin(), code_ptrs.end(), code_ptr) == code_ptrs.end()); + it->second.emplace_back(code_ptr); + break; + } + } } if (compilation_kind == CompilationKind::kOsr) { ScopedDebugDisallowReadBarriers sddrb(self); @@ -859,6 +903,7 @@ bool JitCodeCache::RemoveMethodLocked(ArtMethod* method, bool release_memory) { ++it; } } + method_code_map_reversed_.erase(method); auto osr_it = osr_code_map_.find(method); if (osr_it != osr_code_map_.end()) { @@ -903,6 +948,13 @@ void JitCodeCache::MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_meth it.second = new_method; } } + + auto node = method_code_map_reversed_.extract(old_method); + if (!node.empty()) { + node.key() = new_method; + method_code_map_reversed_.insert(std::move(node)); + } + // Update osr_code_map_ to point to the new method. auto code_map = osr_code_map_.find(old_method); if (code_map != osr_code_map_.end()) { @@ -1108,6 +1160,23 @@ void JitCodeCache::RemoveUnmarkedCode(Thread* self) { } else { OatQuickMethodHeader* header = OatQuickMethodHeader::FromCodePointer(code_ptr); method_headers.insert(header); + + auto method_it = method_code_map_.find(header->GetCode()); + + if (method_it != method_code_map_.end()) { + ArtMethod* method = method_it->second; + auto code_ptrs_it = method_code_map_reversed_.find(method); + + if (code_ptrs_it != method_code_map_reversed_.end()) { + std::vector<const void*>& code_ptrs = code_ptrs_it->second; + RemoveElement(code_ptrs, code_ptr); + + if (code_ptrs.empty()) { + method_code_map_reversed_.erase(code_ptrs_it); + } + } + } + method_code_map_.erase(header->GetCode()); VLOG(jit) << "JIT removed " << *it; it = processed_zombie_code_.erase(it); diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index 3dd57121ca..8dc13cd14c 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -25,6 +25,7 @@ #include <unordered_set> #include <vector> +#include "android-base/thread_annotations.h" #include "base/arena_containers.h" #include "base/array_ref.h" #include "base/atomic.h" @@ -356,6 +357,11 @@ class JitCodeCache { bool IsOsrCompiled(ArtMethod* method) REQUIRES(!Locks::jit_lock_); + // Visit GC roots (except j.l.Class and j.l.String) held by JIT-ed code. + template<typename RootVisitorType> + EXPORT void VisitRootTables(ArtMethod* method, + RootVisitorType& visitor) NO_THREAD_SAFETY_ANALYSIS; + void SweepRootTables(IsMarkedVisitor* visitor) REQUIRES(!Locks::jit_lock_) REQUIRES_SHARED(Locks::mutator_lock_); @@ -508,6 +514,8 @@ class JitCodeCache { REQUIRES(!Locks::jit_lock_) REQUIRES_SHARED(Locks::mutator_lock_); + EXPORT const uint8_t* GetRootTable(const void* code_ptr, uint32_t* number_of_roots = nullptr); + class JniStubKey; class JniStubData; @@ -548,6 +556,10 @@ class JitCodeCache { // Holds compiled code associated to the ArtMethod. SafeMap<const void*, ArtMethod*> method_code_map_ GUARDED_BY(Locks::jit_lock_); + // Subset of `method_code_map_`, but keyed by `ArtMethod*`. Used to treat certain + // objects (like `MethodType`-s) as strongly reachable from the corresponding ArtMethod. + SafeMap<ArtMethod*, std::vector<const void*>> method_code_map_reversed_ + GUARDED_BY(Locks::jit_lock_); // Holds compiled code associated to the ArtMethod. Used when pre-jitting // methods whose entrypoints have the resolution stub. diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index bd8bbe0108..f29daad204 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -251,6 +251,8 @@ struct EXPORT WellKnownClasses { java_lang_StackOverflowError; static constexpr ClassFromField<&java_lang_Thread_daemon> java_lang_Thread; static constexpr ClassFromField<&java_lang_ThreadGroup_groups> java_lang_ThreadGroup; + static constexpr ClassFromMethod<&java_lang_invoke_MethodType_makeImpl> + java_lang_invoke_MethodType; static constexpr ClassFromMethod<&java_lang_reflect_InvocationTargetException_init> java_lang_reflect_InvocationTargetException; static constexpr ClassFromMethod<&java_lang_reflect_Parameter_init> diff --git a/test/2276-const-method-type-gc-cleanup/build.py b/test/2276-const-method-type-gc-cleanup/build.py new file mode 100644 index 0000000000..021c3f0827 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/build.py @@ -0,0 +1,19 @@ +# +# 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. + + +def build(ctx): + ctx.bash("./generate-sources") + ctx.default_build(api_level="const-method-type") diff --git a/test/2276-const-method-type-gc-cleanup/expected-stderr.txt b/test/2276-const-method-type-gc-cleanup/expected-stderr.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/expected-stderr.txt diff --git a/test/2276-const-method-type-gc-cleanup/expected-stdout.txt b/test/2276-const-method-type-gc-cleanup/expected-stdout.txt new file mode 100644 index 0000000000..f682c54e48 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/expected-stdout.txt @@ -0,0 +1,20 @@ +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded +doWork() +ClassLoader was unloaded diff --git a/test/2276-const-method-type-gc-cleanup/generate-sources b/test/2276-const-method-type-gc-cleanup/generate-sources new file mode 100755 index 0000000000..6ac92e3788 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/generate-sources @@ -0,0 +1,29 @@ +#!/bin/bash +# +# Copyright 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. + +# make us exit on a failure +set -e + +export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.6.jar" + +# Build the transformer to apply to compiled classes. +mkdir classes +${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d classes $(find src-util -name '*.java') +${SOONG_ZIP} --jar -o transformer.jar -C classes -D classes +rm -rf classes + +# Add annotation src files to our compiler inputs. +cp -r src-util/annotations src-ex/ diff --git a/test/2276-const-method-type-gc-cleanup/info.txt b/test/2276-const-method-type-gc-cleanup/info.txt new file mode 100644 index 0000000000..4d6d3790e8 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/info.txt @@ -0,0 +1,2 @@ +Calls a method with const-method-type instructions which will be JIT-ed from a class soon to be +garbage collected. diff --git a/test/2276-const-method-type-gc-cleanup/javac_post.sh b/test/2276-const-method-type-gc-cleanup/javac_post.sh new file mode 100755 index 0000000000..d0699d3cd7 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/javac_post.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# +# 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. + +set -e + +export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.6.jar" + +# Move original classes to intermediate location. +mv $1 $1-intermediate-classes +mkdir $1 + +# Transform intermediate classes. +transformer_args="-cp ${ASM_JAR}:$PWD/transformer.jar transformer.ConstantTransformer" +for class in $1-intermediate-classes/*.class ; do + transformed_class=$1/$(basename ${class}) + ${JAVA:-java} ${transformer_args} ${class} ${transformed_class} +done + diff --git a/test/2276-const-method-type-gc-cleanup/run.py b/test/2276-const-method-type-gc-cleanup/run.py new file mode 100644 index 0000000000..29fe6fe1a7 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/run.py @@ -0,0 +1,18 @@ +#!/bin/bash +# +# 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. + +def run(ctx, args): + ctx.default_run(args, secondary_app_image=False) diff --git a/test/2276-const-method-type-gc-cleanup/src-art/Main.java b/test/2276-const-method-type-gc-cleanup/src-art/Main.java new file mode 100644 index 0000000000..8a44687ae0 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/src-art/Main.java @@ -0,0 +1,77 @@ +/* + * 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. + */ + +import java.lang.invoke.MethodType; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class Main { + private static final String DEX_FILE = + System.getenv("DEX_LOCATION") + "/2276-const-method-type-gc-cleanup-ex.jar"; + private static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path"); + + public static void main(String[] args) throws Throwable { + Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader"); + if (pathClassLoader == null) { + throw new AssertionError("Couldn't find path class loader class"); + } + Constructor<?> constructor = + pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class); + + // Identical to Worker.returnStringMethodType() and is captured by JIT-ed code. + MethodType returnStringMethodType = MethodType.methodType(String.class); + + for (int i = 0; i < 10; ++i) { + callDoWork(constructor); + } + + Reference.reachabilityFence(returnStringMethodType); + } + + private static void callDoWork(Constructor constructor) throws Throwable { + ClassLoader loader = (ClassLoader) constructor.newInstance( + DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); + + Class workerClass = loader.loadClass("Worker"); + + if (workerClass.getClassLoader() != loader) { + throw new AssertionError("The class was loaded by a wrong ClassLoader"); + } + + Method m = workerClass.getDeclaredMethod("doWork"); + m.invoke(null); + + WeakReference loaderRef = new WeakReference(loader); + + m = null; + workerClass = null; + loader = null; + + doUnload(); + + if (loaderRef.refersTo(null)) { + System.out.println("ClassLoader was unloaded"); + } + } + + private static void doUnload() { + for (int i = 0; i < 5; ++i) { + Runtime.getRuntime().gc(); + } + } +} diff --git a/test/2276-const-method-type-gc-cleanup/src-ex/Worker.java b/test/2276-const-method-type-gc-cleanup/src-ex/Worker.java new file mode 100644 index 0000000000..533ea4547c --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/src-ex/Worker.java @@ -0,0 +1,86 @@ +/* + * 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. + */ + +import annotations.ConstantMethodType; +import annotations.ConstantMethodHandle; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; + +public class Worker { + + private static final int ITERATIONS_FOR_JIT = 30_000; + + private static void unreachable() { + throw new Error("unreachable!"); + } + + private static void assertSame(Object lhs, Object rhs) { + if (lhs == rhs) { + throw new AssertionError(lhs + " is not equal to " + rhs); + } + } + + private static void assertNonNull(Object object) { + if (object == null) { + throw new AssertionError("object is null"); + } + } + + public String workerToString(Worker worker) { + return worker.toString(); + } + + @ConstantMethodType( + returnType = String.class, + parameterTypes = {Worker.class}) + private static MethodType methodType() { + unreachable(); + return null; + } + + @ConstantMethodType( + returnType = String.class, + parameterTypes = {}) + private static MethodType returnStringMethodType() { + unreachable(); + return null; + } + + @ConstantMethodHandle( + kind = ConstantMethodHandle.INVOKE_VIRTUAL, + owner = "Worker", + fieldOrMethodName = "workerToString", + descriptor = "(LWorker;)Ljava/lang/String;") + private static MethodHandle methodHandle() { + unreachable(); + return null; + } + + public static void doWork() { + System.out.println("doWork()"); + for (int i = 0; i < ITERATIONS_FOR_JIT; ++i) { + MethodType methodType = methodType(); + MethodHandle methodHandle = methodHandle(); + + assertNonNull(methodType); + assertNonNull(methodHandle); + assertSame(methodType, methodHandle.type()); + + assertNonNull(returnStringMethodType()); + } + } +} diff --git a/test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodHandle.java b/test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodHandle.java new file mode 100644 index 0000000000..40785ebc6e --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodHandle.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 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 annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation can be set on method to specify that if this method + * is statically invoked then the invocation is replaced by a + * load-constant bytecode with the MethodHandle constant described by + * the annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ConstantMethodHandle { + /* Method handle kinds */ + public static final int STATIC_PUT = 0; + public static final int STATIC_GET = 1; + public static final int INSTANCE_PUT = 2; + public static final int INSTANCE_GET = 3; + public static final int INVOKE_STATIC = 4; + public static final int INVOKE_VIRTUAL = 5; + public static final int INVOKE_SPECIAL = 6; + public static final int NEW_INVOKE_SPECIAL = 7; + public static final int INVOKE_INTERFACE = 8; + + /** Kind of method handle. */ + int kind(); + + /** Class name owning the field or method. */ + String owner(); + + /** The field or method name addressed by the MethodHandle. */ + String fieldOrMethodName(); + + /** Descriptor for the field (type) or method (method-type) */ + String descriptor(); + + /** Whether the owner is an interface. */ + boolean ownerIsInterface() default false; +} diff --git a/test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodType.java b/test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodType.java new file mode 100644 index 0000000000..c89fa013fe --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodType.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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 annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation can be set on method to specify that if this method + * is statically invoked then the invocation is replaced by a + * load-constant bytecode with the MethodType constant described by + * the annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ConstantMethodType { + /** Return type of method() or field getter() */ + Class<?> returnType() default void.class; + + /** Types of parameters for method or field setter() */ + Class<?>[] parameterTypes() default {}; +} diff --git a/test/2276-const-method-type-gc-cleanup/src-util/transformer/ConstantTransformer.java b/test/2276-const-method-type-gc-cleanup/src-util/transformer/ConstantTransformer.java new file mode 100644 index 0000000000..771a2671c3 --- /dev/null +++ b/test/2276-const-method-type-gc-cleanup/src-util/transformer/ConstantTransformer.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2018 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 transformer; + +import annotations.ConstantMethodHandle; +import annotations.ConstantMethodType; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * Class for transforming invoke static bytecodes into constant method handle loads and and constant + * method type loads. + * + * <p>When a parameterless private static method returning a MethodHandle is defined and annotated + * with {@code ConstantMethodHandle}, this transformer will replace static invocations of the method + * with a load constant bytecode with a method handle in the constant pool. + * + * <p>Suppose a method is annotated as: <code> + * @ConstantMethodHandle( + * kind = ConstantMethodHandle.STATIC_GET, + * owner = "java/lang/Math", + * fieldOrMethodName = "E", + * descriptor = "D" + * ) + * private static MethodHandle getMathE() { + * unreachable(); + * return null; + * } + * </code> Then invocations of {@code getMathE} will be replaced by a load from the constant pool + * with the constant method handle described in the {@code ConstantMethodHandle} annotation. + * + * <p>Similarly, a parameterless private static method returning a {@code MethodType} and annotated + * with {@code ConstantMethodType}, will have invocations replaced by a load constant bytecode with + * a method type in the constant pool. + */ +class ConstantTransformer { + static class ConstantBuilder extends ClassVisitor { + private final Map<String, ConstantMethodHandle> constantMethodHandles; + private final Map<String, ConstantMethodType> constantMethodTypes; + + ConstantBuilder( + int api, + ClassVisitor cv, + Map<String, ConstantMethodHandle> constantMethodHandles, + Map<String, ConstantMethodType> constantMethodTypes) { + super(api, cv); + this.constantMethodHandles = constantMethodHandles; + this.constantMethodTypes = constantMethodTypes; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + return new MethodVisitor(this.api, mv) { + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == org.objectweb.asm.Opcodes.INVOKESTATIC) { + ConstantMethodHandle constantMethodHandle = constantMethodHandles.get(name); + if (constantMethodHandle != null) { + insertConstantMethodHandle(constantMethodHandle); + return; + } + ConstantMethodType constantMethodType = constantMethodTypes.get(name); + if (constantMethodType != null) { + insertConstantMethodType(constantMethodType); + return; + } + } + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } + + private Type buildMethodType(Class<?> returnType, Class<?>[] parameterTypes) { + Type rType = Type.getType(returnType); + Type[] pTypes = new Type[parameterTypes.length]; + for (int i = 0; i < pTypes.length; ++i) { + pTypes[i] = Type.getType(parameterTypes[i]); + } + return Type.getMethodType(rType, pTypes); + } + + private int getHandleTag(int kind) { + switch (kind) { + case ConstantMethodHandle.STATIC_PUT: + return Opcodes.H_PUTSTATIC; + case ConstantMethodHandle.STATIC_GET: + return Opcodes.H_GETSTATIC; + case ConstantMethodHandle.INSTANCE_PUT: + return Opcodes.H_PUTFIELD; + case ConstantMethodHandle.INSTANCE_GET: + return Opcodes.H_GETFIELD; + case ConstantMethodHandle.INVOKE_STATIC: + return Opcodes.H_INVOKESTATIC; + case ConstantMethodHandle.INVOKE_VIRTUAL: + return Opcodes.H_INVOKEVIRTUAL; + case ConstantMethodHandle.INVOKE_SPECIAL: + return Opcodes.H_INVOKESPECIAL; + case ConstantMethodHandle.NEW_INVOKE_SPECIAL: + return Opcodes.H_NEWINVOKESPECIAL; + case ConstantMethodHandle.INVOKE_INTERFACE: + return Opcodes.H_INVOKEINTERFACE; + } + throw new Error("Unhandled kind " + kind); + } + + private void insertConstantMethodHandle(ConstantMethodHandle constantMethodHandle) { + Handle handle = + new Handle( + getHandleTag(constantMethodHandle.kind()), + constantMethodHandle.owner(), + constantMethodHandle.fieldOrMethodName(), + constantMethodHandle.descriptor(), + constantMethodHandle.ownerIsInterface()); + mv.visitLdcInsn(handle); + } + + private void insertConstantMethodType(ConstantMethodType constantMethodType) { + Type methodType = + buildMethodType( + constantMethodType.returnType(), + constantMethodType.parameterTypes()); + mv.visitLdcInsn(methodType); + } + }; + } + } + + private static void throwAnnotationError( + Method method, Class<?> annotationClass, String reason) { + StringBuilder sb = new StringBuilder(); + sb.append("Error in annotation ") + .append(annotationClass) + .append(" on method ") + .append(method) + .append(": ") + .append(reason); + throw new Error(sb.toString()); + } + + private static void checkMethodToBeReplaced( + Method method, Class<?> annotationClass, Class<?> returnType) { + final int PRIVATE_STATIC = Modifier.STATIC | Modifier.PRIVATE; + if ((method.getModifiers() & PRIVATE_STATIC) != PRIVATE_STATIC) { + throwAnnotationError(method, annotationClass, " method is not private and static"); + } + if (method.getTypeParameters().length != 0) { + throwAnnotationError(method, annotationClass, " method expects parameters"); + } + if (!method.getReturnType().equals(returnType)) { + throwAnnotationError(method, annotationClass, " wrong return type"); + } + } + + private static void transform(Path inputClassPath, Path outputClassPath) throws Throwable { + Path classLoadPath = inputClassPath.toAbsolutePath().getParent(); + URLClassLoader classLoader = + new URLClassLoader(new URL[] {classLoadPath.toUri().toURL()}, + ClassLoader.getSystemClassLoader()); + String inputClassName = inputClassPath.getFileName().toString().replace(".class", ""); + Class<?> inputClass = classLoader.loadClass(inputClassName); + + final Map<String, ConstantMethodHandle> constantMethodHandles = new HashMap<>(); + final Map<String, ConstantMethodType> constantMethodTypes = new HashMap<>(); + + for (Method m : inputClass.getDeclaredMethods()) { + ConstantMethodHandle constantMethodHandle = m.getAnnotation(ConstantMethodHandle.class); + if (constantMethodHandle != null) { + checkMethodToBeReplaced(m, ConstantMethodHandle.class, MethodHandle.class); + constantMethodHandles.put(m.getName(), constantMethodHandle); + continue; + } + + ConstantMethodType constantMethodType = m.getAnnotation(ConstantMethodType.class); + if (constantMethodType != null) { + checkMethodToBeReplaced(m, ConstantMethodType.class, MethodType.class); + constantMethodTypes.put(m.getName(), constantMethodType); + continue; + } + } + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + try (InputStream is = Files.newInputStream(inputClassPath)) { + ClassReader cr = new ClassReader(is); + ConstantBuilder cb = + new ConstantBuilder( + Opcodes.ASM7, cw, constantMethodHandles, constantMethodTypes); + cr.accept(cb, 0); + } + try (OutputStream os = Files.newOutputStream(outputClassPath)) { + os.write(cw.toByteArray()); + } + } + + public static void main(String[] args) throws Throwable { + transform(Paths.get(args[0]), Paths.get(args[1])); + } +} diff --git a/test/979-const-method-handle/src/Main.java b/test/979-const-method-handle/src/Main.java index 72d529b68b..17d5d91c50 100644 --- a/test/979-const-method-handle/src/Main.java +++ b/test/979-const-method-handle/src/Main.java @@ -29,7 +29,7 @@ class Main { * Number of iterations run to attempt to trigger JIT compilation. These tests run on ART and * the RI so they iterate rather than using the ART only native method ensureJitCompiled(). */ - private static final int ITERATIONS_FOR_JIT = 12000; + private static final int ITERATIONS_FOR_JIT = 30000; /** A static field updated by method handle getters and setters. */ private static String name = "default"; diff --git a/test/knownfailures.json b/test/knownfailures.json index e9c32bb2ef..9c54ee0b8a 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -1073,7 +1073,8 @@ "2245-checker-smali-instance-of-comparison", "2251-checker-irreducible-loop-do-not-inline", "2264-throwing-systemcleaner", - "2267-class-implements-itself" + "2267-class-implements-itself", + "2276-const-method-type-gc-cleanup" ], "variant": "jvm", "bug": "b/73888836", |