summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Almaz Mingaleev <mingaleev@google.com> 2024-04-12 07:15:38 +0000
committer Almaz Mingaleev <mingaleev@google.com> 2024-07-02 13:36:08 +0000
commit55f1fed0c404146429d38c41b9dd1647db238ba5 (patch)
tree882436a95c9b9932b7717710c8ce778fa30785c9
parent6a44606e6b27eb04e911cab0adec627014e678ff (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
-rw-r--r--compiler/optimizing/code_generation_data.cc11
-rw-r--r--compiler/optimizing/code_generation_data.h30
-rw-r--r--compiler/optimizing/code_generator.cc13
-rw-r--r--compiler/optimizing/code_generator.h5
-rw-r--r--compiler/optimizing/code_generator_x86_64.cc46
-rw-r--r--compiler/optimizing/code_generator_x86_64.h5
-rw-r--r--compiler/optimizing/instruction_builder.cc7
-rw-r--r--compiler/optimizing/nodes.h9
-rw-r--r--compiler/optimizing/sharpening.cc41
-rw-r--r--compiler/optimizing/sharpening.h8
-rw-r--r--runtime/art_method-inl.h9
-rw-r--r--runtime/jit/jit_code_cache-inl.h86
-rw-r--r--runtime/jit/jit_code_cache.cc91
-rw-r--r--runtime/jit/jit_code_cache.h12
-rw-r--r--runtime/well_known_classes.h2
-rw-r--r--test/2276-const-method-type-gc-cleanup/build.py19
-rw-r--r--test/2276-const-method-type-gc-cleanup/expected-stderr.txt0
-rw-r--r--test/2276-const-method-type-gc-cleanup/expected-stdout.txt20
-rwxr-xr-xtest/2276-const-method-type-gc-cleanup/generate-sources29
-rw-r--r--test/2276-const-method-type-gc-cleanup/info.txt2
-rwxr-xr-xtest/2276-const-method-type-gc-cleanup/javac_post.sh31
-rw-r--r--test/2276-const-method-type-gc-cleanup/run.py18
-rw-r--r--test/2276-const-method-type-gc-cleanup/src-art/Main.java77
-rw-r--r--test/2276-const-method-type-gc-cleanup/src-ex/Worker.java86
-rw-r--r--test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodHandle.java58
-rw-r--r--test/2276-const-method-type-gc-cleanup/src-util/annotations/ConstantMethodType.java38
-rw-r--r--test/2276-const-method-type-gc-cleanup/src-util/transformer/ConstantTransformer.java229
-rw-r--r--test/979-const-method-handle/src/Main.java2
-rw-r--r--test/knownfailures.json3
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",