summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/image_writer.cc4
-rw-r--r--compiler/optimizing/graph_visualizer.cc14
-rw-r--r--compiler/optimizing/induction_var_range.cc6
-rw-r--r--compiler/optimizing/intrinsics_arm_vixl.cc36
-rw-r--r--compiler/optimizing/load_store_analysis.h52
-rw-r--r--compiler/verifier_deps_test.cc10
-rw-r--r--openjdkjvmti/OpenjdkJvmTi.cc6
-rw-r--r--openjdkjvmti/art_jvmti.h4
-rw-r--r--openjdkjvmti/events-inl.h29
-rw-r--r--openjdkjvmti/events.cc25
-rw-r--r--openjdkjvmti/events.h5
-rw-r--r--openjdkjvmti/ti_method.cc34
-rw-r--r--openjdkjvmti/ti_redefine.cc4
-rw-r--r--openjdkjvmti/ti_redefine.h1
-rw-r--r--openjdkjvmti/ti_stack.cc72
-rw-r--r--openjdkjvmti/ti_stack.h37
-rw-r--r--patchoat/patchoat.cc2
-rw-r--r--profman/profman.cc42
-rw-r--r--runtime/arch/arm/quick_entrypoints_arm.S83
-rw-r--r--runtime/arch/arm64/quick_entrypoints_arm64.S55
-rw-r--r--runtime/arch/mips/quick_entrypoints_mips.S10
-rw-r--r--runtime/arch/mips64/quick_entrypoints_mips64.S10
-rw-r--r--runtime/arch/x86/quick_entrypoints_x86.S67
-rw-r--r--runtime/arch/x86_64/quick_entrypoints_x86_64.S68
-rw-r--r--runtime/art_method-inl.h67
-rw-r--r--runtime/art_method.h36
-rw-r--r--runtime/asm_support.h2
-rw-r--r--runtime/class_linker-inl.h6
-rw-r--r--runtime/class_linker.cc38
-rw-r--r--runtime/class_linker_test.cc5
-rw-r--r--runtime/entrypoints/entrypoint_utils-inl.h4
-rw-r--r--runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc6
-rw-r--r--runtime/entrypoints/quick/quick_trampoline_entrypoints.cc39
-rw-r--r--runtime/gc/space/image_space.cc2
-rw-r--r--runtime/generated/asm_support_gen.h22
-rw-r--r--runtime/image.cc2
-rw-r--r--runtime/instrumentation.cc175
-rw-r--r--runtime/instrumentation.h50
-rw-r--r--runtime/instrumentation_test.cc50
-rw-r--r--runtime/interpreter/interpreter.cc66
-rw-r--r--runtime/interpreter/interpreter.h8
-rw-r--r--runtime/interpreter/interpreter_common.cc3
-rw-r--r--runtime/interpreter/interpreter_switch_impl.cc122
-rw-r--r--runtime/interpreter/shadow_frame.h14
-rw-r--r--runtime/jit/profile_compilation_info.cc203
-rw-r--r--runtime/jit/profile_compilation_info.h21
-rw-r--r--runtime/jit/profile_compilation_info_test.cc40
-rw-r--r--runtime/oat_file.cc8
-rw-r--r--runtime/thread.cc33
-rw-r--r--runtime/thread.h15
-rw-r--r--runtime/verifier/method_verifier-inl.h2
-rw-r--r--runtime/verifier/method_verifier.cc71
-rw-r--r--runtime/verifier/method_verifier.h11
-rwxr-xr-xtest/004-JniTest/build26
-rw-r--r--test/004-JniTest/expected.txt2
-rw-r--r--test/004-JniTest/jni_test.cc12
-rw-r--r--test/004-JniTest/src/Main.java32
-rw-r--r--test/1923-frame-pop/expected.txt8
-rw-r--r--test/1923-frame-pop/info.txt3
-rwxr-xr-x[-rw-r--r--]test/1923-frame-pop/run (renamed from test/597-deopt-invoke-stub/run)8
-rw-r--r--test/1923-frame-pop/src/Main.java21
-rw-r--r--test/1923-frame-pop/src/art/Breakpoint.java202
-rw-r--r--test/1923-frame-pop/src/art/FramePop.java25
-rw-r--r--test/1923-frame-pop/src/art/Locals.java121
-rw-r--r--test/1923-frame-pop/src/art/StackTrace.java68
-rw-r--r--test/1923-frame-pop/src/art/Suspension.java30
-rw-r--r--test/1923-frame-pop/src/art/Test1923.java209
-rw-r--r--test/1923-frame-pop/src/art/Trace.java56
-rw-r--r--test/1924-frame-pop-toggle/expected.txt8
-rw-r--r--test/1924-frame-pop-toggle/frame_pop_toggle.cc51
-rw-r--r--test/1924-frame-pop-toggle/info.txt3
-rwxr-xr-x[-rw-r--r--]test/1924-frame-pop-toggle/run (renamed from test/597-deopt-busy-loop/run)8
-rw-r--r--test/1924-frame-pop-toggle/src/Main.java21
-rw-r--r--test/1924-frame-pop-toggle/src/art/Breakpoint.java202
-rw-r--r--test/1924-frame-pop-toggle/src/art/FramePop.java25
-rw-r--r--test/1924-frame-pop-toggle/src/art/Locals.java121
-rw-r--r--test/1924-frame-pop-toggle/src/art/StackTrace.java68
-rw-r--r--test/1924-frame-pop-toggle/src/art/Suspension.java30
-rw-r--r--test/1924-frame-pop-toggle/src/art/Test1924.java212
-rw-r--r--test/1924-frame-pop-toggle/src/art/Trace.java56
-rw-r--r--test/1925-self-frame-pop/expected.txt4
-rw-r--r--test/1925-self-frame-pop/info.txt3
-rwxr-xr-xtest/1925-self-frame-pop/run18
-rw-r--r--test/1925-self-frame-pop/src/Main.java21
-rw-r--r--test/1925-self-frame-pop/src/art/Breakpoint.java202
-rw-r--r--test/1925-self-frame-pop/src/art/FramePop.java25
-rw-r--r--test/1925-self-frame-pop/src/art/Locals.java121
-rw-r--r--test/1925-self-frame-pop/src/art/StackTrace.java68
-rw-r--r--test/1925-self-frame-pop/src/art/Suspension.java30
-rw-r--r--test/1925-self-frame-pop/src/art/Test1925.java168
-rw-r--r--test/1925-self-frame-pop/src/art/Trace.java56
-rw-r--r--test/1926-missed-frame-pop/expected.txt9
-rw-r--r--test/1926-missed-frame-pop/frame_pop_missed.cc55
-rw-r--r--test/1926-missed-frame-pop/info.txt3
-rwxr-xr-xtest/1926-missed-frame-pop/run18
-rw-r--r--test/1926-missed-frame-pop/src/Main.java21
-rw-r--r--test/1926-missed-frame-pop/src/art/Breakpoint.java202
-rw-r--r--test/1926-missed-frame-pop/src/art/FramePop.java25
-rw-r--r--test/1926-missed-frame-pop/src/art/Locals.java121
-rw-r--r--test/1926-missed-frame-pop/src/art/StackTrace.java68
-rw-r--r--test/1926-missed-frame-pop/src/art/Suspension.java30
-rw-r--r--test/1926-missed-frame-pop/src/art/Test1926.java222
-rw-r--r--test/1926-missed-frame-pop/src/art/Trace.java56
-rw-r--r--test/597-deopt-busy-loop/expected.txt2
-rw-r--r--test/597-deopt-busy-loop/info.txt1
-rw-r--r--test/597-deopt-busy-loop/src/Main.java69
-rw-r--r--test/597-deopt-invoke-stub/expected.txt2
-rw-r--r--test/597-deopt-invoke-stub/info.txt1
-rw-r--r--test/597-deopt-invoke-stub/src/Main.java75
-rw-r--r--test/620-checker-bce-intrinsics/src/Main.java14
-rw-r--r--test/623-checker-loop-regressions/src/Main.java27
-rw-r--r--test/954-invoke-polymorphic-verifier/smali/Unresolved.smali2
-rw-r--r--test/Android.bp3
-rw-r--r--test/knownfailures.json14
-rwxr-xr-xtest/run-test5
-rwxr-xr-xtest/testrunner/testrunner.py9
-rw-r--r--test/ti-agent/frame_pop_helper.cc130
-rw-r--r--tools/cpp-define-generator/constant_globals.def4
-rw-r--r--tools/cpp-define-generator/offset_art_method.def (renamed from tools/cpp-define-generator/offset_dexcache.def)2
-rw-r--r--tools/cpp-define-generator/offset_mirror_class.def32
-rw-r--r--tools/cpp-define-generator/offset_mirror_dex_cache.def32
-rw-r--r--tools/cpp-define-generator/offsets_all.def5
122 files changed, 4402 insertions, 998 deletions
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 115e722a75..6ee9cc6056 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -2593,10 +2593,6 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig,
CopyReference(copy->GetDeclaringClassAddressWithoutBarrier(), orig->GetDeclaringClassUnchecked());
- mirror::MethodDexCacheType* orig_resolved_methods =
- orig->GetDexCacheResolvedMethods(target_ptr_size_);
- copy->SetDexCacheResolvedMethods(NativeLocationInImage(orig_resolved_methods), target_ptr_size_);
-
// OatWriter replaces the code_ with an offset value. Here we re-adjust to a pointer relative to
// oat_begin_
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index a20ec3c0db..3035e4657d 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -501,6 +501,20 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor {
StartAttributeStream("field_type") << iset->GetFieldType();
}
+ void VisitStaticFieldGet(HStaticFieldGet* sget) OVERRIDE {
+ StartAttributeStream("field_name") <<
+ sget->GetFieldInfo().GetDexFile().PrettyField(sget->GetFieldInfo().GetFieldIndex(),
+ /* with type */ false);
+ StartAttributeStream("field_type") << sget->GetFieldType();
+ }
+
+ void VisitStaticFieldSet(HStaticFieldSet* sset) OVERRIDE {
+ StartAttributeStream("field_name") <<
+ sset->GetFieldInfo().GetDexFile().PrettyField(sset->GetFieldInfo().GetFieldIndex(),
+ /* with type */ false);
+ StartAttributeStream("field_type") << sset->GetFieldType();
+ }
+
void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* field_access) OVERRIDE {
StartAttributeStream("field_type") << field_access->GetFieldType();
}
diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc
index f35aace3a9..089340e715 100644
--- a/compiler/optimizing/induction_var_range.cc
+++ b/compiler/optimizing/induction_var_range.cc
@@ -87,8 +87,10 @@ static bool IsGEZero(HInstruction* instruction) {
IsGEZero(instruction->InputAt(1));
case Intrinsics::kMathAbsInt:
case Intrinsics::kMathAbsLong:
- // Instruction ABS(x) is >= 0.
- return true;
+ // Instruction ABS(>=0) is >= 0.
+ // NOTE: ABS(minint) = minint prevents assuming
+ // >= 0 without looking at the argument.
+ return IsGEZero(instruction->InputAt(0));
default:
break;
}
diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc
index 76c1410340..d2dc88a73b 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.cc
+++ b/compiler/optimizing/intrinsics_arm_vixl.cc
@@ -331,7 +331,7 @@ static void CreateIntToIntLocations(ArenaAllocator* arena, HInvoke* invoke) {
locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
}
-static void CreateIntToIntLocationsWithOverlap(ArenaAllocator* arena, HInvoke* invoke) {
+static void CreateLongToLongLocationsWithOverlap(ArenaAllocator* arena, HInvoke* invoke) {
LocationSummary* locations = new (arena) LocationSummary(invoke,
LocationSummary::kNoCall,
kIntrinsified);
@@ -383,11 +383,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerNumberOfLeadingZeros(HInvoke* in
}
void IntrinsicLocationsBuilderARMVIXL::VisitLongNumberOfLeadingZeros(HInvoke* invoke) {
- LocationSummary* locations = new (arena_) LocationSummary(invoke,
- LocationSummary::kNoCall,
- kIntrinsified);
- locations->SetInAt(0, Location::RequiresRegister());
- locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
+ CreateLongToLongLocationsWithOverlap(arena_, invoke);
}
void IntrinsicCodeGeneratorARMVIXL::VisitLongNumberOfLeadingZeros(HInvoke* invoke) {
@@ -425,11 +421,7 @@ static void GenNumberOfTrailingZeros(HInvoke* invoke,
}
void IntrinsicLocationsBuilderARMVIXL::VisitIntegerNumberOfTrailingZeros(HInvoke* invoke) {
- LocationSummary* locations = new (arena_) LocationSummary(invoke,
- LocationSummary::kNoCall,
- kIntrinsified);
- locations->SetInAt(0, Location::RequiresRegister());
- locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
+ CreateIntToIntLocations(arena_, invoke);
}
void IntrinsicCodeGeneratorARMVIXL::VisitIntegerNumberOfTrailingZeros(HInvoke* invoke) {
@@ -437,11 +429,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerNumberOfTrailingZeros(HInvoke* i
}
void IntrinsicLocationsBuilderARMVIXL::VisitLongNumberOfTrailingZeros(HInvoke* invoke) {
- LocationSummary* locations = new (arena_) LocationSummary(invoke,
- LocationSummary::kNoCall,
- kIntrinsified);
- locations->SetInAt(0, Location::RequiresRegister());
- locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
+ CreateLongToLongLocationsWithOverlap(arena_, invoke);
}
void IntrinsicCodeGeneratorARMVIXL::VisitLongNumberOfTrailingZeros(HInvoke* invoke) {
@@ -2819,11 +2807,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerReverse(HInvoke* invoke) {
}
void IntrinsicLocationsBuilderARMVIXL::VisitLongReverse(HInvoke* invoke) {
- LocationSummary* locations = new (arena_) LocationSummary(invoke,
- LocationSummary::kNoCall,
- kIntrinsified);
- locations->SetInAt(0, Location::RequiresRegister());
- locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
+ CreateLongToLongLocationsWithOverlap(arena_, invoke);
}
void IntrinsicCodeGeneratorARMVIXL::VisitLongReverse(HInvoke* invoke) {
@@ -2849,11 +2833,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerReverseBytes(HInvoke* invoke) {
}
void IntrinsicLocationsBuilderARMVIXL::VisitLongReverseBytes(HInvoke* invoke) {
- LocationSummary* locations = new (arena_) LocationSummary(invoke,
- LocationSummary::kNoCall,
- kIntrinsified);
- locations->SetInAt(0, Location::RequiresRegister());
- locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
+ CreateLongToLongLocationsWithOverlap(arena_, invoke);
}
void IntrinsicCodeGeneratorARMVIXL::VisitLongReverseBytes(HInvoke* invoke) {
@@ -2982,7 +2962,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerHighestOneBit(HInvoke* invoke) {
}
void IntrinsicLocationsBuilderARMVIXL::VisitLongHighestOneBit(HInvoke* invoke) {
- CreateIntToIntLocationsWithOverlap(arena_, invoke);
+ CreateLongToLongLocationsWithOverlap(arena_, invoke);
}
void IntrinsicCodeGeneratorARMVIXL::VisitLongHighestOneBit(HInvoke* invoke) {
@@ -3047,7 +3027,7 @@ void IntrinsicCodeGeneratorARMVIXL::VisitIntegerLowestOneBit(HInvoke* invoke) {
}
void IntrinsicLocationsBuilderARMVIXL::VisitLongLowestOneBit(HInvoke* invoke) {
- CreateIntToIntLocationsWithOverlap(arena_, invoke);
+ CreateLongToLongLocationsWithOverlap(arena_, invoke);
}
void IntrinsicCodeGeneratorARMVIXL::VisitLongLowestOneBit(HInvoke* invoke) {
diff --git a/compiler/optimizing/load_store_analysis.h b/compiler/optimizing/load_store_analysis.h
index a2c17944f2..02bc254729 100644
--- a/compiler/optimizing/load_store_analysis.h
+++ b/compiler/optimizing/load_store_analysis.h
@@ -461,49 +461,15 @@ class HeapLocationCollector : public HGraphVisitor {
has_heap_stores_ = true;
}
- void VisitNewInstance(HNewInstance* new_instance) OVERRIDE {
- // Any references appearing in the ref_info_array_ so far cannot alias with new_instance.
- CreateReferenceInfoForReferenceType(new_instance);
- }
-
- void VisitNewArray(HNewArray* new_array) OVERRIDE {
- // Any references appearing in the ref_info_array_ so far cannot alias with new_array.
- CreateReferenceInfoForReferenceType(new_array);
- }
-
- void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitInvokeVirtual(HInvokeVirtual* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitInvokeInterface(HInvokeInterface* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitInvokeUnresolved(HInvokeUnresolved* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitInvokePolymorphic(HInvokePolymorphic* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitLoadString(HLoadString* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitPhi(HPhi* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitParameterValue(HParameterValue* instruction) OVERRIDE {
- CreateReferenceInfoForReferenceType(instruction);
- }
-
- void VisitSelect(HSelect* instruction) OVERRIDE {
+ void VisitInstruction(HInstruction* instruction) OVERRIDE {
+ // Any new-instance or new-array cannot alias with references that
+ // pre-exist the new-instance/new-array. We append entries into
+ // ref_info_array_ which keeps track of the order of creation
+ // of reference values since we visit the blocks in reverse post order.
+ //
+ // By default, VisitXXX() (including VisitPhi()) calls VisitInstruction(),
+ // unless VisitXXX() is overridden. VisitInstanceFieldGet() etc. above
+ // also call CreateReferenceInfoForReferenceType() explicitly.
CreateReferenceInfoForReferenceType(instruction);
}
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 65389252e2..5c097da16f 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -624,7 +624,7 @@ TEST_F(VerifierDepsTest, ConstClass_Resolved) {
}
TEST_F(VerifierDepsTest, ConstClass_Unresolved) {
- ASSERT_TRUE(VerifyMethod("ConstClass_Unresolved"));
+ ASSERT_FALSE(VerifyMethod("ConstClass_Unresolved"));
ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
}
@@ -634,7 +634,7 @@ TEST_F(VerifierDepsTest, CheckCast_Resolved) {
}
TEST_F(VerifierDepsTest, CheckCast_Unresolved) {
- ASSERT_TRUE(VerifyMethod("CheckCast_Unresolved"));
+ ASSERT_FALSE(VerifyMethod("CheckCast_Unresolved"));
ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
}
@@ -644,7 +644,7 @@ TEST_F(VerifierDepsTest, InstanceOf_Resolved) {
}
TEST_F(VerifierDepsTest, InstanceOf_Unresolved) {
- ASSERT_TRUE(VerifyMethod("InstanceOf_Unresolved"));
+ ASSERT_FALSE(VerifyMethod("InstanceOf_Unresolved"));
ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
}
@@ -654,12 +654,12 @@ TEST_F(VerifierDepsTest, NewInstance_Resolved) {
}
TEST_F(VerifierDepsTest, NewInstance_Unresolved) {
- ASSERT_TRUE(VerifyMethod("NewInstance_Unresolved"));
+ ASSERT_FALSE(VerifyMethod("NewInstance_Unresolved"));
ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
}
TEST_F(VerifierDepsTest, NewArray_Unresolved) {
- ASSERT_TRUE(VerifyMethod("NewArray_Unresolved"));
+ ASSERT_FALSE(VerifyMethod("NewArray_Unresolved"));
ASSERT_TRUE(HasClass("[LUnresolvedClass;", false));
}
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 1cca36ba2e..6c0d492be6 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -318,12 +318,10 @@ class JvmtiFunctions {
return StackUtil::GetFrameLocation(env, thread, depth, method_ptr, location_ptr);
}
- static jvmtiError NotifyFramePop(jvmtiEnv* env,
- jthread thread ATTRIBUTE_UNUSED,
- jint depth ATTRIBUTE_UNUSED) {
+ static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_generate_frame_pop_events);
- return ERR(NOT_IMPLEMENTED);
+ return StackUtil::NotifyFramePop(env, thread, depth);
}
static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env,
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 71a8d304fb..d74e25b807 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -52,6 +52,7 @@
namespace art {
class ArtField;
class ArtMethod;
+class ShadowFrame;
} // namespace art
namespace openjdkjvmti {
@@ -81,6 +82,7 @@ struct ArtJvmTiEnv : public jvmtiEnv {
// Set of breakpoints is unique to each jvmtiEnv.
std::unordered_set<Breakpoint> breakpoints;
+ std::unordered_set<const art::ShadowFrame*> notify_frames;
ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler);
@@ -235,7 +237,7 @@ const jvmtiCapabilities kPotentialCapabilities = {
.can_maintain_original_method_order = 1,
.can_generate_single_step_events = 1,
.can_generate_exception_events = 0,
- .can_generate_frame_pop_events = 0,
+ .can_generate_frame_pop_events = 1,
.can_generate_breakpoint_events = 1,
.can_suspend = 1,
.can_redefine_any_class = 0,
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index 32dba3e3e1..31edc73ac6 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -244,6 +244,35 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread*
}
}
+// Need to give custom specializations for FramePop since it needs to filter out which particular
+// agents get the event. This specialization gets an extra argument so we can determine which (if
+// any) environments have the frame pop.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFramePop>(
+ art::Thread* thread,
+ JNIEnv* jnienv,
+ jthread jni_thread,
+ jmethodID jmethod,
+ jboolean is_exception,
+ const art::ShadowFrame* frame) const {
+ for (ArtJvmTiEnv* env : envs) {
+ // Search for the frame. Do this before checking if we need to send the event so that we don't
+ // have to deal with use-after-free or the frames being reallocated later.
+ if (env != nullptr && env->notify_frames.erase(frame) != 0) {
+ if (ShouldDispatch<ArtJvmtiEvent::kFramePop>(env, thread)) {
+ // We temporarily clear any pending exceptions so the event can call back into java code.
+ ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+ jnienv->ExceptionClear();
+ auto callback = impl::GetCallback<ArtJvmtiEvent::kFramePop>(env);
+ (*callback)(env, jnienv, jni_thread, jmethod, is_exception);
+ if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+ jnienv->Throw(thr.get());
+ }
+ }
+ }
+ }
+}
+
// Need to give custom specializations for FieldAccess and FieldModification since they need to
// filter out which particular fields agents want to get notified on.
// TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 73e688190d..acef682a13 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -538,6 +538,20 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat
}
}
+ void WatchedFramePop(art::Thread* self, const art::ShadowFrame& frame)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFramePop)) {
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ jboolean is_exception_pending = self->IsExceptionPending();
+ RunEventCallback<ArtJvmtiEvent::kFramePop>(
+ self,
+ jnienv,
+ art::jni::EncodeArtMethod(frame.GetMethod()),
+ is_exception_pending,
+ &frame);
+ }
+ }
+
// Call-back when an exception is thrown.
void ExceptionThrown(art::Thread* self ATTRIBUTE_UNUSED,
art::Handle<art::mirror::Throwable> exception_object ATTRIBUTE_UNUSED)
@@ -582,6 +596,8 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) {
case ArtJvmtiEvent::kBreakpoint:
case ArtJvmtiEvent::kSingleStep:
return art::instrumentation::Instrumentation::kDexPcMoved;
+ case ArtJvmtiEvent::kFramePop:
+ return art::instrumentation::Instrumentation::kWatchedFramePop;
default:
LOG(FATAL) << "Unknown event ";
return 0;
@@ -648,6 +664,15 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
}
return;
}
+ // FramePop can never be disabled once it's been turned on since we would either need to deal
+ // with dangling pointers or have missed events.
+ case ArtJvmtiEvent::kFramePop:
+ if (!enable || (enable && frame_pop_enabled)) {
+ break;
+ } else {
+ SetupTraceListener(method_trace_listener_.get(), event, enable);
+ break;
+ }
case ArtJvmtiEvent::kMethodEntry:
case ArtJvmtiEvent::kMethodExit:
case ArtJvmtiEvent::kFieldAccess:
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index 3d05fa18c7..b49e80c46d 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -223,6 +223,11 @@ class EventHandler {
std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
+
+ // True if frame pop has ever been enabled. Since we store pointers to stack frames we need to
+ // continue to listen to this event even if it has been disabled.
+ // TODO We could remove the listeners once all jvmtiEnvs have drained their shadow-frame vectors.
+ bool frame_pop_enabled;
};
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
index 35f2f0cb07..f99b16758c 100644
--- a/openjdkjvmti/ti_method.cc
+++ b/openjdkjvmti/ti_method.cc
@@ -51,6 +51,7 @@
#include "stack.h"
#include "thread-current-inl.h"
#include "thread_list.h"
+#include "ti_stack.h"
#include "ti_thread.h"
#include "ti_phase.h"
@@ -533,39 +534,6 @@ jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* i
return IsMethodT(env, m, test, is_synthetic_ptr);
}
-struct FindFrameAtDepthVisitor : art::StackVisitor {
- public:
- FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
- REQUIRES_SHARED(art::Locks::mutator_lock_)
- : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
- found_frame_(false),
- cnt_(0),
- depth_(static_cast<size_t>(depth)) { }
-
- bool FoundFrame() {
- return found_frame_;
- }
-
- bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
- if (GetMethod()->IsRuntimeMethod()) {
- return true;
- }
- if (cnt_ == depth_) {
- // We found our frame, exit.
- found_frame_ = true;
- return false;
- } else {
- cnt_++;
- return true;
- }
- }
-
- private:
- bool found_frame_;
- size_t cnt_;
- size_t depth_;
-};
-
class CommonLocalVariableClosure : public art::Closure {
public:
CommonLocalVariableClosure(art::Thread* caller,
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index c679d731fe..1b4e910062 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -1364,7 +1364,6 @@ jvmtiError Redefiner::Run() {
}
void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
- art::ObjPtr<art::mirror::DexCache> new_dex_cache,
const art::DexFile::ClassDef& class_def) {
art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
art::PointerSize image_pointer_size = linker->GetImagePointerSize();
@@ -1396,7 +1395,6 @@ void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class>
method.SetDexMethodIndex(dex_method_idx);
linker->SetEntryPointsToInterpreter(&method);
method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx));
- method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
// Clear all the intrinsics related flags.
method.ClearAccessFlags(art::kAccIntrinsic | (~art::kAccFlagsNotUsedByIntrinsic));
// Notify the jit that this method is redefined.
@@ -1433,7 +1431,7 @@ void Redefiner::ClassRedefinition::UpdateClass(
art::ObjPtr<art::mirror::Object> original_dex_file) {
DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0);
- UpdateMethods(mclass, new_dex_cache, class_def);
+ UpdateMethods(mclass, class_def);
UpdateFields(mclass);
// Update the class fields.
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 984f922e25..528563bd07 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -184,7 +184,6 @@ class Redefiner {
REQUIRES(art::Locks::mutator_lock_);
void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
- art::ObjPtr<art::mirror::DexCache> new_dex_cache,
const art::DexFile::ClassDef& class_def)
REQUIRES(art::Locks::mutator_lock_);
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
index 20de9aa8fd..e165187348 100644
--- a/openjdkjvmti/ti_stack.cc
+++ b/openjdkjvmti/ti_stack.cc
@@ -37,6 +37,7 @@
#include <vector>
#include "art_field-inl.h"
+#include "art_method-inl.h"
#include "art_jvmti.h"
#include "art_method-inl.h"
#include "barrier.h"
@@ -54,6 +55,7 @@
#include "nativehelper/ScopedLocalRef.h"
#include "scoped_thread_state_change-inl.h"
#include "stack.h"
+#include "ti_thread.h"
#include "thread-current-inl.h"
#include "thread_list.h"
#include "thread_pool.h"
@@ -991,4 +993,74 @@ jvmtiError StackUtil::GetOwnedMonitorInfo(jvmtiEnv* env,
return GetOwnedMonitorInfoCommon(thread, handle_fun);
}
+jvmtiError StackUtil::NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) {
+ if (depth < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ ArtJvmTiEnv* tienv = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+ art::Thread* self = art::Thread::Current();
+ art::Thread* target;
+ do {
+ ThreadUtil::SuspendCheck(self);
+ art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+ // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by a
+ // user-code suspension. We retry and do another SuspendCheck to clear this.
+ if (ThreadUtil::WouldSuspendForUserCodeLocked(self)) {
+ continue;
+ }
+ // From now on we know we cannot get suspended by user-code.
+ // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't
+ // have the 'suspend_lock' locked here.
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+ target = ThreadUtil::GetNativeThread(thread, soa);
+ if (target == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ } else if (target != self) {
+ // TODO This is part of the spec but we could easily avoid needing to do it. We would just put
+ // all the logic into a sync-checkpoint.
+ art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+ if (target->GetUserCodeSuspendCount() == 0) {
+ return ERR(THREAD_NOT_SUSPENDED);
+ }
+ }
+ // We hold the user_code_suspension_lock_ so the target thread is staying suspended until we are
+ // done (unless it's 'self' in which case we don't care since we aren't going to be returning).
+ // TODO We could implement this using a synchronous checkpoint and not bother with any of the
+ // suspension stuff. The spec does specifically say to return THREAD_NOT_SUSPENDED though.
+ // Find the requested stack frame.
+ std::unique_ptr<art::Context> context(art::Context::Create());
+ FindFrameAtDepthVisitor visitor(target, context.get(), depth);
+ visitor.WalkStack();
+ if (!visitor.FoundFrame()) {
+ return ERR(NO_MORE_FRAMES);
+ }
+ art::ArtMethod* method = visitor.GetMethod();
+ if (method->IsNative()) {
+ return ERR(OPAQUE_FRAME);
+ }
+ // From here we are sure to succeed.
+ bool needs_instrument = false;
+ // Get/create a shadow frame
+ art::ShadowFrame* shadow_frame = visitor.GetCurrentShadowFrame();
+ if (shadow_frame == nullptr) {
+ needs_instrument = true;
+ const size_t frame_id = visitor.GetFrameId();
+ const uint16_t num_regs = method->GetCodeItem()->registers_size_;
+ shadow_frame = target->FindOrCreateDebuggerShadowFrame(frame_id,
+ num_regs,
+ method,
+ visitor.GetDexPc());
+ }
+ // Mark shadow frame as needs_notify_pop_
+ shadow_frame->SetNotifyPop(true);
+ tienv->notify_frames.insert(shadow_frame);
+ // Make sure can we will go to the interpreter and use the shadow frames.
+ if (needs_instrument) {
+ art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(target);
+ }
+ return OK;
+ } while (true);
+}
+
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h
index 2f506d0064..b41fa4bf5a 100644
--- a/openjdkjvmti/ti_stack.h
+++ b/openjdkjvmti/ti_stack.h
@@ -35,7 +35,9 @@
#include "jni.h"
#include "jvmti.h"
+#include "art_method.h"
#include "base/mutex.h"
+#include "stack.h"
namespace openjdkjvmti {
@@ -77,6 +79,41 @@ class StackUtil {
jthread thread,
jint* owned_monitor_count_ptr,
jobject** owned_monitors_ptr);
+
+ static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth);
+};
+
+struct FindFrameAtDepthVisitor : art::StackVisitor {
+ public:
+ FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ found_frame_(false),
+ cnt_(0),
+ depth_(static_cast<size_t>(depth)) { }
+
+ bool FoundFrame() {
+ return found_frame_;
+ }
+
+ bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
+ if (GetMethod()->IsRuntimeMethod()) {
+ return true;
+ }
+ if (cnt_ == depth_) {
+ // We found our frame, exit.
+ found_frame_ = true;
+ return false;
+ } else {
+ cnt_++;
+ return true;
+ }
+ }
+
+ private:
+ bool found_frame_;
+ size_t cnt_;
+ size_t depth_;
};
} // namespace openjdkjvmti
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index ed7623a465..efa2969eea 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -686,8 +686,6 @@ void PatchOat::FixupMethod(ArtMethod* object, ArtMethod* copy) {
// Just update the entry points if it looks like we should.
// TODO: sanity check all the pointers' values
copy->SetDeclaringClass(RelocatedAddressOfPointer(object->GetDeclaringClass()));
- copy->SetDexCacheResolvedMethods(
- RelocatedAddressOfPointer(object->GetDexCacheResolvedMethods(pointer_size)), pointer_size);
copy->SetEntryPointFromQuickCompiledCodePtrSize(RelocatedAddressOfPointer(
object->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size)), pointer_size);
// No special handling for IMT conflict table since all pointers are moved by the same offset.
diff --git a/profman/profman.cc b/profman/profman.cc
index fd3bd11c0e..d0c99e0201 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -116,9 +116,9 @@ NO_RETURN static void Usage(const char *fmt, ...) {
UsageError(" --generate-test-profile=<filename>: generates a random profile file for testing.");
UsageError(" --generate-test-profile-num-dex=<number>: number of dex files that should be");
UsageError(" included in the generated profile. Defaults to 20.");
- UsageError(" --generate-test-profile-method-ratio=<number>: the percentage from the maximum");
+ UsageError(" --generate-test-profile-method-percentage=<number>: the percentage from the maximum");
UsageError(" number of methods that should be generated. Defaults to 5.");
- UsageError(" --generate-test-profile-class-ratio=<number>: the percentage from the maximum");
+ UsageError(" --generate-test-profile-class-percentage=<number>: the percentage from the maximum");
UsageError(" number of classes that should be generated. Defaults to 5.");
UsageError(" --generate-test-profile-seed=<number>: seed for random number generator used when");
UsageError(" generating random test profiles. Defaults to using NanoTime.");
@@ -151,8 +151,8 @@ NO_RETURN static void Usage(const char *fmt, ...) {
// Note: make sure you update the Usage if you change these values.
static constexpr uint16_t kDefaultTestProfileNumDex = 20;
-static constexpr uint16_t kDefaultTestProfileMethodRatio = 5;
-static constexpr uint16_t kDefaultTestProfileClassRatio = 5;
+static constexpr uint16_t kDefaultTestProfileMethodPercentage = 5;
+static constexpr uint16_t kDefaultTestProfileClassPercentage = 5;
// Separators used when parsing human friendly representation of profiles.
static const std::string kMethodSep = "->";
@@ -178,8 +178,8 @@ class ProfMan FINAL {
generate_boot_image_profile_(false),
dump_output_to_fd_(kInvalidFd),
test_profile_num_dex_(kDefaultTestProfileNumDex),
- test_profile_method_ratio_(kDefaultTestProfileMethodRatio),
- test_profile_class_ratio_(kDefaultTestProfileClassRatio),
+ test_profile_method_percerntage_(kDefaultTestProfileMethodPercentage),
+ test_profile_class_percentage_(kDefaultTestProfileClassPercentage),
test_profile_seed_(NanoTime()),
start_ns_(NanoTime()) {}
@@ -253,15 +253,15 @@ class ProfMan FINAL {
"--generate-test-profile-num-dex",
&test_profile_num_dex_,
Usage);
- } else if (option.starts_with("--generate-test-profile-method-ratio")) {
+ } else if (option.starts_with("--generate-test-profile-method-percentage")) {
ParseUintOption(option,
- "--generate-test-profile-method-ratio",
- &test_profile_method_ratio_,
+ "--generate-test-profile-method-percentage",
+ &test_profile_method_percerntage_,
Usage);
- } else if (option.starts_with("--generate-test-profile-class-ratio")) {
+ } else if (option.starts_with("--generate-test-profile-class-percentage")) {
ParseUintOption(option,
- "--generate-test-profile-class-ratio",
- &test_profile_class_ratio_,
+ "--generate-test-profile-class-percentage",
+ &test_profile_class_percentage_,
Usage);
} else if (option.starts_with("--generate-test-profile-seed=")) {
ParseUintOption(option, "--generate-test-profile-seed", &test_profile_seed_, Usage);
@@ -1014,11 +1014,11 @@ class ProfMan FINAL {
int GenerateTestProfile() {
// Validate parameters for this command.
- if (test_profile_method_ratio_ > 100) {
- Usage("Invalid ratio for --generate-test-profile-method-ratio");
+ if (test_profile_method_percerntage_ > 100) {
+ Usage("Invalid percentage for --generate-test-profile-method-percentage");
}
- if (test_profile_class_ratio_ > 100) {
- Usage("Invalid ratio for --generate-test-profile-class-ratio");
+ if (test_profile_class_percentage_ > 100) {
+ Usage("Invalid percentage for --generate-test-profile-class-percentage");
}
// If given APK files or DEX locations, check that they're ok.
if (!apk_files_.empty() || !apks_fd_.empty() || !dex_locations_.empty()) {
@@ -1039,8 +1039,8 @@ class ProfMan FINAL {
if (apk_files_.empty() && apks_fd_.empty() && dex_locations_.empty()) {
result = ProfileCompilationInfo::GenerateTestProfile(profile_test_fd,
test_profile_num_dex_,
- test_profile_method_ratio_,
- test_profile_class_ratio_,
+ test_profile_method_percerntage_,
+ test_profile_class_percentage_,
test_profile_seed_);
} else {
// Initialize MemMap for ZipArchive::OpenFromFd.
@@ -1051,6 +1051,8 @@ class ProfMan FINAL {
// Create a random profile file based on the set of dex files.
result = ProfileCompilationInfo::GenerateTestProfile(profile_test_fd,
dex_files,
+ test_profile_method_percerntage_,
+ test_profile_class_percentage_,
test_profile_seed_);
}
close(profile_test_fd); // ignore close result.
@@ -1101,8 +1103,8 @@ class ProfMan FINAL {
std::string test_profile_;
std::string create_profile_from_file_;
uint16_t test_profile_num_dex_;
- uint16_t test_profile_method_ratio_;
- uint16_t test_profile_class_ratio_;
+ uint16_t test_profile_method_percerntage_;
+ uint16_t test_profile_class_percentage_;
uint32_t test_profile_seed_;
uint64_t start_ns_;
};
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index ea8d501939..69c615db9f 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1596,10 +1596,21 @@ ENTRY art_quick_imt_conflict_trampoline
.cfi_rel_offset r1, 0
.cfi_rel_offset r2, 4
ldr r4, [sp, #(2 * 4)] // Load referrer.
+ ldr r2, [r0, #ART_METHOD_JNI_OFFSET_32] // Load ImtConflictTable
+ // Load the declaring class (without read barrier) and access flags (for obsolete method check).
+ // The obsolete flag is set with suspended threads, so we do not need an acquire operation here.
+#if ART_METHOD_ACCESS_FLAGS_OFFSET != ART_METHOD_DECLARING_CLASS_OFFSET + 4
+#error "Expecting declaring class and access flags to be consecutive for LDRD."
+#endif
+ ldrd r0, r1, [r4, #ART_METHOD_DECLARING_CLASS_OFFSET]
+ // If the method is obsolete, just go through the dex cache miss slow path.
+ lsrs r1, #(ACC_OBSOLETE_METHOD_SHIFT + 1)
+ bcs .Limt_conflict_trampoline_dex_cache_miss
+ ldr r4, [r0, #MIRROR_CLASS_DEX_CACHE_OFFSET] // Load the DexCache (without read barrier).
+ UNPOISON_HEAP_REF r4
ubfx r1, r12, #0, #METHOD_DEX_CACHE_HASH_BITS // Calculate DexCache method slot index.
- ldr r4, [r4, #ART_METHOD_DEX_CACHE_METHODS_OFFSET_32] // Load dex cache methods array
+ ldr r4, [r4, #MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET] // Load the resolved methods.
add r4, r4, r1, lsl #(POINTER_SIZE_SHIFT + 1) // Load DexCache method slot address.
- ldr r2, [r0, #ART_METHOD_JNI_OFFSET_32] // Load ImtConflictTable
// FIXME: Configure the build to use the faster code when appropriate.
// Currently we fall back to the slower version.
@@ -1832,45 +1843,47 @@ ENTRY art_quick_instrumentation_entry
mov r12, r0 @ r12 holds reference to code
ldr r0, [sp, #4] @ restore r0
RESTORE_SAVE_REFS_AND_ARGS_FRAME
- adr lr, art_quick_instrumentation_exit + /* thumb mode */ 1
- @ load art_quick_instrumentation_exit into lr in thumb mode
REFRESH_MARKING_REGISTER
- bx r12 @ call method with lr set to art_quick_instrumentation_exit
-.Ldeliver_instrumentation_entry_exception:
- @ Deliver exception for art_quick_instrumentation_entry placed after
- @ art_quick_instrumentation_exit so that the fallthrough works.
- RESTORE_SAVE_REFS_AND_ARGS_FRAME
- DELIVER_PENDING_EXCEPTION
-END art_quick_instrumentation_entry
-
-ENTRY art_quick_instrumentation_exit
+ blx r12 @ call method with lr set to art_quick_instrumentation_exit
+@ Deliberate fall-through into art_quick_instrumentation_exit.
+ .type art_quick_instrumentation_exit, #function
+ .global art_quick_instrumentation_exit
+art_quick_instrumentation_exit:
mov lr, #0 @ link register is to here, so clobber with 0 for later checks
- SETUP_SAVE_EVERYTHING_FRAME r2
-
- add r3, sp, #8 @ store fpr_res pointer, in kSaveEverything frame
- add r2, sp, #136 @ store gpr_res pointer, in kSaveEverything frame
- mov r1, sp @ pass SP
+ SETUP_SAVE_REFS_ONLY_FRAME r2 @ set up frame knowing r2 and r3 must be dead on exit
+ mov r12, sp @ remember bottom of caller's frame
+ push {r0-r1} @ save return value
+ .cfi_adjust_cfa_offset 8
+ .cfi_rel_offset r0, 0
+ .cfi_rel_offset r1, 4
+ mov r2, sp @ store gpr_res pointer.
+ vpush {d0} @ save fp return value
+ .cfi_adjust_cfa_offset 8
+ mov r3, sp @ store fpr_res pointer
+ mov r1, r12 @ pass SP
mov r0, r9 @ pass Thread::Current
blx artInstrumentationMethodExitFromCode @ (Thread*, SP, gpr_res*, fpr_res*)
- cbz r0, .Ldo_deliver_instrumentation_exception
- @ Deliver exception if we got nullptr as function.
- cbnz r1, .Ldeoptimize
- // Normal return.
- str r0, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
- @ Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
+ mov r2, r0 @ link register saved by instrumentation
+ mov lr, r1 @ r1 is holding link register if we're to bounce to deoptimize
+ vpop {d0} @ restore fp return value
+ .cfi_adjust_cfa_offset -8
+ pop {r0, r1} @ restore return value
+ .cfi_adjust_cfa_offset -8
+ .cfi_restore r0
+ .cfi_restore r1
+ RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- bx lr
+ cbz r2, .Ldo_deliver_instrumentation_exception
+ @ Deliver exception if we got nullptr as function.
+ bx r2 @ Otherwise, return
+.Ldeliver_instrumentation_entry_exception:
+ @ Deliver exception for art_quick_instrumentation_entry placed after
+ @ art_quick_instrumentation_exit so that the fallthrough works.
+ RESTORE_SAVE_REFS_AND_ARGS_FRAME
.Ldo_deliver_instrumentation_exception:
- DELIVER_PENDING_EXCEPTION_FRAME_READY
-.Ldeoptimize:
- str r1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 4]
- @ Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
- // Jump to art_quick_deoptimize.
- b art_quick_deoptimize
-END art_quick_instrumentation_exit
+ DELIVER_PENDING_EXCEPTION
+END art_quick_instrumentation_entry
/*
* Instrumentation has requested that we deoptimize into the interpreter. The deoptimization
@@ -1878,7 +1891,7 @@ END art_quick_instrumentation_exit
*/
.extern artDeoptimize
ENTRY art_quick_deoptimize
- SETUP_SAVE_EVERYTHING_FRAME r0
+ SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r0
mov r0, r9 @ pass Thread::Current
blx artDeoptimize @ (Thread*)
END art_quick_deoptimize
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 6c9ce93d2e..802cf5e711 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2060,8 +2060,18 @@ END art_quick_proxy_invoke_handler
.extern artLookupResolvedMethod
ENTRY art_quick_imt_conflict_trampoline
ldr xIP0, [sp, #0] // Load referrer
+ // Load the declaring class (without read barrier) and access flags (for obsolete method check).
+ // The obsolete flag is set with suspended threads, so we do not need an acquire operation here.
+#if ART_METHOD_ACCESS_FLAGS_OFFSET != ART_METHOD_DECLARING_CLASS_OFFSET + 4
+#error "Expecting declaring class and access flags to be consecutive for LDP."
+#endif
+ ldp wIP0, w15, [xIP0, #ART_METHOD_DECLARING_CLASS_OFFSET]
+ // If the method is obsolete, just go through the dex cache miss slow path.
+ tbnz x15, #ACC_OBSOLETE_METHOD_SHIFT, .Limt_conflict_trampoline_dex_cache_miss
+ ldr wIP0, [xIP0, #MIRROR_CLASS_DEX_CACHE_OFFSET] // Load the DexCache (without read barrier).
+ UNPOISON_HEAP_REF wIP0
ubfx x15, xIP1, #0, #METHOD_DEX_CACHE_HASH_BITS // Calculate DexCache method slot index.
- ldr xIP0, [xIP0, #ART_METHOD_DEX_CACHE_METHODS_OFFSET_64] // Load dex cache methods array
+ ldr xIP0, [xIP0, #MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET] // Load the resolved methods.
add xIP0, xIP0, x15, lsl #(POINTER_SIZE_SHIFT + 1) // Load DexCache method slot address.
// Relaxed atomic load x14:x15 from the dex cache slot.
@@ -2355,31 +2365,32 @@ END art_quick_instrumentation_entry
.extern artInstrumentationMethodExitFromCode
ENTRY art_quick_instrumentation_exit
mov xLR, #0 // Clobber LR for later checks.
- SETUP_SAVE_EVERYTHING_FRAME
- add x3, sp, #8 // Pass floating-point result pointer, in kSaveEverything frame.
- add x2, sp, #264 // Pass integer result pointer, in kSaveEverything frame.
- mov x1, sp // Pass SP.
+ SETUP_SAVE_REFS_ONLY_FRAME
+
+ str x0, [sp, #-16]! // Save integer result.
+ .cfi_adjust_cfa_offset 16
+ str d0, [sp, #8] // Save floating-point result.
+
+ add x3, sp, #8 // Pass floating-point result pointer.
+ mov x2, sp // Pass integer result pointer.
+ add x1, sp, #16 // Pass SP.
mov x0, xSELF // Pass Thread.
bl artInstrumentationMethodExitFromCode // (Thread*, SP, gpr_res*, fpr_res*)
- cbz x0, .Ldo_deliver_instrumentation_exception
- // Handle error
- cbnz x1, .Ldeoptimize
- // Normal return.
- str x0, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 8]
- // Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
+ mov xIP0, x0 // Return address from instrumentation call.
+ mov xLR, x1 // r1 is holding link register if we're to bounce to deoptimize
+
+ ldr d0, [sp, #8] // Restore floating-point result.
+ ldr x0, [sp], #16 // Restore integer result, and drop stack area.
+ .cfi_adjust_cfa_offset -16
+
+ RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- br lr
-.Ldo_deliver_instrumentation_exception:
- DELIVER_PENDING_EXCEPTION_FRAME_READY
-.Ldeoptimize:
- str x1, [sp, #FRAME_SIZE_SAVE_EVERYTHING - 8]
- // Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
- // Jump to art_quick_deoptimize.
- b art_quick_deoptimize
+ cbz xIP0, 1f // Handle error
+ br xIP0 // Tail-call out.
+1:
+ DELIVER_PENDING_EXCEPTION
END art_quick_instrumentation_exit
/*
@@ -2388,7 +2399,7 @@ END art_quick_instrumentation_exit
*/
.extern artDeoptimize
ENTRY art_quick_deoptimize
- SETUP_SAVE_EVERYTHING_FRAME
+ SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
mov x0, xSELF // Pass thread.
bl artDeoptimize // (Thread*)
brk 0
diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S
index bb82d5819d..b876353183 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -2112,10 +2112,18 @@ ENTRY art_quick_imt_conflict_trampoline
SETUP_SAVE_REFS_AND_ARGS_FRAME_REGISTERS_ONLY /* save_s4_thru_s8 */ 0
lw $t8, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $t8 = referrer.
+ // If the method is obsolete, just go through the dex cache miss slow path.
+ // The obsolete flag is set with suspended threads, so we do not need an acquire operation here.
+ lw $t9, ART_METHOD_ACCESS_FLAGS_OFFSET($t8) # $t9 = access flags.
+ sll $t9, $t9, 31 - ACC_OBSOLETE_METHOD_SHIFT # Move obsolete method bit to sign bit.
+ bltz $t9, .Limt_conflict_trampoline_dex_cache_miss
+ lw $t8, ART_METHOD_DECLARING_CLASS_OFFSET($t8) # $t8 = declaring class (no read barrier).
+ lw $t8, MIRROR_CLASS_DEX_CACHE_OFFSET($t8) # $t8 = dex cache (without read barrier).
+ UNPOISON_HEAP_REF $t8
la $t9, __atomic_load_8
addiu $sp, $sp, -ARG_SLOT_SIZE # Reserve argument slots on the stack.
.cfi_adjust_cfa_offset ARG_SLOT_SIZE
- lw $t8, ART_METHOD_DEX_CACHE_METHODS_OFFSET_32($t8) # $t8 = dex cache methods array.
+ lw $t8, MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET($t8) # $t8 = dex cache methods array.
move $s2, $t7 # $s2 = method index (callee-saved).
lw $s3, ART_METHOD_JNI_OFFSET_32($a0) # $s3 = ImtConflictTable (callee-saved).
diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S
index 7350c8521a..eeaae3c698 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -2024,8 +2024,16 @@ ENTRY art_quick_imt_conflict_trampoline
SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL /* save_s4_thru_s8 */ 0
ld $t1, FRAME_SIZE_SAVE_REFS_AND_ARGS($sp) # $t1 = referrer.
+ // If the method is obsolete, just go through the dex cache miss slow path.
+ // The obsolete flag is set with suspended threads, so we do not need an acquire operation here.
+ lw $t9, ART_METHOD_ACCESS_FLAGS_OFFSET($t1) # $t9 = access flags.
+ sll $t9, $t9, 31 - ACC_OBSOLETE_METHOD_SHIFT # Move obsolete method bit to sign bit.
+ bltzc $t9, .Limt_conflict_trampoline_dex_cache_miss
+ lwu $t1, ART_METHOD_DECLARING_CLASS_OFFSET($t1) # $t1 = declaring class (no read barrier).
+ lwu $t1, MIRROR_CLASS_DEX_CACHE_OFFSET($t1) # $t1 = dex cache (without read barrier).
+ UNPOISON_HEAP_REF $t1
dla $t9, __atomic_load_16
- ld $t1, ART_METHOD_DEX_CACHE_METHODS_OFFSET_64($t1) # $t1 = dex cache methods array.
+ ld $t1, MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET($t1) # $t1 = dex cache methods array.
dext $s2, $t0, 0, 32 # $s2 = zero-extended method index
# (callee-saved).
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index af82e08698..f08c7fedcc 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -1787,7 +1787,14 @@ DEFINE_FUNCTION art_quick_imt_conflict_trampoline
PUSH ESI
PUSH EDX
movl 16(%esp), %edi // Load referrer.
- movl ART_METHOD_DEX_CACHE_METHODS_OFFSET_32(%edi), %edi // Load dex cache methods array.
+ // If the method is obsolete, just go through the dex cache miss slow path.
+ // The obsolete flag is set with suspended threads, so we do not need an acquire operation here.
+ testl LITERAL(ACC_OBSOLETE_METHOD), ART_METHOD_ACCESS_FLAGS_OFFSET(%edi)
+ jnz .Limt_conflict_trampoline_dex_cache_miss
+ movl ART_METHOD_DECLARING_CLASS_OFFSET(%edi), %edi // Load declaring class (no read barrier).
+ movl MIRROR_CLASS_DEX_CACHE_OFFSET(%edi), %edi // Load the DexCache (without read barrier).
+ UNPOISON_HEAP_REF edi
+ movl MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET(%edi), %edi // Load the resolved methods.
pushl ART_METHOD_JNI_OFFSET_32(%eax) // Push ImtConflictTable.
CFI_ADJUST_CFA_OFFSET(4)
movd %xmm7, %eax // Get target method index stored in xmm7.
@@ -2056,43 +2063,42 @@ END_FUNCTION art_quick_instrumentation_entry
DEFINE_FUNCTION_CUSTOM_CFA art_quick_instrumentation_exit, 0
pushl LITERAL(0) // Push a fake return PC as there will be none on the stack.
CFI_ADJUST_CFA_OFFSET(4)
- SETUP_SAVE_EVERYTHING_FRAME ebx, ebx
-
- movl %esp, %ecx // Remember SP
- subl LITERAL(8), %esp // Align stack.
+ SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx
+ mov %esp, %ecx // Remember SP
+ subl LITERAL(8), %esp // Save float return value.
CFI_ADJUST_CFA_OFFSET(8)
- PUSH edx // Save gpr return value. edx and eax need to be together,
- // which isn't the case in kSaveEverything frame.
+ movq %xmm0, (%esp)
+ PUSH edx // Save gpr return value.
PUSH eax
- leal 32(%esp), %eax // Get pointer to fpr_result, in kSaveEverything frame
+ leal 8(%esp), %eax // Get pointer to fpr_result
movl %esp, %edx // Get pointer to gpr_result
PUSH eax // Pass fpr_result
PUSH edx // Pass gpr_result
- PUSH ecx // Pass SP
+ PUSH ecx // Pass SP.
pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current.
CFI_ADJUST_CFA_OFFSET(4)
-
call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_result*, fpr_result*)
- // Return result could have been changed if it's a reference.
- movl 16(%esp), %ecx
- movl %ecx, (80+32)(%esp)
- addl LITERAL(32), %esp // Pop arguments and grp_result.
- CFI_ADJUST_CFA_OFFSET(-32)
-
testl %eax, %eax // Check if we returned error.
- jz .Ldo_deliver_instrumentation_exception
- testl %edx, %edx
- jnz .Ldeoptimize
- // Normal return.
- movl %eax, FRAME_SIZE_SAVE_EVERYTHING-4(%esp) // Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
- ret
-.Ldeoptimize:
- mov %edx, (FRAME_SIZE_SAVE_EVERYTHING-4)(%esp) // Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
- jmp SYMBOL(art_quick_deoptimize)
-.Ldo_deliver_instrumentation_exception:
- DELIVER_PENDING_EXCEPTION_FRAME_READY
+ jz 1f
+ mov %eax, %ecx // Move returned link register.
+ addl LITERAL(16), %esp // Pop arguments.
+ CFI_ADJUST_CFA_OFFSET(-16)
+ movl %edx, %ebx // Move returned link register for deopt
+ // (ebx is pretending to be our LR).
+ POP eax // Restore gpr return value.
+ POP edx
+ movq (%esp), %xmm0 // Restore fpr return value.
+ addl LITERAL(8), %esp
+ CFI_ADJUST_CFA_OFFSET(-8)
+ RESTORE_SAVE_REFS_ONLY_FRAME
+ addl LITERAL(4), %esp // Remove fake return pc.
+ CFI_ADJUST_CFA_OFFSET(-4)
+ jmp *%ecx // Return.
+1:
+ addl LITERAL(32), %esp
+ CFI_ADJUST_CFA_OFFSET(-32)
+ RESTORE_SAVE_REFS_ONLY_FRAME
+ DELIVER_PENDING_EXCEPTION
END_FUNCTION art_quick_instrumentation_exit
/*
@@ -2100,7 +2106,8 @@ END_FUNCTION art_quick_instrumentation_exit
* will long jump to the upcall with a special exception of -1.
*/
DEFINE_FUNCTION art_quick_deoptimize
- SETUP_SAVE_EVERYTHING_FRAME ebx, ebx
+ PUSH ebx // Entry point for a jump. Fake that we were called.
+ SETUP_SAVE_ALL_CALLEE_SAVES_FRAME ebx, ebx
subl LITERAL(12), %esp // Align stack.
CFI_ADJUST_CFA_OFFSET(12)
pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current().
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index 6bf08289ee..b70abaa81e 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -1646,7 +1646,14 @@ DEFINE_FUNCTION art_quick_imt_conflict_trampoline
int3
#else
movq __SIZEOF_POINTER__(%rsp), %r10 // Load referrer.
- movq ART_METHOD_DEX_CACHE_METHODS_OFFSET_64(%r10), %r10 // Load dex cache methods array.
+ // If the method is obsolete, just go through the dex cache miss slow path.
+ // The obsolete flag is set with suspended threads, so we do not need an acquire operation here.
+ testl LITERAL(ACC_OBSOLETE_METHOD), ART_METHOD_ACCESS_FLAGS_OFFSET(%r10)
+ jnz .Limt_conflict_trampoline_dex_cache_miss
+ movl ART_METHOD_DECLARING_CLASS_OFFSET(%r10), %r10d // Load declaring class (no read barrier).
+ movl MIRROR_CLASS_DEX_CACHE_OFFSET(%r10), %r10d // Load the DexCache (without read barrier).
+ UNPOISON_HEAP_REF r10d
+ movq MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET(%r10), %r10 // Load the resolved methods.
mov %eax, %r11d // Remember method index in R11.
andl LITERAL(METHOD_DEX_CACHE_SIZE_MINUS_ONE), %eax // Calculate DexCache method slot index.
shll LITERAL(1), %eax // Multiply by 2 as entries have size 2 * __SIZEOF_POINTER__.
@@ -2019,31 +2026,45 @@ DEFINE_FUNCTION_CUSTOM_CFA art_quick_instrumentation_exit, 0
pushq LITERAL(0) // Push a fake return PC as there will be none on the stack.
CFI_ADJUST_CFA_OFFSET(8)
- SETUP_SAVE_EVERYTHING_FRAME
+ SETUP_SAVE_REFS_ONLY_FRAME
- leaq 16(%rsp), %rcx // Pass floating-point result pointer, in kSaveEverything frame.
- leaq 144(%rsp), %rdx // Pass integer result pointer, in kSaveEverything frame.
- movq %rsp, %rsi // Pass SP.
- movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread.
+ // We need to save rax and xmm0. We could use a callee-save from SETUP_REF_ONLY, but then
+ // we would need to fully restore it. As there are a good number of callee-save registers, it
+ // seems easier to have an extra small stack area. But this should be revisited.
+
+ movq %rsp, %rsi // Pass SP.
+
+ PUSH rax // Save integer result.
+ movq %rsp, %rdx // Pass integer result pointer.
+
+ subq LITERAL(8), %rsp // Save floating-point result.
+ CFI_ADJUST_CFA_OFFSET(8)
+ movq %xmm0, (%rsp)
+ movq %rsp, %rcx // Pass floating-point result pointer.
+
+ movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread.
call SYMBOL(artInstrumentationMethodExitFromCode) // (Thread*, SP, gpr_res*, fpr_res*)
- testq %rax, %rax // Check if we have a return-pc to go to. If we don't then there was
+ movq %rax, %rdi // Store return PC
+ movq %rdx, %rsi // Store second return PC in hidden arg.
+
+ movq (%rsp), %xmm0 // Restore floating-point result.
+ addq LITERAL(8), %rsp
+ CFI_ADJUST_CFA_OFFSET(-8)
+ POP rax // Restore integer result.
+
+ RESTORE_SAVE_REFS_ONLY_FRAME
+
+ testq %rdi, %rdi // Check if we have a return-pc to go to. If we don't then there was
// an exception
- jz .Ldo_deliver_instrumentation_exception
- testq %rdx, %rdx
- jnz .Ldeoptimize
- // Normal return.
- movq %rax, FRAME_SIZE_SAVE_EVERYTHING-8(%rsp) // Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
- ret
-.Ldeoptimize:
- movq %rdx, FRAME_SIZE_SAVE_EVERYTHING-8(%rsp) // Set return pc.
- RESTORE_SAVE_EVERYTHING_FRAME
- // Jump to art_quick_deoptimize.
- jmp SYMBOL(art_quick_deoptimize)
-.Ldo_deliver_instrumentation_exception:
- DELIVER_PENDING_EXCEPTION_FRAME_READY
+ jz 1f
+
+ addq LITERAL(8), %rsp // Drop fake return pc.
+
+ jmp *%rdi // Return.
+1:
+ DELIVER_PENDING_EXCEPTION
END_FUNCTION art_quick_instrumentation_exit
/*
@@ -2051,7 +2072,10 @@ END_FUNCTION art_quick_instrumentation_exit
* will long jump to the upcall with a special exception of -1.
*/
DEFINE_FUNCTION art_quick_deoptimize
- SETUP_SAVE_EVERYTHING_FRAME // Stack should be aligned now.
+ pushq %rsi // Entry point for a jump. Fake that we were called.
+ // Use hidden arg.
+ SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
+ // Stack should be aligned now.
movq %gs:THREAD_SELF_OFFSET, %rdi // Pass Thread.
call SYMBOL(artDeoptimize) // (Thread*)
UNREACHABLE
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 11f825353b..1588920e94 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -102,58 +102,6 @@ inline uint32_t ArtMethod::GetDexMethodIndex() {
return GetDexMethodIndexUnchecked();
}
-inline mirror::MethodDexCacheType* ArtMethod::GetDexCacheResolvedMethods(PointerSize pointer_size) {
- return GetNativePointer<mirror::MethodDexCacheType*>(DexCacheResolvedMethodsOffset(pointer_size),
- pointer_size);
-}
-
-inline ArtMethod* ArtMethod::GetDexCacheResolvedMethod(uint16_t method_index,
- PointerSize pointer_size) {
- // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
- // without accessing the DexCache and we don't want to do that in release build.
- DCHECK_LT(method_index, GetInterfaceMethodIfProxy(pointer_size)->GetDexFile()->NumMethodIds());
- uint32_t slot_idx = method_index % mirror::DexCache::kDexCacheMethodCacheSize;
- DCHECK_LT(slot_idx, GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
- mirror::MethodDexCachePair pair = mirror::DexCache::GetNativePairPtrSize(
- GetDexCacheResolvedMethods(pointer_size), slot_idx, pointer_size);
- ArtMethod* method = pair.GetObjectForIndex(method_index);
- if (LIKELY(method != nullptr)) {
- auto* declaring_class = method->GetDeclaringClass();
- if (LIKELY(declaring_class == nullptr || !declaring_class->IsErroneous())) {
- return method;
- }
- }
- return nullptr;
-}
-
-inline void ArtMethod::SetDexCacheResolvedMethod(uint16_t method_index,
- ArtMethod* new_method,
- PointerSize pointer_size) {
- // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
- // without accessing the DexCache and we don't want to do that in release build.
- DCHECK_LT(method_index, GetInterfaceMethodIfProxy(pointer_size)->GetDexFile()->NumMethodIds());
- DCHECK(new_method == nullptr || new_method->GetDeclaringClass() != nullptr);
- uint32_t slot_idx = method_index % mirror::DexCache::kDexCacheMethodCacheSize;
- DCHECK_LT(slot_idx, GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
- mirror::MethodDexCachePair pair(new_method, method_index);
- mirror::DexCache::SetNativePairPtrSize(
- GetDexCacheResolvedMethods(pointer_size), slot_idx, pair, pointer_size);
-}
-
-inline bool ArtMethod::HasDexCacheResolvedMethods(PointerSize pointer_size) {
- return GetDexCacheResolvedMethods(pointer_size) != nullptr;
-}
-
-inline bool ArtMethod::HasSameDexCacheResolvedMethods(ArtMethod* other, PointerSize pointer_size) {
- return GetDexCacheResolvedMethods(pointer_size) ==
- other->GetDexCacheResolvedMethods(pointer_size);
-}
-
-inline bool ArtMethod::HasSameDexCacheResolvedMethods(mirror::MethodDexCacheType* other_cache,
- PointerSize pointer_size) {
- return GetDexCacheResolvedMethods(pointer_size) == other_cache;
-}
-
inline ObjPtr<mirror::Class> ArtMethod::LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx) {
ObjPtr<mirror::DexCache> dex_cache = GetDexCache();
ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx);
@@ -403,13 +351,6 @@ inline ArtMethod* ArtMethod::GetInterfaceMethodIfProxy(PointerSize pointer_size)
return interface_method;
}
-inline void ArtMethod::SetDexCacheResolvedMethods(mirror::MethodDexCacheType* new_dex_cache_methods,
- PointerSize pointer_size) {
- SetNativePointer(DexCacheResolvedMethodsOffset(pointer_size),
- new_dex_cache_methods,
- pointer_size);
-}
-
inline dex::TypeIndex ArtMethod::GetReturnTypeIndex() {
DCHECK(!IsProxyMethod());
const DexFile* dex_file = GetDexFile();
@@ -489,18 +430,12 @@ void ArtMethod::VisitRoots(RootVisitorType& visitor, PointerSize pointer_size) {
}
template <typename Visitor>
-inline void ArtMethod::UpdateObjectsForImageRelocation(const Visitor& visitor,
- PointerSize pointer_size) {
+inline void ArtMethod::UpdateObjectsForImageRelocation(const Visitor& visitor) {
mirror::Class* old_class = GetDeclaringClassUnchecked<kWithoutReadBarrier>();
mirror::Class* new_class = visitor(old_class);
if (old_class != new_class) {
SetDeclaringClass(new_class);
}
- mirror::MethodDexCacheType* old_methods = GetDexCacheResolvedMethods(pointer_size);
- mirror::MethodDexCacheType* new_methods = visitor(old_methods);
- if (old_methods != new_methods) {
- SetDexCacheResolvedMethods(new_methods, pointer_size);
- }
}
template <ReadBarrierOption kReadBarrierOption, typename Visitor>
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 64988f2528..2d677617d9 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -137,6 +137,10 @@ class ArtMethod FINAL {
} while (!access_flags_.compare_exchange_weak(old_access_flags, new_access_flags));
}
+ static MemberOffset AccessFlagsOffset() {
+ return MemberOffset(OFFSETOF_MEMBER(ArtMethod, access_flags_));
+ }
+
// Approximate what kind of method call would be used for this method.
InvokeType GetInvokeType() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -356,26 +360,6 @@ class ArtMethod FINAL {
dex_method_index_ = new_idx;
}
- ALWAYS_INLINE mirror::MethodDexCacheType* GetDexCacheResolvedMethods(PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
- ALWAYS_INLINE ArtMethod* GetDexCacheResolvedMethod(uint16_t method_index,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- ALWAYS_INLINE void SetDexCacheResolvedMethod(uint16_t method_index,
- ArtMethod* new_method,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
- ALWAYS_INLINE void SetDexCacheResolvedMethods(mirror::MethodDexCacheType* new_dex_cache_methods,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
- bool HasDexCacheResolvedMethods(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_);
- bool HasSameDexCacheResolvedMethods(ArtMethod* other, PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
- bool HasSameDexCacheResolvedMethods(mirror::MethodDexCacheType* other_cache,
- PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Lookup the Class* from the type index into this method's dex cache.
ObjPtr<mirror::Class> LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -427,12 +411,6 @@ class ArtMethod FINAL {
void UnregisterNative() REQUIRES_SHARED(Locks::mutator_lock_);
- static MemberOffset DexCacheResolvedMethodsOffset(PointerSize pointer_size) {
- return MemberOffset(PtrSizedFieldsOffset(pointer_size) + OFFSETOF_MEMBER(
- PtrSizedFields, dex_cache_resolved_methods_) / sizeof(void*)
- * static_cast<size_t>(pointer_size));
- }
-
static MemberOffset DataOffset(PointerSize pointer_size) {
return MemberOffset(PtrSizedFieldsOffset(pointer_size) + OFFSETOF_MEMBER(
PtrSizedFields, data_) / sizeof(void*) * static_cast<size_t>(pointer_size));
@@ -686,8 +664,7 @@ class ArtMethod FINAL {
// Update heap objects and non-entrypoint pointers by the passed in visitor for image relocation.
// Does not use read barrier.
template <typename Visitor>
- ALWAYS_INLINE void UpdateObjectsForImageRelocation(const Visitor& visitor,
- PointerSize pointer_size)
+ ALWAYS_INLINE void UpdateObjectsForImageRelocation(const Visitor& visitor)
REQUIRES_SHARED(Locks::mutator_lock_);
// Update entry points by passing them through the visitor.
@@ -728,9 +705,6 @@ class ArtMethod FINAL {
// Must be the last fields in the method.
struct PtrSizedFields {
- // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
- mirror::MethodDexCacheType* dex_cache_resolved_methods_;
-
// Depending on the method type, the data is
// - native method: pointer to the JNI function registered to this method
// or a function to resolve the JNI function,
diff --git a/runtime/asm_support.h b/runtime/asm_support.h
index 44c0661e3f..f4830e2db5 100644
--- a/runtime/asm_support.h
+++ b/runtime/asm_support.h
@@ -141,7 +141,7 @@ ADD_TEST_EQ(SHADOWFRAME_CACHED_HOTNESS_COUNTDOWN_OFFSET,
#define SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET (SHADOWFRAME_NUMBER_OF_VREGS_OFFSET + 10)
ADD_TEST_EQ(SHADOWFRAME_HOTNESS_COUNTDOWN_OFFSET,
static_cast<int32_t>(art::ShadowFrame::HotnessCountdownOffset()))
-#define SHADOWFRAME_VREGS_OFFSET (SHADOWFRAME_NUMBER_OF_VREGS_OFFSET + 12)
+#define SHADOWFRAME_VREGS_OFFSET (SHADOWFRAME_NUMBER_OF_VREGS_OFFSET + 16)
ADD_TEST_EQ(SHADOWFRAME_VREGS_OFFSET,
static_cast<int32_t>(art::ShadowFrame::VRegsOffset()))
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 439ecaf28e..d6f003027b 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -186,7 +186,8 @@ inline ArtMethod* ClassLinker::GetResolvedMethod(uint32_t method_idx, ArtMethod*
// lookup in the context of the original method from where it steals the code.
// However, we delay the GetInterfaceMethodIfProxy() until needed.
DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor());
- ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_);
+ ArtMethod* resolved_method = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedMethod(
+ method_idx, image_pointer_size_);
if (resolved_method == nullptr) {
return nullptr;
}
@@ -226,7 +227,8 @@ inline ArtMethod* ClassLinker::ResolveMethod(Thread* self,
// However, we delay the GetInterfaceMethodIfProxy() until needed.
DCHECK(!referrer->IsProxyMethod() || referrer->IsConstructor());
Thread::PoisonObjectPointersIfDebug();
- ArtMethod* resolved_method = referrer->GetDexCacheResolvedMethod(method_idx, image_pointer_size_);
+ ArtMethod* resolved_method = referrer->GetDexCache<kWithoutReadBarrier>()->GetResolvedMethod(
+ method_idx, image_pointer_size_);
DCHECK(resolved_method == nullptr || !resolved_method->IsRuntimeMethod());
if (UNLIKELY(resolved_method == nullptr)) {
referrer = referrer->GetInterfaceMethodIfProxy(image_pointer_size_);
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 051c0c2938..1beb7837d4 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1115,35 +1115,6 @@ static bool FlattenPathClassLoader(ObjPtr<mirror::ClassLoader> class_loader,
return true;
}
-class FixupArtMethodArrayVisitor : public ArtMethodVisitor {
- public:
- explicit FixupArtMethodArrayVisitor(const ImageHeader& header) : header_(header) {}
-
- virtual void Visit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
- const bool is_copied = method->IsCopied();
- mirror::MethodDexCacheType* resolved_methods =
- method->GetDexCacheResolvedMethods(kRuntimePointerSize);
- if (resolved_methods != nullptr) {
- bool in_image_space = false;
- if (kIsDebugBuild || is_copied) {
- in_image_space = header_.GetImageSection(ImageHeader::kSectionDexCacheArrays).Contains(
- reinterpret_cast<const uint8_t*>(resolved_methods) - header_.GetImageBegin());
- }
- // Must be in image space for non-miranda method.
- DCHECK(is_copied || in_image_space)
- << resolved_methods << " is not in image starting at "
- << reinterpret_cast<void*>(header_.GetImageBegin());
- if (!is_copied || in_image_space) {
- method->SetDexCacheResolvedMethods(method->GetDexCache()->GetResolvedMethods(),
- kRuntimePointerSize);
- }
- }
- }
-
- private:
- const ImageHeader& header_;
-};
-
class VerifyDeclaringClassVisitor : public ArtMethodVisitor {
public:
VerifyDeclaringClassVisitor() REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_)
@@ -1492,12 +1463,6 @@ bool AppImageClassLoadersAndDexCachesHelper::Update(
FixupInternVisitor fixup_intern_visitor;
bitmap->VisitMarkedRange(objects_begin, objects_end, fixup_intern_visitor);
}
- if (*out_forward_dex_cache_array) {
- ScopedTrace timing("Fixup ArtMethod dex cache arrays");
- FixupArtMethodArrayVisitor visitor(header);
- header.VisitPackedArtMethods(&visitor, space->Begin(), kRuntimePointerSize);
- Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get());
- }
if (kVerifyArtMethodDeclaringClasses) {
ScopedTrace timing("Verify declaring classes");
ReaderMutexLock rmu(self, *Locks::heap_bitmap_lock_);
@@ -3444,8 +3409,6 @@ void ClassLinker::LoadMethod(const DexFile& dex_file,
dst->SetDeclaringClass(klass.Get());
dst->SetCodeItemOffset(it.GetMethodCodeItemOffset());
- dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods(), image_pointer_size_);
-
uint32_t access_flags = it.GetMethodAccessFlags();
if (UNLIKELY(strcmp("finalize", method_name) == 0)) {
@@ -4729,7 +4692,6 @@ void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) cons
// The proxy method doesn't have its own dex cache or dex file and so it steals those of its
// interface prototype. The exception to this are Constructors and the Class of the Proxy itself.
- CHECK(prototype->HasSameDexCacheResolvedMethods(method, image_pointer_size_));
auto* np = method->GetInterfaceMethodIfProxy(image_pointer_size_);
CHECK_EQ(prototype->GetDeclaringClass()->GetDexCache(), np->GetDexCache());
CHECK_EQ(prototype->GetDexMethodIndex(), method->GetDexMethodIndex());
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 5e9707c062..f887b8ed42 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -245,11 +245,6 @@ class ClassLinkerTest : public CommonRuntimeTest {
EXPECT_TRUE(method->GetDeclaringClass() != nullptr);
EXPECT_TRUE(method->GetName() != nullptr);
EXPECT_TRUE(method->GetSignature() != Signature::NoSignature());
-
- EXPECT_TRUE(method->HasDexCacheResolvedMethods(kRuntimePointerSize));
- EXPECT_TRUE(method->HasSameDexCacheResolvedMethods(
- method->GetDeclaringClass()->GetDexCache()->GetResolvedMethods(),
- kRuntimePointerSize));
}
void AssertField(ObjPtr<mirror::Class> klass, ArtField* field)
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index be3e4f811a..8253739427 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -83,7 +83,7 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method,
ObjPtr<mirror::DexCache> dex_cache = caller->GetDexCache();
const DexFile* dex_file = dex_cache->GetDexFile();
const DexFile::MethodId& method_id = dex_file->GetMethodId(method_index);
- ArtMethod* inlined_method = caller->GetDexCacheResolvedMethod(method_index, kRuntimePointerSize);
+ ArtMethod* inlined_method = dex_cache->GetResolvedMethod(method_index, kRuntimePointerSize);
if (inlined_method != nullptr) {
DCHECK(!inlined_method->IsRuntimeMethod());
return inlined_method;
@@ -106,7 +106,7 @@ inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method,
<< dex_file->GetMethodSignature(method_id) << " declared. "
<< "This must be due to duplicate classes or playing wrongly with class loaders";
}
- caller->SetDexCacheResolvedMethod(method_index, inlined_method, kRuntimePointerSize);
+ dex_cache->SetResolvedMethod(method_index, inlined_method, kRuntimePointerSize);
return inlined_method;
}
diff --git a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
index 5f40711753..53f0727a5f 100644
--- a/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_deoptimization_entrypoints.cc
@@ -73,11 +73,7 @@ extern "C" NO_RETURN void artDeoptimizeFromCompiledCode(DeoptimizationKind kind,
// Before deoptimizing to interpreter, we must push the deoptimization context.
JValue return_value;
return_value.SetJ(0); // we never deoptimize from compiled code with an invoke result.
- self->PushDeoptimizationContext(return_value,
- false /* is_reference */,
- self->GetException(),
- true /* from_code */,
- DeoptimizationMethodType::kDefault);
+ self->PushDeoptimizationContext(return_value, false, /* from_code */ true, self->GetException());
artDeoptimizeImpl(self, kind, true);
}
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 5f713265df..c6abd2882a 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -744,11 +744,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self,
ObjPtr<mirror::Throwable> pending_exception;
bool from_code = false;
- DeoptimizationMethodType method_type;
- self->PopDeoptimizationContext(/* out */ &result,
- /* out */ &pending_exception,
- /* out */ &from_code,
- /* out */ &method_type);
+ self->PopDeoptimizationContext(&result, &pending_exception, /* out */ &from_code);
// Push a transition back into managed code onto the linked list in thread.
self->PushManagedStackFragment(&fragment);
@@ -775,11 +771,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self,
if (pending_exception != nullptr) {
self->SetException(pending_exception);
}
- interpreter::EnterInterpreterFromDeoptimize(self,
- deopt_frame,
- &result,
- from_code,
- DeoptimizationMethodType::kDefault);
+ interpreter::EnterInterpreterFromDeoptimize(self, deopt_frame, from_code, &result);
} else {
const char* old_cause = self->StartAssertNoThreadSuspension(
"Building interpreter shadow frame");
@@ -831,11 +823,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self,
// Push the context of the deoptimization stack so we can restore the return value and the
// exception before executing the deoptimized frames.
self->PushDeoptimizationContext(
- result,
- shorty[0] == 'L' || shorty[0] == '[', /* class or array */
- self->GetException(),
- false /* from_code */,
- DeoptimizationMethodType::kDefault);
+ result, shorty[0] == 'L', /* from_code */ false, self->GetException());
// Set special exception to cause deoptimization.
self->SetException(Thread::GetDeoptimizationException());
@@ -1053,8 +1041,7 @@ extern "C" TwoWordReturn artInstrumentationMethodExitFromCode(Thread* self,
CHECK(!self->IsExceptionPending()) << "Enter instrumentation exit stub with pending exception "
<< self->GetException()->Dump();
// Compute address of return PC and sanity check that it currently holds 0.
- size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA,
- CalleeSaveType::kSaveEverything);
+ size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, CalleeSaveType::kSaveRefsOnly);
uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) +
return_pc_offset);
CHECK_EQ(*return_pc, 0U);
@@ -1271,7 +1258,7 @@ extern "C" const void* artQuickResolutionTrampoline(
// FindVirtualMethodFor... This is ok for FindDexMethodIndexInOtherDexFile that only cares
// about the name and signature.
uint32_t update_dex_cache_method_index = called->GetDexMethodIndex();
- if (!called->HasSameDexCacheResolvedMethods(caller, kRuntimePointerSize)) {
+ if (called->GetDexFile() != caller->GetDexFile()) {
// Calling from one dex file to another, need to compute the method index appropriate to
// the caller's dex file. Since we get here only if the original called was a runtime
// method, we've got the correct dex_file and a dex_method_idx from above.
@@ -1283,12 +1270,16 @@ extern "C" const void* artQuickResolutionTrampoline(
called->FindDexMethodIndexInOtherDexFile(*caller_dex_file,
caller_method_name_and_sig_index);
}
- if ((update_dex_cache_method_index != DexFile::kDexNoIndex) &&
- (caller->GetDexCacheResolvedMethod(
- update_dex_cache_method_index, kRuntimePointerSize) != called)) {
- caller->SetDexCacheResolvedMethod(update_dex_cache_method_index,
- called,
- kRuntimePointerSize);
+ if (update_dex_cache_method_index != DexFile::kDexNoIndex) {
+ // Note: We do not need the read barrier for the dex cache as the SetResolvedMethod()
+ // operates on native (non-moveable) data and constants (num_resolved_methods_).
+ ObjPtr<mirror::DexCache> caller_dex_cache = caller->GetDexCache<kWithoutReadBarrier>();
+ if (caller_dex_cache->GetResolvedMethod(
+ update_dex_cache_method_index, kRuntimePointerSize) != called) {
+ caller_dex_cache->SetResolvedMethod(update_dex_cache_method_index,
+ called,
+ kRuntimePointerSize);
+ }
}
} else if (invoke_type == kStatic) {
const auto called_dex_method_idx = called->GetDexMethodIndex();
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 14e017abd9..1a48b46020 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1128,7 +1128,7 @@ class ImageSpaceLoader {
}
} else {
if (fixup_heap_objects_) {
- method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this), pointer_size_);
+ method->UpdateObjectsForImageRelocation(ForwardObjectAdapter(this));
}
method->UpdateEntrypoints<kWithoutReadBarrier>(ForwardCodeAdapter(this), pointer_size_);
}
diff --git a/runtime/generated/asm_support_gen.h b/runtime/generated/asm_support_gen.h
index 314c45e117..071d1aedb7 100644
--- a/runtime/generated/asm_support_gen.h
+++ b/runtime/generated/asm_support_gen.h
@@ -48,6 +48,10 @@ DEFINE_CHECK_EQ(static_cast<int32_t>(THREAD_IS_GC_MARKING_OFFSET), (static_cast<
DEFINE_CHECK_EQ(static_cast<int32_t>(THREAD_CARD_TABLE_OFFSET), (static_cast<int32_t>(art::Thread:: CardTableOffset<art::kRuntimePointerSize>().Int32Value())))
#define CODEITEM_INSNS_OFFSET 16
DEFINE_CHECK_EQ(static_cast<int32_t>(CODEITEM_INSNS_OFFSET), (static_cast<int32_t>(__builtin_offsetof(art::DexFile::CodeItem, insns_))))
+#define MIRROR_CLASS_DEX_CACHE_OFFSET 16
+DEFINE_CHECK_EQ(static_cast<int32_t>(MIRROR_CLASS_DEX_CACHE_OFFSET), (static_cast<int32_t>(art::mirror::Class:: DexCacheOffset().Int32Value())))
+#define MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET 48
+DEFINE_CHECK_EQ(static_cast<int32_t>(MIRROR_DEX_CACHE_RESOLVED_METHODS_OFFSET), (static_cast<int32_t>(art::mirror::DexCache:: ResolvedMethodsOffset().Int32Value())))
#define MIRROR_OBJECT_CLASS_OFFSET 0
DEFINE_CHECK_EQ(static_cast<int32_t>(MIRROR_OBJECT_CLASS_OFFSET), (static_cast<int32_t>(art::mirror::Object:: ClassOffset().Int32Value())))
#define MIRROR_OBJECT_LOCK_WORD_OFFSET 4
@@ -60,20 +64,18 @@ DEFINE_CHECK_EQ(static_cast<uint32_t>(ACCESS_FLAGS_CLASS_IS_FINALIZABLE), (stati
DEFINE_CHECK_EQ(static_cast<uint32_t>(ACCESS_FLAGS_CLASS_IS_INTERFACE), (static_cast<uint32_t>((art::kAccInterface))))
#define ACCESS_FLAGS_CLASS_IS_FINALIZABLE_BIT 0x1f
DEFINE_CHECK_EQ(static_cast<uint32_t>(ACCESS_FLAGS_CLASS_IS_FINALIZABLE_BIT), (static_cast<uint32_t>((art::MostSignificantBit(art::kAccClassIsFinalizable)))))
-#define ART_METHOD_DEX_CACHE_METHODS_OFFSET_32 20
-DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_DEX_CACHE_METHODS_OFFSET_32), (static_cast<int32_t>(art::ArtMethod:: DexCacheResolvedMethodsOffset(art::PointerSize::k32).Int32Value())))
-#define ART_METHOD_DEX_CACHE_METHODS_OFFSET_64 24
-DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_DEX_CACHE_METHODS_OFFSET_64), (static_cast<int32_t>(art::ArtMethod:: DexCacheResolvedMethodsOffset(art::PointerSize::k64).Int32Value())))
-#define ART_METHOD_JNI_OFFSET_32 24
+#define ART_METHOD_JNI_OFFSET_32 20
DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_JNI_OFFSET_32), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromJniOffset(art::PointerSize::k32).Int32Value())))
-#define ART_METHOD_JNI_OFFSET_64 32
+#define ART_METHOD_JNI_OFFSET_64 24
DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_JNI_OFFSET_64), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromJniOffset(art::PointerSize::k64).Int32Value())))
-#define ART_METHOD_QUICK_CODE_OFFSET_32 28
+#define ART_METHOD_QUICK_CODE_OFFSET_32 24
DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_QUICK_CODE_OFFSET_32), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromQuickCompiledCodeOffset(art::PointerSize::k32).Int32Value())))
-#define ART_METHOD_QUICK_CODE_OFFSET_64 40
+#define ART_METHOD_QUICK_CODE_OFFSET_64 32
DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_QUICK_CODE_OFFSET_64), (static_cast<int32_t>(art::ArtMethod:: EntryPointFromQuickCompiledCodeOffset(art::PointerSize::k64).Int32Value())))
#define ART_METHOD_DECLARING_CLASS_OFFSET 0
DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_DECLARING_CLASS_OFFSET), (static_cast<int32_t>(art::ArtMethod:: DeclaringClassOffset().Int32Value())))
+#define ART_METHOD_ACCESS_FLAGS_OFFSET 4
+DEFINE_CHECK_EQ(static_cast<int32_t>(ART_METHOD_ACCESS_FLAGS_OFFSET), (static_cast<int32_t>(art::ArtMethod:: AccessFlagsOffset().Int32Value())))
#define STRING_DEX_CACHE_ELEMENT_SIZE_SHIFT 3
DEFINE_CHECK_EQ(static_cast<int32_t>(STRING_DEX_CACHE_ELEMENT_SIZE_SHIFT), (static_cast<int32_t>(art::WhichPowerOf2(sizeof(art::mirror::StringDexCachePair)))))
#define STRING_DEX_CACHE_SIZE_MINUS_ONE 1023
@@ -126,6 +128,10 @@ DEFINE_CHECK_EQ(static_cast<size_t>(OBJECT_ALIGNMENT_MASK), (static_cast<size_t>
DEFINE_CHECK_EQ(static_cast<uint32_t>(OBJECT_ALIGNMENT_MASK_TOGGLED), (static_cast<uint32_t>(~static_cast<uint32_t>(art::kObjectAlignment - 1))))
#define OBJECT_ALIGNMENT_MASK_TOGGLED64 0xfffffffffffffff8
DEFINE_CHECK_EQ(static_cast<uint64_t>(OBJECT_ALIGNMENT_MASK_TOGGLED64), (static_cast<uint64_t>(~static_cast<uint64_t>(art::kObjectAlignment - 1))))
+#define ACC_OBSOLETE_METHOD 262144
+DEFINE_CHECK_EQ(static_cast<int32_t>(ACC_OBSOLETE_METHOD), (static_cast<int32_t>(art::kAccObsoleteMethod)))
+#define ACC_OBSOLETE_METHOD_SHIFT 18
+DEFINE_CHECK_EQ(static_cast<int32_t>(ACC_OBSOLETE_METHOD_SHIFT), (static_cast<int32_t>(art::WhichPowerOf2(art::kAccObsoleteMethod))))
#define ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE 128
DEFINE_CHECK_EQ(static_cast<int32_t>(ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE), (static_cast<int32_t>((art::gc::allocator::RosAlloc::kMaxThreadLocalBracketSize))))
#define ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT 3
diff --git a/runtime/image.cc b/runtime/image.cc
index 950ac5dcbf..1f7e0f31b5 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -26,7 +26,7 @@
namespace art {
const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '6', '\0' }; // Hash-based methods array.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '7', '\0' }; // Smaller ArtMethod.
ImageHeader::ImageHeader(uint32_t image_begin,
uint32_t image_size,
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 474e3684f6..05384b417c 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -26,7 +26,6 @@
#include "class_linker.h"
#include "debugger.h"
#include "dex_file-inl.h"
-#include "dex_instruction-inl.h"
#include "entrypoints/quick/quick_alloc_entrypoints.h"
#include "entrypoints/quick/quick_entrypoints.h"
#include "entrypoints/runtime_asm_entrypoints.h"
@@ -106,6 +105,7 @@ Instrumentation::Instrumentation()
have_field_read_listeners_(false),
have_field_write_listeners_(false),
have_exception_thrown_listeners_(false),
+ have_watched_frame_pop_listeners_(false),
have_branch_listeners_(false),
have_invoke_virtual_or_interface_listeners_(false),
deoptimized_methods_lock_("deoptimized methods lock", kDeoptimizedMethodsLock),
@@ -228,32 +228,39 @@ static void InstrumentationInstallStack(Thread* thread, void* arg)
return true; // Continue.
}
uintptr_t return_pc = GetReturnPc();
- if (kVerboseInstrumentation) {
- LOG(INFO) << " Installing exit stub in " << DescribeLocation();
- }
- if (return_pc == instrumentation_exit_pc_) {
- CHECK_LT(instrumentation_stack_depth_, instrumentation_stack_->size());
-
- if (m->IsRuntimeMethod()) {
+ if (m->IsRuntimeMethod()) {
+ if (return_pc == instrumentation_exit_pc_) {
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << " Handling quick to interpreter transition. Frame " << GetFrameId();
+ }
+ CHECK_LT(instrumentation_stack_depth_, instrumentation_stack_->size());
const InstrumentationStackFrame& frame =
instrumentation_stack_->at(instrumentation_stack_depth_);
- if (frame.interpreter_entry_) {
- // This instrumentation frame is for an interpreter bridge and is
- // pushed when executing the instrumented interpreter bridge. So method
- // enter event must have been reported. However we need to push a DEX pc
- // into the dex_pcs_ list to match size of instrumentation stack.
- uint32_t dex_pc = DexFile::kDexNoIndex;
- dex_pcs_.push_back(dex_pc);
- last_return_pc_ = frame.return_pc_;
- ++instrumentation_stack_depth_;
- return true;
+ CHECK(frame.interpreter_entry_);
+ // This is an interpreter frame so method enter event must have been reported. However we
+ // need to push a DEX pc into the dex_pcs_ list to match size of instrumentation stack.
+ // Since we won't report method entry here, we can safely push any DEX pc.
+ dex_pcs_.push_back(0);
+ last_return_pc_ = frame.return_pc_;
+ ++instrumentation_stack_depth_;
+ return true;
+ } else {
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << " Skipping runtime method. Frame " << GetFrameId();
}
+ last_return_pc_ = GetReturnPc();
+ return true; // Ignore unresolved methods since they will be instrumented after resolution.
}
-
+ }
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << " Installing exit stub in " << DescribeLocation();
+ }
+ if (return_pc == instrumentation_exit_pc_) {
// We've reached a frame which has already been installed with instrumentation exit stub.
// We should have already installed instrumentation on previous frames.
reached_existing_instrumentation_frames_ = true;
+ CHECK_LT(instrumentation_stack_depth_, instrumentation_stack_->size());
const InstrumentationStackFrame& frame =
instrumentation_stack_->at(instrumentation_stack_depth_);
CHECK_EQ(m, frame.method_) << "Expected " << ArtMethod::PrettyMethod(m)
@@ -265,12 +272,8 @@ static void InstrumentationInstallStack(Thread* thread, void* arg)
} else {
CHECK_NE(return_pc, 0U);
CHECK(!reached_existing_instrumentation_frames_);
- InstrumentationStackFrame instrumentation_frame(
- m->IsRuntimeMethod() ? nullptr : GetThisObject(),
- m,
- return_pc,
- GetFrameId(), // A runtime method still gets a frame id.
- false);
+ InstrumentationStackFrame instrumentation_frame(GetThisObject(), m, return_pc, GetFrameId(),
+ false);
if (kVerboseInstrumentation) {
LOG(INFO) << "Pushing frame " << instrumentation_frame.Dump();
}
@@ -287,12 +290,9 @@ static void InstrumentationInstallStack(Thread* thread, void* arg)
instrumentation_stack_->insert(it, instrumentation_frame);
SetReturnPc(instrumentation_exit_pc_);
}
- uint32_t dex_pc = DexFile::kDexNoIndex;
- if (last_return_pc_ != 0 &&
- GetCurrentOatQuickMethodHeader() != nullptr) {
- dex_pc = GetCurrentOatQuickMethodHeader()->ToDexPc(m, last_return_pc_);
- }
- dex_pcs_.push_back(dex_pc);
+ dex_pcs_.push_back((GetCurrentOatQuickMethodHeader() == nullptr)
+ ? DexFile::kDexNoIndex
+ : GetCurrentOatQuickMethodHeader()->ToDexPc(m, last_return_pc_));
last_return_pc_ = return_pc;
++instrumentation_stack_depth_;
return true; // Continue.
@@ -390,8 +390,7 @@ static void InstrumentationRestoreStack(Thread* thread, void* arg)
CHECK(m == instrumentation_frame.method_) << ArtMethod::PrettyMethod(m);
}
SetReturnPc(instrumentation_frame.return_pc_);
- if (instrumentation_->ShouldNotifyMethodEnterExitEvents() &&
- !m->IsRuntimeMethod()) {
+ if (instrumentation_->ShouldNotifyMethodEnterExitEvents()) {
// Create the method exit events. As the methods didn't really exit the result is 0.
// We only do this if no debugger is attached to prevent from posting events twice.
instrumentation_->MethodExitEvent(thread_, instrumentation_frame.this_object_, m,
@@ -506,6 +505,11 @@ void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t ev
exception_thrown_listeners_,
listener,
&have_exception_thrown_listeners_);
+ PotentiallyAddListenerTo(kWatchedFramePop,
+ events,
+ watched_frame_pop_listeners_,
+ listener,
+ &have_watched_frame_pop_listeners_);
UpdateInterpreterHandlerTable();
}
@@ -583,6 +587,11 @@ void Instrumentation::RemoveListener(InstrumentationListener* listener, uint32_t
exception_thrown_listeners_,
listener,
&have_exception_thrown_listeners_);
+ PotentiallyRemoveListenerFrom(kWatchedFramePop,
+ events,
+ watched_frame_pop_listeners_,
+ listener,
+ &have_watched_frame_pop_listeners_);
UpdateInterpreterHandlerTable();
}
@@ -949,7 +958,6 @@ void Instrumentation::MethodEnterEventImpl(Thread* thread,
ObjPtr<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc) const {
- DCHECK(!method->IsRuntimeMethod());
if (HasMethodEntryListeners()) {
Thread* self = Thread::Current();
StackHandleScope<1> hs(self);
@@ -1045,6 +1053,14 @@ void Instrumentation::InvokeVirtualOrInterfaceImpl(Thread* thread,
}
}
+void Instrumentation::WatchedFramePopImpl(Thread* thread, const ShadowFrame& frame) const {
+ for (InstrumentationListener* listener : watched_frame_pop_listeners_) {
+ if (listener != nullptr) {
+ listener->WatchedFramePop(thread, frame);
+ }
+ }
+}
+
void Instrumentation::FieldReadEventImpl(Thread* thread,
ObjPtr<mirror::Object> this_object,
ArtMethod* method,
@@ -1154,54 +1170,6 @@ void Instrumentation::PushInstrumentationStackFrame(Thread* self, mirror::Object
stack->push_front(instrumentation_frame);
}
-DeoptimizationMethodType Instrumentation::GetDeoptimizationMethodType(ArtMethod* method) {
- if (method->IsRuntimeMethod()) {
- // Certain methods have strict requirement on whether the dex instruction
- // should be re-executed upon deoptimization.
- if (method == Runtime::Current()->GetCalleeSaveMethod(
- CalleeSaveType::kSaveEverythingForClinit)) {
- return DeoptimizationMethodType::kKeepDexPc;
- }
- if (method == Runtime::Current()->GetCalleeSaveMethod(
- CalleeSaveType::kSaveEverythingForSuspendCheck)) {
- return DeoptimizationMethodType::kKeepDexPc;
- }
- }
- return DeoptimizationMethodType::kDefault;
-}
-
-// Try to get the shorty of a runtime method if it's an invocation stub.
-struct RuntimeMethodShortyVisitor : public StackVisitor {
- explicit RuntimeMethodShortyVisitor(Thread* thread)
- : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
- shorty('V') {}
-
- bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) {
- ArtMethod* m = GetMethod();
- if (m != nullptr && !m->IsRuntimeMethod()) {
- // The first Java method.
- if (m->IsNative()) {
- // Use JNI method's shorty for the jni stub.
- shorty = m->GetShorty()[0];
- return false;
- }
- const DexFile::CodeItem* code_item = m->GetCodeItem();
- const Instruction* instr = Instruction::At(&code_item->insns_[GetDexPc()]);
- if (instr->IsInvoke()) {
- // If it's an invoke, use its shorty.
- uint32_t method_idx = instr->VRegB();
- shorty = m->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetDexFile()
- ->GetMethodShorty(method_idx)[0];
- }
- // Stop stack walking since we've seen a Java frame.
- return false;
- }
- return true;
- }
-
- char shorty;
-};
-
TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
uintptr_t* return_pc,
uint64_t* gpr_result,
@@ -1222,36 +1190,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
ArtMethod* method = instrumentation_frame.method_;
uint32_t length;
const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- char return_shorty;
-
- // Runtime method does not call into MethodExitEvent() so there should not be
- // suspension point below.
- ScopedAssertNoThreadSuspension ants(__FUNCTION__, method->IsRuntimeMethod());
- if (method->IsRuntimeMethod()) {
- if (method != Runtime::Current()->GetCalleeSaveMethod(
- CalleeSaveType::kSaveEverythingForClinit)) {
- // If the caller is at an invocation point and the runtime method is not
- // for clinit, we need to pass return results to the caller.
- // We need the correct shorty to decide whether we need to pass the return
- // result for deoptimization below.
- RuntimeMethodShortyVisitor visitor(self);
- visitor.WalkStack();
- return_shorty = visitor.shorty;
- } else {
- // Some runtime methods such as allocations, unresolved field getters, etc.
- // have return value. We don't need to set return_value since MethodExitEvent()
- // below isn't called for runtime methods. Deoptimization doesn't need the
- // value either since the dex instruction will be re-executed by the
- // interpreter, except these two cases:
- // (1) For an invoke, which is handled above to get the correct shorty.
- // (2) For MONITOR_ENTER/EXIT, which cannot be re-executed since it's not
- // idempotent. However there is no return value for it anyway.
- return_shorty = 'V';
- }
- } else {
- return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
- }
-
+ char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
bool is_ref = return_shorty == '[' || return_shorty == 'L';
StackHandleScope<1> hs(self);
MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr));
@@ -1271,7 +1210,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
// return_pc.
uint32_t dex_pc = DexFile::kDexNoIndex;
mirror::Object* this_object = instrumentation_frame.this_object_;
- if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) {
+ if (!instrumentation_frame.interpreter_entry_) {
MethodExitEvent(self, this_object, instrumentation_frame.method_, dex_pc, return_value);
}
@@ -1297,12 +1236,10 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
<< " in "
<< *self;
}
- DeoptimizationMethodType deopt_method_type = GetDeoptimizationMethodType(method);
self->PushDeoptimizationContext(return_value,
- return_shorty == 'L' || return_shorty == '[',
- nullptr /* no pending exception */,
+ return_shorty == 'L',
false /* from_code */,
- deopt_method_type);
+ nullptr /* no pending exception */);
return GetTwoWordSuccessValue(*return_pc,
reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint()));
} else {
@@ -1339,9 +1276,7 @@ uintptr_t Instrumentation::PopMethodForUnwind(Thread* self, bool is_deoptimizati
// TODO: improve the dex pc information here, requires knowledge of current PC as opposed to
// return_pc.
uint32_t dex_pc = DexFile::kDexNoIndex;
- if (!method->IsRuntimeMethod()) {
- MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc);
- }
+ MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc);
}
// TODO: bring back CheckStackDepth(self, instrumentation_frame, 2);
CHECK_EQ(stack->size(), idx);
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index de416c77e8..114db7682d 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -38,8 +38,8 @@ class ArtField;
class ArtMethod;
template <typename T> class Handle;
union JValue;
+class ShadowFrame;
class Thread;
-enum class DeoptimizationMethodType;
namespace instrumentation {
@@ -144,6 +144,15 @@ struct InstrumentationListener {
uint32_t dex_pc,
ArtMethod* callee)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ // Call-back when a shadow_frame with the needs_notify_pop_ boolean set is popped off the stack by
+ // either return or exceptions. Normally instrumentation listeners should ensure that there are
+ // shadow-frames by deoptimizing stacks.
+ virtual void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED,
+ const ShadowFrame& frame ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return;
+ }
};
// Instrumentation is a catch-all for when extra information is required from the runtime. The
@@ -162,6 +171,7 @@ class Instrumentation {
kExceptionThrown = 0x40,
kBranch = 0x80,
kInvokeVirtualOrInterface = 0x100,
+ kWatchedFramePop = 0x200,
};
enum class InstrumentationLevel {
@@ -335,11 +345,16 @@ class Instrumentation {
return have_invoke_virtual_or_interface_listeners_;
}
+ bool HasWatchedFramePopListeners() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ return have_watched_frame_pop_listeners_;
+ }
+
bool IsActive() const REQUIRES_SHARED(Locks::mutator_lock_) {
return have_dex_pc_listeners_ || have_method_entry_listeners_ || have_method_exit_listeners_ ||
have_field_read_listeners_ || have_field_write_listeners_ ||
have_exception_thrown_listeners_ || have_method_unwind_listeners_ ||
- have_branch_listeners_ || have_invoke_virtual_or_interface_listeners_;
+ have_branch_listeners_ || have_invoke_virtual_or_interface_listeners_ ||
+ have_watched_frame_pop_listeners_;
}
// Any instrumentation *other* than what is needed for Jit profiling active?
@@ -347,7 +362,7 @@ class Instrumentation {
return have_dex_pc_listeners_ || have_method_exit_listeners_ ||
have_field_read_listeners_ || have_field_write_listeners_ ||
have_exception_thrown_listeners_ || have_method_unwind_listeners_ ||
- have_branch_listeners_;
+ have_branch_listeners_ || have_watched_frame_pop_listeners_;
}
// Inform listeners that a method has been entered. A dex PC is provided as we may install
@@ -425,6 +440,14 @@ class Instrumentation {
}
}
+ // Inform listeners that a branch has been taken (only supported by the interpreter).
+ void WatchedFramePopped(Thread* thread, const ShadowFrame& frame) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (UNLIKELY(HasWatchedFramePopListeners())) {
+ WatchedFramePopImpl(thread, frame);
+ }
+ }
+
// Inform listeners that an exception was thrown.
void ExceptionThrownEvent(Thread* thread, mirror::Throwable* exception_object) const
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -436,9 +459,6 @@ class Instrumentation {
bool interpreter_entry)
REQUIRES_SHARED(Locks::mutator_lock_);
- DeoptimizationMethodType GetDeoptimizationMethodType(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Called when an instrumented method is exited. Removes the pushed instrumentation frame
// returning the intended link register. Generates method exit events. The gpr_result and
// fpr_result pointers are pointers to the locations where the integer/pointer and floating point
@@ -535,6 +555,8 @@ class Instrumentation {
uint32_t dex_pc,
ArtMethod* callee) const
REQUIRES_SHARED(Locks::mutator_lock_);
+ void WatchedFramePopImpl(Thread* thread, const ShadowFrame& frame) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
void FieldReadEventImpl(Thread* thread,
ObjPtr<mirror::Object> this_object,
ArtMethod* method,
@@ -606,6 +628,9 @@ class Instrumentation {
// Do we have any exception thrown listeners? Short-cut to avoid taking the instrumentation_lock_.
bool have_exception_thrown_listeners_ GUARDED_BY(Locks::mutator_lock_);
+ // Do we have any frame pop listeners? Short-cut to avoid taking the instrumentation_lock_.
+ bool have_watched_frame_pop_listeners_ GUARDED_BY(Locks::mutator_lock_);
+
// Do we have any branch listeners? Short-cut to avoid taking the instrumentation_lock_.
bool have_branch_listeners_ GUARDED_BY(Locks::mutator_lock_);
@@ -637,6 +662,7 @@ class Instrumentation {
std::list<InstrumentationListener*> field_read_listeners_ GUARDED_BY(Locks::mutator_lock_);
std::list<InstrumentationListener*> field_write_listeners_ GUARDED_BY(Locks::mutator_lock_);
std::list<InstrumentationListener*> exception_thrown_listeners_ GUARDED_BY(Locks::mutator_lock_);
+ std::list<InstrumentationListener*> watched_frame_pop_listeners_ GUARDED_BY(Locks::mutator_lock_);
// The set of methods being deoptimized (by the debugger) which must be executed with interpreter
// only.
@@ -665,15 +691,9 @@ std::ostream& operator<<(std::ostream& os, const Instrumentation::Instrumentatio
// An element in the instrumentation side stack maintained in art::Thread.
struct InstrumentationStackFrame {
- InstrumentationStackFrame(mirror::Object* this_object,
- ArtMethod* method,
- uintptr_t return_pc,
- size_t frame_id,
- bool interpreter_entry)
- : this_object_(this_object),
- method_(method),
- return_pc_(return_pc),
- frame_id_(frame_id),
+ InstrumentationStackFrame(mirror::Object* this_object, ArtMethod* method,
+ uintptr_t return_pc, size_t frame_id, bool interpreter_entry)
+ : this_object_(this_object), method_(method), return_pc_(return_pc), frame_id_(frame_id),
interpreter_entry_(interpreter_entry) {
}
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
index 5ec07e3508..7390f4fc97 100644
--- a/runtime/instrumentation_test.cc
+++ b/runtime/instrumentation_test.cc
@@ -28,6 +28,7 @@
#include "jvalue.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
+#include "interpreter/shadow_frame.h"
#include "thread-inl.h"
#include "thread_list.h"
#include "well_known_classes.h"
@@ -48,7 +49,8 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio
received_field_written_object_event(false),
received_exception_thrown_event(false),
received_branch_event(false),
- received_invoke_virtual_or_interface_event(false) {}
+ received_invoke_virtual_or_interface_event(false),
+ received_watched_frame_pop(false) {}
virtual ~TestInstrumentationListener() {}
@@ -146,6 +148,11 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio
received_invoke_virtual_or_interface_event = true;
}
+ void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED, const ShadowFrame& frame ATTRIBUTE_UNUSED)
+ OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ received_watched_frame_pop = true;
+ }
+
void Reset() {
received_method_enter_event = false;
received_method_exit_event = false;
@@ -158,6 +165,7 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio
received_exception_thrown_event = false;
received_branch_event = false;
received_invoke_virtual_or_interface_event = false;
+ received_watched_frame_pop = false;
}
bool received_method_enter_event;
@@ -171,6 +179,7 @@ class TestInstrumentationListener FINAL : public instrumentation::Instrumentatio
bool received_exception_thrown_event;
bool received_branch_event;
bool received_invoke_virtual_or_interface_event;
+ bool received_watched_frame_pop;
private:
DISALLOW_COPY_AND_ASSIGN(TestInstrumentationListener);
@@ -221,6 +230,7 @@ class InstrumentationTest : public CommonRuntimeTest {
mirror::Object* const event_obj = nullptr;
const uint32_t event_dex_pc = 0;
+ ShadowFrameAllocaUniquePtr test_frame = CREATE_SHADOW_FRAME(0, nullptr, event_method, 0);
// Check the listener is registered and is notified of the event.
EXPECT_TRUE(HasEventListener(instr, instrumentation_event));
@@ -231,7 +241,8 @@ class InstrumentationTest : public CommonRuntimeTest {
event_method,
event_obj,
event_field,
- event_dex_pc);
+ event_dex_pc,
+ *test_frame);
EXPECT_TRUE(DidListenerReceiveEvent(listener, instrumentation_event, with_object));
listener.Reset();
@@ -250,7 +261,8 @@ class InstrumentationTest : public CommonRuntimeTest {
event_method,
event_obj,
event_field,
- event_dex_pc);
+ event_dex_pc,
+ *test_frame);
EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event, with_object));
}
@@ -361,6 +373,8 @@ class InstrumentationTest : public CommonRuntimeTest {
return instr->HasBranchListeners();
case instrumentation::Instrumentation::kInvokeVirtualOrInterface:
return instr->HasInvokeVirtualOrInterfaceListeners();
+ case instrumentation::Instrumentation::kWatchedFramePop:
+ return instr->HasWatchedFramePopListeners();
default:
LOG(FATAL) << "Unknown instrumentation event " << event_type;
UNREACHABLE();
@@ -373,7 +387,8 @@ class InstrumentationTest : public CommonRuntimeTest {
ArtMethod* method,
mirror::Object* obj,
ArtField* field,
- uint32_t dex_pc)
+ uint32_t dex_pc,
+ const ShadowFrame& frame)
REQUIRES_SHARED(Locks::mutator_lock_) {
switch (event_type) {
case instrumentation::Instrumentation::kMethodEntered:
@@ -411,6 +426,9 @@ class InstrumentationTest : public CommonRuntimeTest {
case instrumentation::Instrumentation::kInvokeVirtualOrInterface:
instr->InvokeVirtualOrInterface(self, obj, method, dex_pc, method);
break;
+ case instrumentation::Instrumentation::kWatchedFramePop:
+ instr->WatchedFramePopped(self, frame);
+ break;
default:
LOG(FATAL) << "Unknown instrumentation event " << event_type;
UNREACHABLE();
@@ -441,6 +459,8 @@ class InstrumentationTest : public CommonRuntimeTest {
return listener.received_branch_event;
case instrumentation::Instrumentation::kInvokeVirtualOrInterface:
return listener.received_invoke_virtual_or_interface_event;
+ case instrumentation::Instrumentation::kWatchedFramePop:
+ return listener.received_watched_frame_pop;
default:
LOG(FATAL) << "Unknown instrumentation event " << event_type;
UNREACHABLE();
@@ -473,23 +493,7 @@ TEST_F(InstrumentationTest, NoInstrumentation) {
// Test instrumentation listeners for each event.
TEST_F(InstrumentationTest, MethodEntryEvent) {
- ScopedObjectAccess soa(Thread::Current());
- jobject class_loader = LoadDex("Instrumentation");
- Runtime* const runtime = Runtime::Current();
- ClassLinker* class_linker = runtime->GetClassLinker();
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
- mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
- ASSERT_TRUE(klass != nullptr);
- ArtMethod* method =
- klass->FindClassMethod("returnReference", "()Ljava/lang/Object;", kRuntimePointerSize);
- ASSERT_TRUE(method != nullptr);
- ASSERT_TRUE(method->IsDirect());
- ASSERT_TRUE(method->GetDeclaringClass() == klass);
- TestEvent(instrumentation::Instrumentation::kMethodEntered,
- /*event_method*/ method,
- /*event_field*/ nullptr,
- /*with_object*/ true);
+ TestEvent(instrumentation::Instrumentation::kMethodEntered);
}
TEST_F(InstrumentationTest, MethodExitObjectEvent) {
@@ -543,6 +547,10 @@ TEST_F(InstrumentationTest, FieldReadEvent) {
TestEvent(instrumentation::Instrumentation::kFieldRead);
}
+TEST_F(InstrumentationTest, WatchedFramePop) {
+ TestEvent(instrumentation::Instrumentation::kWatchedFramePop);
+}
+
TEST_F(InstrumentationTest, FieldWriteObjectEvent) {
ScopedObjectAccess soa(Thread::Current());
jobject class_loader = LoadDex("Instrumentation");
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 34332d7df7..9cb74f7c36 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -499,9 +499,8 @@ static int16_t GetReceiverRegisterForStringInit(const Instruction* instr) {
void EnterInterpreterFromDeoptimize(Thread* self,
ShadowFrame* shadow_frame,
- JValue* ret_val,
bool from_code,
- DeoptimizationMethodType deopt_method_type)
+ JValue* ret_val)
REQUIRES_SHARED(Locks::mutator_lock_) {
JValue value;
// Set value to last known result in case the shadow frame chain is empty.
@@ -528,27 +527,11 @@ void EnterInterpreterFromDeoptimize(Thread* self,
new_dex_pc = found_dex_pc; // the dex pc of a matching catch handler
// or DexFile::kDexNoIndex if there is none.
} else if (!from_code) {
- // Deoptimization is not called from code directly.
+ // For the debugger and full deoptimization stack, we must go past the invoke
+ // instruction, as it already executed.
+ // TODO: should be tested more once b/17586779 is fixed.
const Instruction* instr = Instruction::At(&code_item->insns_[dex_pc]);
- if (deopt_method_type == DeoptimizationMethodType::kKeepDexPc) {
- DCHECK(first);
- // Need to re-execute the dex instruction.
- // (1) An invocation might be split into class initialization and invoke.
- // In this case, the invoke should not be skipped.
- // (2) A suspend check should also execute the dex instruction at the
- // corresponding dex pc.
- DCHECK_EQ(new_dex_pc, dex_pc);
- } else if (instr->Opcode() == Instruction::MONITOR_ENTER ||
- instr->Opcode() == Instruction::MONITOR_EXIT) {
- DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
- DCHECK(first);
- // Non-idempotent dex instruction should not be re-executed.
- // On the other hand, if a MONITOR_ENTER is at the dex_pc of a suspend
- // check, that MONITOR_ENTER should be executed. That case is handled
- // above.
- new_dex_pc = dex_pc + instr->SizeInCodeUnits();
- } else if (instr->IsInvoke()) {
- DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
+ if (instr->IsInvoke()) {
if (IsStringInit(instr, shadow_frame->GetMethod())) {
uint16_t this_obj_vreg = GetReceiverRegisterForStringInit(instr);
// Move the StringFactory.newStringFromChars() result into the register representing
@@ -561,27 +544,30 @@ void EnterInterpreterFromDeoptimize(Thread* self,
}
new_dex_pc = dex_pc + instr->SizeInCodeUnits();
} else if (instr->Opcode() == Instruction::NEW_INSTANCE) {
- // A NEW_INSTANCE is simply re-executed, including
- // "new-instance String" which is compiled into a call into
- // StringFactory.newEmptyString().
- DCHECK_EQ(new_dex_pc, dex_pc);
+ // It's possible to deoptimize at a NEW_INSTANCE dex instruciton that's for a
+ // java string, which is turned into a call into StringFactory.newEmptyString();
+ // Move the StringFactory.newEmptyString() result into the destination register.
+ DCHECK(value.GetL()->IsString());
+ shadow_frame->SetVRegReference(instr->VRegA_21c(), value.GetL());
+ // new-instance doesn't generate a result value.
+ value.SetJ(0);
+ // Skip the dex instruction since we essentially come back from an invocation.
+ new_dex_pc = dex_pc + instr->SizeInCodeUnits();
+ if (kIsDebugBuild) {
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ // This is a suspend point. But it's ok since value has been set into shadow_frame.
+ ObjPtr<mirror::Class> klass = class_linker->ResolveType(
+ dex::TypeIndex(instr->VRegB_21c()), shadow_frame->GetMethod());
+ DCHECK(klass->IsStringClass());
+ }
} else {
- DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
- DCHECK(first);
- // By default, we re-execute the dex instruction since if they are not
- // an invoke, so that we don't have to decode the dex instruction to move
- // result into the right vreg. All slow paths have been audited to be
- // idempotent except monitor-enter/exit and invocation stubs.
- // TODO: move result and advance dex pc. That also requires that we
- // can tell the return type of a runtime method, possibly by decoding
- // the dex instruction at the caller.
- DCHECK_EQ(new_dex_pc, dex_pc);
+ CHECK(false) << "Unexpected instruction opcode " << instr->Opcode()
+ << " at dex_pc " << dex_pc
+ << " of method: " << ArtMethod::PrettyMethod(shadow_frame->GetMethod(), false);
}
} else {
// Nothing to do, the dex_pc is the one at which the code requested
// the deoptimization.
- DCHECK(first);
- DCHECK_EQ(new_dex_pc, dex_pc);
}
if (new_dex_pc != DexFile::kDexNoIndex) {
shadow_frame->SetDexPC(new_dex_pc);
@@ -590,10 +576,8 @@ void EnterInterpreterFromDeoptimize(Thread* self,
ShadowFrame* old_frame = shadow_frame;
shadow_frame = shadow_frame->GetLink();
ShadowFrame::DeleteDeoptimizedFrame(old_frame);
- // Following deoptimizations of shadow frames must be at invocation point
- // and should advance dex pc past the invoke instruction.
+ // Following deoptimizations of shadow frames must pass the invoke instruction.
from_code = false;
- deopt_method_type = DeoptimizationMethodType::kDefault;
first = false;
}
ret_val->SetJ(value.GetJ());
diff --git a/runtime/interpreter/interpreter.h b/runtime/interpreter/interpreter.h
index df8568edcd..65cfade09a 100644
--- a/runtime/interpreter/interpreter.h
+++ b/runtime/interpreter/interpreter.h
@@ -30,7 +30,6 @@ class ArtMethod;
union JValue;
class ShadowFrame;
class Thread;
-enum class DeoptimizationMethodType;
namespace interpreter {
@@ -45,11 +44,8 @@ extern void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method,
REQUIRES_SHARED(Locks::mutator_lock_);
// 'from_code' denotes whether the deoptimization was explicitly triggered by compiled code.
-extern void EnterInterpreterFromDeoptimize(Thread* self,
- ShadowFrame* shadow_frame,
- JValue* ret_val,
- bool from_code,
- DeoptimizationMethodType method_type)
+extern void EnterInterpreterFromDeoptimize(Thread* self, ShadowFrame* shadow_frame, bool from_code,
+ JValue* ret_val)
REQUIRES_SHARED(Locks::mutator_lock_);
extern JValue EnterInterpreterFromEntryPoint(Thread* self, const DexFile::CodeItem* code_item,
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 8c825f3f97..ae461fd987 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -431,6 +431,9 @@ uint32_t FindNextInstructionFollowingException(
uint32_t found_dex_pc = shadow_frame.GetMethod()->FindCatchBlock(
hs.NewHandle(exception->GetClass()), dex_pc, &clear_exception);
if (found_dex_pc == DexFile::kDexNoIndex && instrumentation != nullptr) {
+ if (shadow_frame.NeedsNotifyPop()) {
+ instrumentation->WatchedFramePopped(self, shadow_frame);
+ }
// Exception is not caught by the current method. We will unwind to the
// caller. Notify any instrumentation listener.
instrumentation->MethodUnwindEvent(self, shadow_frame.GetThisObject(),
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 0c5a45faf0..f352960204 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -150,6 +150,37 @@ NO_INLINE static bool DoDexPcMoveEvent(Thread* self,
}
}
+static bool NeedsMethodExitEvent(const instrumentation::Instrumentation* ins)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return ins->HasMethodExitListeners() || ins->HasWatchedFramePopListeners();
+}
+
+// Sends the normal method exit event. Returns true if the events succeeded and false if there is a
+// pending exception.
+NO_INLINE static bool SendMethodExitEvents(Thread* self,
+ const instrumentation::Instrumentation* instrumentation,
+ const ShadowFrame& frame,
+ ObjPtr<mirror::Object> thiz,
+ ArtMethod* method,
+ uint32_t dex_pc,
+ const JValue& result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ bool had_event = false;
+ if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
+ had_event = true;
+ instrumentation->MethodExitEvent(self, thiz.Ptr(), method, dex_pc, result);
+ }
+ if (UNLIKELY(frame.NeedsNotifyPop() && instrumentation->HasWatchedFramePopListeners())) {
+ had_event = true;
+ instrumentation->WatchedFramePopped(self, frame);
+ }
+ if (UNLIKELY(had_event)) {
+ return !self->IsExceptionPending();
+ } else {
+ return true;
+ }
+}
+
template<bool do_access_check, bool transaction_active>
JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame, JValue result_register,
@@ -262,14 +293,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
JValue result;
self->AllowThreadSuspension();
HANDLE_MONITOR_CHECKS();
- if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
- instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), inst->GetDexPc(insns),
- result);
- if (UNLIKELY(self->IsExceptionPending())) {
- // Don't send another method exit event.
- HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
- }
+ if (UNLIKELY(NeedsMethodExitEvent(instrumentation) &&
+ !SendMethodExitEvents(self,
+ instrumentation,
+ shadow_frame,
+ shadow_frame.GetThisObject(code_item->ins_size_),
+ shadow_frame.GetMethod(),
+ inst->GetDexPc(insns),
+ result))) {
+ HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
}
if (interpret_one_instruction) {
/* Signal mterp to return to caller */
@@ -283,14 +315,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
JValue result;
self->AllowThreadSuspension();
HANDLE_MONITOR_CHECKS();
- if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
- instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), inst->GetDexPc(insns),
- result);
- if (UNLIKELY(self->IsExceptionPending())) {
- // Don't send another method exit event.
- HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
- }
+ if (UNLIKELY(NeedsMethodExitEvent(instrumentation) &&
+ !SendMethodExitEvents(self,
+ instrumentation,
+ shadow_frame,
+ shadow_frame.GetThisObject(code_item->ins_size_),
+ shadow_frame.GetMethod(),
+ inst->GetDexPc(insns),
+ result))) {
+ HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
}
if (interpret_one_instruction) {
/* Signal mterp to return to caller */
@@ -305,14 +338,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
result.SetI(shadow_frame.GetVReg(inst->VRegA_11x(inst_data)));
self->AllowThreadSuspension();
HANDLE_MONITOR_CHECKS();
- if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
- instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), inst->GetDexPc(insns),
- result);
- if (UNLIKELY(self->IsExceptionPending())) {
- // Don't send another method exit event.
- HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
- }
+ if (UNLIKELY(NeedsMethodExitEvent(instrumentation) &&
+ !SendMethodExitEvents(self,
+ instrumentation,
+ shadow_frame,
+ shadow_frame.GetThisObject(code_item->ins_size_),
+ shadow_frame.GetMethod(),
+ inst->GetDexPc(insns),
+ result))) {
+ HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
}
if (interpret_one_instruction) {
/* Signal mterp to return to caller */
@@ -326,14 +360,15 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
result.SetJ(shadow_frame.GetVRegLong(inst->VRegA_11x(inst_data)));
self->AllowThreadSuspension();
HANDLE_MONITOR_CHECKS();
- if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
- instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), inst->GetDexPc(insns),
- result);
- if (UNLIKELY(self->IsExceptionPending())) {
- // Don't send another method exit event.
- HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
- }
+ if (UNLIKELY(NeedsMethodExitEvent(instrumentation) &&
+ !SendMethodExitEvents(self,
+ instrumentation,
+ shadow_frame,
+ shadow_frame.GetThisObject(code_item->ins_size_),
+ shadow_frame.GetMethod(),
+ inst->GetDexPc(insns),
+ result))) {
+ HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
}
if (interpret_one_instruction) {
/* Signal mterp to return to caller */
@@ -367,17 +402,18 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item,
}
}
result.SetL(obj_result);
- if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
- instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), inst->GetDexPc(insns),
- result);
- if (UNLIKELY(self->IsExceptionPending())) {
- // Don't send another method exit event.
- HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
- }
- // Re-load since it might have moved during the MethodExitEvent.
- result.SetL(shadow_frame.GetVRegReference(ref_idx));
- }
+ if (UNLIKELY(NeedsMethodExitEvent(instrumentation) &&
+ !SendMethodExitEvents(self,
+ instrumentation,
+ shadow_frame,
+ shadow_frame.GetThisObject(code_item->ins_size_),
+ shadow_frame.GetMethod(),
+ inst->GetDexPc(insns),
+ result))) {
+ HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
+ }
+ // Re-load since it might have moved during the MethodExitEvent.
+ result.SetL(shadow_frame.GetVRegReference(ref_idx));
if (interpret_one_instruction) {
/* Signal mterp to return to caller */
shadow_frame.SetDexPC(DexFile::kDexNoIndex);
diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h
index 05768cd6d3..6903af284f 100644
--- a/runtime/interpreter/shadow_frame.h
+++ b/runtime/interpreter/shadow_frame.h
@@ -361,6 +361,14 @@ class ShadowFrame {
return result_register_;
}
+ bool NeedsNotifyPop() const {
+ return needs_notify_pop_;
+ }
+
+ void SetNotifyPop(bool notify) {
+ needs_notify_pop_ = notify;
+ }
+
private:
ShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method,
uint32_t dex_pc, bool has_reference_array)
@@ -372,7 +380,8 @@ class ShadowFrame {
number_of_vregs_(num_vregs),
dex_pc_(dex_pc),
cached_hotness_countdown_(0),
- hotness_countdown_(0) {
+ hotness_countdown_(0),
+ needs_notify_pop_(0) {
// TODO(iam): Remove this parameter, it's an an artifact of portable removal
DCHECK(has_reference_array);
if (has_reference_array) {
@@ -404,6 +413,9 @@ class ShadowFrame {
uint32_t dex_pc_;
int16_t cached_hotness_countdown_;
int16_t hotness_countdown_;
+ // TODO Might be worth it to try to bit-pack this into some other field to reduce stack usage.
+ // NB alignment requires that this field takes 4 bytes. Only 1 bit is actually ever used.
+ bool needs_notify_pop_;
// This is a two-part array:
// - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index c9bfc9c991..4b0e1071d4 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -28,6 +28,7 @@
#include <cstdlib>
#include <string>
#include <vector>
+#include <iostream>
#include "android-base/file.h"
@@ -47,8 +48,10 @@
namespace art {
const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
-// Last profile version: update the multidex separator.
-const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '9', '\0' };
+// Last profile version: merge profiles directly from the file without creating
+// profile_compilation_info object. All the profile line headers are now placed together
+// before corresponding method_encodings and class_ids.
+const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '1', '0', '\0' };
static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX;
@@ -177,9 +180,36 @@ bool ProfileCompilationInfo::AddClasses(const std::set<DexCacheResolvedClasses>&
return true;
}
+bool ProfileCompilationInfo::MergeWith(const std::string& filename) {
+ std::string error;
+ int flags = O_RDONLY | O_NOFOLLOW | O_CLOEXEC;
+ ScopedFlock profile_file = LockedFile::Open(filename.c_str(), flags,
+ /*block*/false, &error);
+
+ if (profile_file.get() == nullptr) {
+ LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error;
+ return false;
+ }
+
+ int fd = profile_file->Fd();
+
+ ProfileLoadSatus status = LoadInternal(fd, &error);
+ if (status == kProfileLoadSuccess) {
+ return true;
+ }
+
+ LOG(WARNING) << "Could not load profile data from file " << filename << ": " << error;
+ return false;
+}
+
bool ProfileCompilationInfo::Load(const std::string& filename, bool clear_if_invalid) {
ScopedTrace trace(__PRETTY_FUNCTION__);
std::string error;
+
+ if (!IsEmpty()) {
+ return kProfileLoadWouldOverwiteData;
+ }
+
int flags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
// There's no need to fsync profile data right away. We get many chances
// to write it again in case something goes wrong. We can rely on a simple
@@ -288,15 +318,14 @@ static constexpr size_t kLineHeaderSize =
/**
* Serialization format:
- * magic,version,number_of_dex_files,uncompressed_size_of_zipped_data,compressed_data_size,
- * zipped[dex_location1,number_of_classes1,methods_region_size,dex_location_checksum1
- * num_method_ids,
- * method_encoding_11,method_encoding_12...,class_id1,class_id2...
- * startup/post startup bitmap,
- * dex_location2,number_of_classes2,methods_region_size,dex_location_checksum2, num_method_ids,
- * method_encoding_21,method_encoding_22...,,class_id1,class_id2...
- * startup/post startup bitmap,
- * .....]
+ * [profile_header, zipped[[profile_line_header1, profile_line_header2...],[profile_line_data1,
+ * profile_line_data2...]]]
+ * profile_header:
+ * magic,version,number_of_dex_files,uncompressed_size_of_zipped_data,compressed_data_size
+ * profile_line_header:
+ * dex_location,number_of_classes,methods_region_size,dex_location_checksum,num_method_ids
+ * profile_line_data:
+ * method_encoding_1,method_encoding_2...,class_id1,class_id2...,startup/post startup bitmap
* The method_encoding is:
* method_id,number_of_inline_caches,inline_cache1,inline_cache2...
* The inline_cache is:
@@ -343,10 +372,6 @@ bool ProfileCompilationInfo::Save(int fd) {
<< " bytes. Profile will not be written to disk.";
return false;
}
- if (required_capacity > kProfileSizeWarningThresholdInBytes) {
- LOG(WARNING) << "Profile data size exceeds "
- << std::to_string(kProfileSizeWarningThresholdInBytes);
- }
AddUintToBuffer(&buffer, required_capacity);
if (!WriteBuffer(fd, buffer.data(), buffer.size())) {
return false;
@@ -358,12 +383,10 @@ bool ProfileCompilationInfo::Save(int fd) {
// Dex files must be written in the order of their profile index. This
// avoids writing the index in the output file and simplifies the parsing logic.
+ // Write profile line headers.
for (const DexFileData* dex_data_ptr : info_) {
const DexFileData& dex_data = *dex_data_ptr;
- // Note that we allow dex files without any methods or classes, so that
- // inline caches can refer valid dex files.
-
if (dex_data.profile_key.size() >= kMaxDexFileKeyLength) {
LOG(WARNING) << "DexFileKey exceeds allocated limit";
return false;
@@ -381,6 +404,13 @@ bool ProfileCompilationInfo::Save(int fd) {
AddUintToBuffer(&buffer, dex_data.num_method_ids); // uint32_t
AddStringToBuffer(&buffer, dex_data.profile_key);
+ }
+
+ for (const DexFileData* dex_data_ptr : info_) {
+ const DexFileData& dex_data = *dex_data_ptr;
+
+ // Note that we allow dex files without any methods or classes, so that
+ // inline caches can refer valid dex files.
uint16_t last_method_index = 0;
for (const auto& method_it : dex_data.method_map) {
@@ -413,6 +443,11 @@ bool ProfileCompilationInfo::Save(int fd) {
required_capacity,
&output_size);
+ if (output_size > kProfileSizeWarningThresholdInBytes) {
+ LOG(WARNING) << "Profile data size exceeds "
+ << std::to_string(kProfileSizeWarningThresholdInBytes);
+ }
+
buffer.clear();
AddUintToBuffer(&buffer, output_size);
@@ -690,10 +725,12 @@ bool ProfileCompilationInfo::AddClassIndex(const std::string& dex_location,
} \
while (false)
-bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer,
- uint8_t number_of_dex_files,
- /*out*/ InlineCacheMap* inline_cache,
- /*out*/ std::string* error) {
+bool ProfileCompilationInfo::ReadInlineCache(
+ SafeBuffer& buffer,
+ uint8_t number_of_dex_files,
+ const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap,
+ /*out*/ InlineCacheMap* inline_cache,
+ /*out*/ std::string* error) {
uint16_t inline_cache_size;
READ_UINT(uint16_t, buffer, inline_cache_size, error);
for (; inline_cache_size > 0; inline_cache_size--) {
@@ -723,7 +760,8 @@ bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer,
for (; dex_classes_size > 0; dex_classes_size--) {
uint16_t type_index;
READ_UINT(uint16_t, buffer, type_index, error);
- dex_pc_data->AddClass(dex_profile_index, dex::TypeIndex(type_index));
+ dex_pc_data->AddClass(dex_profile_index_remap.Get(dex_profile_index),
+ dex::TypeIndex(type_index));
}
}
}
@@ -733,6 +771,7 @@ bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer,
bool ProfileCompilationInfo::ReadMethods(SafeBuffer& buffer,
uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
+ const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap,
/*out*/std::string* error) {
uint32_t unread_bytes_before_operation = buffer.CountUnreadBytes();
if (unread_bytes_before_operation < line_header.method_region_size_bytes) {
@@ -751,7 +790,11 @@ bool ProfileCompilationInfo::ReadMethods(SafeBuffer& buffer,
uint16_t method_index = last_method_index + diff_with_last_method_index;
last_method_index = method_index;
InlineCacheMap* inline_cache = data->FindOrAddMethod(method_index);
- if (!ReadInlineCache(buffer, number_of_dex_files, inline_cache, error)) {
+ if (!ReadInlineCache(buffer,
+ number_of_dex_files,
+ dex_profile_index_remap,
+ inline_cache,
+ error)) {
return false;
}
}
@@ -954,6 +997,8 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine
SafeBuffer& buffer,
uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
+ const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap,
+ bool merge_classes,
/*out*/std::string* error) {
DexFileData* data = GetOrAddDexFileData(line_header.dex_location,
line_header.checksum,
@@ -964,12 +1009,14 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine
return kProfileLoadBadData;
}
- if (!ReadMethods(buffer, number_of_dex_files, line_header, error)) {
+ if (!ReadMethods(buffer, number_of_dex_files, line_header, dex_profile_index_remap, error)) {
return kProfileLoadBadData;
}
- if (!ReadClasses(buffer, line_header, error)) {
- return kProfileLoadBadData;
+ if (merge_classes) {
+ if (!ReadClasses(buffer, line_header, error)) {
+ return kProfileLoadBadData;
+ }
}
const size_t bytes = data->bitmap_storage.size();
@@ -986,9 +1033,10 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine
// TODO(calin): Fix this API. ProfileCompilationInfo::Load should be static and
// return a unique pointer to a ProfileCompilationInfo upon success.
-bool ProfileCompilationInfo::Load(int fd) {
+bool ProfileCompilationInfo::Load(int fd, bool merge_classes) {
std::string error;
- ProfileLoadSatus status = LoadInternal(fd, &error);
+
+ ProfileLoadSatus status = LoadInternal(fd, &error, merge_classes);
if (status == kProfileLoadSuccess) {
return true;
@@ -1000,14 +1048,10 @@ bool ProfileCompilationInfo::Load(int fd) {
// TODO(calin): fail fast if the dex checksums don't match.
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal(
- int fd, std::string* error) {
+ int fd, std::string* error, bool merge_classes) {
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
- if (!IsEmpty()) {
- return kProfileLoadWouldOverwiteData;
- }
-
struct stat stat_buffer;
if (fstat(fd, &stat_buffer) != 0) {
return kProfileLoadIOError;
@@ -1071,6 +1115,8 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal(
return kProfileLoadBadData;
}
+ std::vector<ProfileLineHeader> profile_line_headers;
+ // Read profile line headers.
for (uint8_t k = 0; k < number_of_dex_files; k++) {
ProfileLineHeader line_header;
@@ -1079,9 +1125,22 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal(
if (status != kProfileLoadSuccess) {
return status;
}
+ profile_line_headers.push_back(line_header);
+ }
+ SafeMap<uint8_t, uint8_t> dex_profile_index_remap;
+ if (!RemapProfileIndex(profile_line_headers, &dex_profile_index_remap)) {
+ return kProfileLoadBadData;
+ }
+
+ for (uint8_t k = 0; k < number_of_dex_files; k++) {
// Now read the actual profile line.
- status = ReadProfileLine(uncompressed_data, number_of_dex_files, line_header, error);
+ status = ReadProfileLine(uncompressed_data,
+ number_of_dex_files,
+ profile_line_headers[k],
+ dex_profile_index_remap,
+ merge_classes,
+ error);
if (status != kProfileLoadSuccess) {
return status;
}
@@ -1096,6 +1155,37 @@ ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal(
}
}
+bool ProfileCompilationInfo::RemapProfileIndex(
+ const std::vector<ProfileLineHeader>& profile_line_headers,
+ /*out*/SafeMap<uint8_t, uint8_t>* dex_profile_index_remap) {
+ // First verify that all checksums match. This will avoid adding garbage to
+ // the current profile info.
+ // Note that the number of elements should be very small, so this should not
+ // be a performance issue.
+ for (const ProfileLineHeader other_profile_line_header : profile_line_headers) {
+ // verify_checksum is false because we want to differentiate between a missing dex data and
+ // a mismatched checksum.
+ const DexFileData* dex_data = FindDexData(other_profile_line_header.dex_location,
+ 0u,
+ false /* verify_checksum */);
+ if ((dex_data != nullptr) && (dex_data->checksum != other_profile_line_header.checksum)) {
+ LOG(WARNING) << "Checksum mismatch for dex " << other_profile_line_header.dex_location;
+ return false;
+ }
+ }
+ // All checksums match. Import the data.
+ uint32_t num_dex_files = static_cast<uint32_t>(profile_line_headers.size());
+ for (uint32_t i = 0; i < num_dex_files; i++) {
+ const DexFileData* dex_data = GetOrAddDexFileData(profile_line_headers[i].dex_location,
+ profile_line_headers[i].checksum,
+ profile_line_headers[i].num_method_ids);
+ if (dex_data == nullptr) {
+ return false; // Could happen if we exceed the number of allowed dex files.
+ }
+ dex_profile_index_remap->Put(i, dex_data->profile_index);
+ }
+ return true;
+}
std::unique_ptr<uint8_t[]> ProfileCompilationInfo::DeflateBuffer(const uint8_t* in_buffer,
uint32_t in_size,
uint32_t* compressed_data_size) {
@@ -1488,16 +1578,16 @@ std::set<DexCacheResolvedClasses> ProfileCompilationInfo::GetResolvedClasses(
// Naive implementation to generate a random profile file suitable for testing.
bool ProfileCompilationInfo::GenerateTestProfile(int fd,
uint16_t number_of_dex_files,
- uint16_t method_ratio,
- uint16_t class_ratio,
+ uint16_t method_percentage,
+ uint16_t class_percentage,
uint32_t random_seed) {
const std::string base_dex_location = "base.apk";
ProfileCompilationInfo info;
// The limits are defined by the dex specification.
const uint16_t max_method = std::numeric_limits<uint16_t>::max();
const uint16_t max_classes = std::numeric_limits<uint16_t>::max();
- uint16_t number_of_methods = max_method * method_ratio / 100;
- uint16_t number_of_classes = max_classes * class_ratio / 100;
+ uint16_t number_of_methods = max_method * method_percentage / 100;
+ uint16_t number_of_classes = max_classes * class_percentage / 100;
std::srand(random_seed);
@@ -1534,28 +1624,47 @@ bool ProfileCompilationInfo::GenerateTestProfile(int fd,
}
// Naive implementation to generate a random profile file suitable for testing.
+// Description of random selection:
+// * Select a random starting point S.
+// * For every index i, add (S+i) % (N - total number of methods/classes) to profile with the
+// probably of 1/(N - i - number of methods/classes needed to add in profile).
bool ProfileCompilationInfo::GenerateTestProfile(
int fd,
std::vector<std::unique_ptr<const DexFile>>& dex_files,
+ uint16_t method_percentage,
+ uint16_t class_percentage,
uint32_t random_seed) {
std::srand(random_seed);
ProfileCompilationInfo info;
for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
const std::string& location = dex_file->GetLocation();
uint32_t checksum = dex_file->GetLocationChecksum();
- for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) {
- // Randomly add a class from the dex file (with 50% chance).
- if (std::rand() % 2 != 0) {
+
+ uint32_t number_of_classes = dex_file->NumClassDefs();
+ uint32_t classes_required_in_profile = (number_of_classes * class_percentage) / 100;
+ uint32_t class_start_index = rand() % number_of_classes;
+ for (uint32_t i = 0; i < number_of_classes && classes_required_in_profile; ++i) {
+ if (number_of_classes - i == classes_required_in_profile ||
+ std::rand() % (number_of_classes - i - classes_required_in_profile) == 0) {
+ uint32_t class_index = (i + class_start_index) % number_of_classes;
info.AddClassIndex(location,
checksum,
- dex_file->GetClassDef(i).class_idx_,
+ dex_file->GetClassDef(class_index).class_idx_,
dex_file->NumMethodIds());
+ classes_required_in_profile--;
}
}
- for (uint32_t i = 0; i < dex_file->NumMethodIds(); ++i) {
- // Randomly add a method from the dex file (with 50% chance).
- if (std::rand() % 2 != 0) {
- info.AddMethodIndex(MethodHotness::kFlagHot, MethodReference(dex_file.get(), i));
+
+ uint32_t number_of_methods = dex_file->NumMethodIds();
+ uint32_t methods_required_in_profile = (number_of_methods * method_percentage) / 100;
+ uint32_t method_start_index = rand() % number_of_methods;
+ for (uint32_t i = 0; i < number_of_methods && methods_required_in_profile; ++i) {
+ if (number_of_methods - i == methods_required_in_profile ||
+ std::rand() % (number_of_methods - i - methods_required_in_profile) == 0) {
+ uint32_t method_index = (method_start_index + i) % number_of_methods;
+ info.AddMethodIndex(MethodHotness::kFlagHot, MethodReference(dex_file.get(),
+ method_index));
+ methods_required_in_profile--;
}
}
}
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index ffb67ae2be..7fd7a2d049 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -288,9 +288,10 @@ class ProfileCompilationInfo {
// Add hotness flags for a simple method.
bool AddMethodHotness(const MethodReference& method_ref, const MethodHotness& hotness);
- // Load profile information from the given file descriptor.
+ // Load or Merge profile information from the given file descriptor.
// If the current profile is non-empty the load will fail.
- bool Load(int fd);
+ // If merge_classes is set to false, classes will not be merged/loaded.
+ bool Load(int fd, bool merge_classes = true);
// Load profile information from the given file
// If the current profile is non-empty the load will fail.
@@ -303,6 +304,9 @@ class ProfileCompilationInfo {
// we don't want all of the classes to be image classes.
bool MergeWith(const ProfileCompilationInfo& info, bool merge_classes = true);
+ // Merge profile information from the given file descriptor.
+ bool MergeWith(const std::string& filename);
+
// Save the profile data to the given file descriptor.
bool Save(int fd);
@@ -372,6 +376,8 @@ class ProfileCompilationInfo {
// the provided list of dex files.
static bool GenerateTestProfile(int fd,
std::vector<std::unique_ptr<const DexFile>>& dex_files,
+ uint16_t method_percentage,
+ uint16_t class_percentage,
uint32_t random_seed);
// Check that the given profile method info contain the same data.
@@ -594,7 +600,7 @@ class ProfileCompilationInfo {
};
// Entry point for profile loding functionality.
- ProfileLoadSatus LoadInternal(int fd, std::string* error);
+ ProfileLoadSatus LoadInternal(int fd, std::string* error, bool merge_classes = true);
// Read the profile header from the given fd and store the number of profile
// lines into number_of_dex_files.
@@ -619,6 +625,8 @@ class ProfileCompilationInfo {
ProfileLoadSatus ReadProfileLine(SafeBuffer& buffer,
uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
+ const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap,
+ bool merge_classes,
/*out*/std::string* error);
// Read all the classes from the buffer into the profile `info_` structure.
@@ -630,11 +638,18 @@ class ProfileCompilationInfo {
bool ReadMethods(SafeBuffer& buffer,
uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
+ const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap,
/*out*/std::string* error);
+ // The method generates mapping of profile indices while merging a new profile
+ // data into current data. It returns true, if the mapping was successful.
+ bool RemapProfileIndex(const std::vector<ProfileLineHeader>& profile_line_headers,
+ /*out*/SafeMap<uint8_t, uint8_t>* dex_profile_index_remap);
+
// Read the inline cache encoding from line_bufer into inline_cache.
bool ReadInlineCache(SafeBuffer& buffer,
uint8_t number_of_dex_files,
+ const SafeMap<uint8_t, uint8_t>& dex_profile_index_remap,
/*out*/InlineCacheMap* inline_cache,
/*out*/std::string* error);
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index 6010bce2e5..40d303fc13 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -387,6 +387,23 @@ TEST_F(ProfileCompilationInfoTest, MergeFail) {
ASSERT_FALSE(info1.MergeWith(info2));
}
+
+TEST_F(ProfileCompilationInfoTest, MergeFdFail) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo info1;
+ ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1));
+ // Use the same file, change the checksum.
+ ProfileCompilationInfo info2;
+ ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2));
+
+ ASSERT_TRUE(info1.Save(profile.GetFd()));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+
+ ASSERT_FALSE(info2.Load(profile.GetFd()));
+}
+
TEST_F(ProfileCompilationInfoTest, SaveMaxMethods) {
ScratchFile profile;
@@ -833,29 +850,6 @@ TEST_F(ProfileCompilationInfoTest, MissingTypesInlineCachesMerge) {
ASSERT_TRUE(info_no_inline_cache.Save(GetFd(profile)));
}
-TEST_F(ProfileCompilationInfoTest, LoadShouldClearExistingDataFromProfiles) {
- ScratchFile profile;
-
- ProfileCompilationInfo saved_info;
- // Save a few methods.
- for (uint16_t i = 0; i < 10; i++) {
- ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
- }
- ASSERT_TRUE(saved_info.Save(GetFd(profile)));
- ASSERT_EQ(0, profile.GetFile()->Flush());
- ASSERT_TRUE(profile.GetFile()->ResetOffset());
-
- // Add a bunch of methods to test_info.
- ProfileCompilationInfo test_info;
- for (uint16_t i = 0; i < 10; i++) {
- ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &test_info));
- }
-
- // Attempt to load the saved profile into test_info.
- // This should fail since the test_info already contains data and the load would overwrite it.
- ASSERT_FALSE(test_info.Load(GetFd(profile)));
-}
-
TEST_F(ProfileCompilationInfoTest, SampledMethodsTest) {
ProfileCompilationInfo test_info;
static constexpr size_t kNumMethods = 1000;
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 2d8d6ba147..d9cfa533ca 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -75,7 +75,7 @@ static constexpr bool kUseDlopenOnHost = true;
static constexpr bool kPrintDlOpenErrorMessage = false;
// If true, we advise the kernel about dex file mem map accesses.
-static constexpr bool kMadviseDexFileAccesses = false;
+static constexpr bool kMadviseDexFileAccesses = true;
// Note for OatFileBase and descendents:
//
@@ -1498,11 +1498,13 @@ const DexFile::ClassDef* OatFile::OatDexFile::FindClassDef(const DexFile& dex_fi
// Madvise the dex file based on the state we are moving to.
void OatDexFile::MadviseDexFile(const DexFile& dex_file, MadviseState state) {
- if (!kMadviseDexFileAccesses) {
+ const bool low_ram = Runtime::Current()->GetHeap()->IsLowMemoryMode();
+ // TODO: Also do madvise hints for non low ram devices.
+ if (!kMadviseDexFileAccesses || !low_ram) {
return;
}
if (state == MadviseState::kMadviseStateAtLoad) {
- if (Runtime::Current()->GetHeap()->IsLowMemoryMode()) {
+ if (low_ram) {
// Default every dex file to MADV_RANDOM when its loaded by default for low ram devices.
// Other devices have enough page cache to get performance benefits from loading more pages
// into the page cache.
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 57b3a75352..3f23926b29 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -166,13 +166,11 @@ class DeoptimizationContextRecord {
bool is_reference,
bool from_code,
ObjPtr<mirror::Throwable> pending_exception,
- DeoptimizationMethodType method_type,
DeoptimizationContextRecord* link)
: ret_val_(ret_val),
is_reference_(is_reference),
from_code_(from_code),
pending_exception_(pending_exception.Ptr()),
- deopt_method_type_(method_type),
link_(link) {}
JValue GetReturnValue() const { return ret_val_; }
@@ -187,9 +185,6 @@ class DeoptimizationContextRecord {
mirror::Object** GetPendingExceptionAsGCRoot() {
return reinterpret_cast<mirror::Object**>(&pending_exception_);
}
- DeoptimizationMethodType GetDeoptimizationMethodType() const {
- return deopt_method_type_;
- }
private:
// The value returned by the method at the top of the stack before deoptimization.
@@ -205,9 +200,6 @@ class DeoptimizationContextRecord {
// exception).
mirror::Throwable* pending_exception_;
- // Whether the context was created for an (idempotent) runtime method.
- const DeoptimizationMethodType deopt_method_type_;
-
// A link to the previous DeoptimizationContextRecord.
DeoptimizationContextRecord* const link_;
@@ -237,30 +229,26 @@ class StackedShadowFrameRecord {
void Thread::PushDeoptimizationContext(const JValue& return_value,
bool is_reference,
- ObjPtr<mirror::Throwable> exception,
bool from_code,
- DeoptimizationMethodType method_type) {
+ ObjPtr<mirror::Throwable> exception) {
DeoptimizationContextRecord* record = new DeoptimizationContextRecord(
return_value,
is_reference,
from_code,
exception,
- method_type,
tlsPtr_.deoptimization_context_stack);
tlsPtr_.deoptimization_context_stack = record;
}
void Thread::PopDeoptimizationContext(JValue* result,
ObjPtr<mirror::Throwable>* exception,
- bool* from_code,
- DeoptimizationMethodType* method_type) {
+ bool* from_code) {
AssertHasDeoptimizationContext();
DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
tlsPtr_.deoptimization_context_stack = record->GetLink();
result->SetJ(record->GetReturnValue().GetJ());
*exception = record->GetPendingException();
*from_code = record->GetFromCode();
- *method_type = record->GetDeoptimizationMethodType();
delete record;
}
@@ -3096,16 +3084,10 @@ void Thread::QuickDeliverException() {
NthCallerVisitor visitor(this, 0, false);
visitor.WalkStack();
if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
- // method_type shouldn't matter due to exception handling.
- const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
// Save the exception into the deoptimization context so it can be restored
// before entering the interpreter.
PushDeoptimizationContext(
- JValue(),
- false /* is_reference */,
- exception,
- false /* from_code */,
- method_type);
+ JValue(), /*is_reference */ false, /* from_code */ false, exception);
artDeoptimize(this);
UNREACHABLE();
} else {
@@ -3665,8 +3647,7 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) {
PopStackedShadowFrame(StackedShadowFrameType::kDeoptimizationShadowFrame);
ObjPtr<mirror::Throwable> pending_exception;
bool from_code = false;
- DeoptimizationMethodType method_type;
- PopDeoptimizationContext(result, &pending_exception, &from_code, &method_type);
+ PopDeoptimizationContext(result, &pending_exception, &from_code);
SetTopOfStack(nullptr);
SetTopOfShadowStack(shadow_frame);
@@ -3675,11 +3656,7 @@ void Thread::DeoptimizeWithDeoptimizationException(JValue* result) {
if (pending_exception != nullptr) {
SetException(pending_exception);
}
- interpreter::EnterInterpreterFromDeoptimize(this,
- shadow_frame,
- result,
- from_code,
- method_type);
+ interpreter::EnterInterpreterFromDeoptimize(this, shadow_frame, from_code, result);
}
void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) {
diff --git a/runtime/thread.h b/runtime/thread.h
index ad4506e309..7540fd2563 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -117,13 +117,6 @@ enum class StackedShadowFrameType {
kDeoptimizationShadowFrame,
};
-// The type of method that triggers deoptimization. It contains info on whether
-// the deoptimized method should advance dex_pc.
-enum class DeoptimizationMethodType {
- kKeepDexPc, // dex pc is required to be kept upon deoptimization.
- kDefault // dex pc may or may not advance depending on other conditions.
-};
-
// This should match RosAlloc::kNumThreadLocalSizeBrackets.
static constexpr size_t kNumRosAllocThreadLocalSizeBracketsInThread = 16;
@@ -967,18 +960,14 @@ class Thread {
// values on stacks.
// 'from_code' denotes whether the deoptimization was explicitly made from
// compiled code.
- // 'method_type' contains info on whether deoptimization should advance
- // dex_pc.
void PushDeoptimizationContext(const JValue& return_value,
bool is_reference,
- ObjPtr<mirror::Throwable> exception,
bool from_code,
- DeoptimizationMethodType method_type)
+ ObjPtr<mirror::Throwable> exception)
REQUIRES_SHARED(Locks::mutator_lock_);
void PopDeoptimizationContext(JValue* result,
ObjPtr<mirror::Throwable>* exception,
- bool* from_code,
- DeoptimizationMethodType* method_type)
+ bool* from_code)
REQUIRES_SHARED(Locks::mutator_lock_);
void AssertHasDeoptimizationContext()
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/verifier/method_verifier-inl.h b/runtime/verifier/method_verifier-inl.h
index 6c832e3492..9bb875c033 100644
--- a/runtime/verifier/method_verifier-inl.h
+++ b/runtime/verifier/method_verifier-inl.h
@@ -77,7 +77,7 @@ inline bool MethodVerifier::HasFailures() const {
inline const RegType& MethodVerifier::ResolveCheckedClass(dex::TypeIndex class_idx) {
DCHECK(!HasFailures());
- const RegType& result = ResolveClassAndCheckAccess(class_idx);
+ const RegType& result = ResolveClass<CheckAccess::kYes>(class_idx);
DCHECK(!HasFailures());
return result;
}
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 312d8dfc18..9a876077d4 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -1765,7 +1765,9 @@ bool MethodVerifier::SetTypesFromSignature() {
// it's effectively considered initialized the instant we reach here (in the sense that we
// can return without doing anything or call virtual methods).
{
- const RegType& reg_type = ResolveClassAndCheckAccess(iterator.GetTypeIdx());
+ // Note: don't check access. No error would be thrown for declaring or passing an
+ // inaccessible class. Only actual accesses to fields or methods will.
+ const RegType& reg_type = ResolveClass<CheckAccess::kNo>(iterator.GetTypeIdx());
if (!reg_type.IsNonZeroReferenceTypes()) {
DCHECK(HasFailures());
return false;
@@ -2322,7 +2324,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
case Instruction::CONST_CLASS: {
// Get type from instruction if unresolved then we need an access check
// TODO: check Compiler::CanAccessTypeWithoutChecks returns false when res_type is unresolved
- const RegType& res_type = ResolveClassAndCheckAccess(dex::TypeIndex(inst->VRegB_21c()));
+ const RegType& res_type = ResolveClass<CheckAccess::kYes>(dex::TypeIndex(inst->VRegB_21c()));
// Register holds class, ie its type is class, on error it will hold Conflict.
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_21c(), res_type.IsConflict() ? res_type
@@ -2393,7 +2395,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
*/
const bool is_checkcast = (inst->Opcode() == Instruction::CHECK_CAST);
const dex::TypeIndex type_idx((is_checkcast) ? inst->VRegB_21c() : inst->VRegC_22c());
- const RegType& res_type = ResolveClassAndCheckAccess(type_idx);
+ const RegType& res_type = ResolveClass<CheckAccess::kYes>(type_idx);
if (res_type.IsConflict()) {
// If this is a primitive type, fail HARD.
ObjPtr<mirror::Class> klass =
@@ -2463,7 +2465,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
break;
}
case Instruction::NEW_INSTANCE: {
- const RegType& res_type = ResolveClassAndCheckAccess(dex::TypeIndex(inst->VRegB_21c()));
+ const RegType& res_type = ResolveClass<CheckAccess::kYes>(dex::TypeIndex(inst->VRegB_21c()));
if (res_type.IsConflict()) {
DCHECK_NE(failures_.size(), 0U);
break; // bad class
@@ -2675,7 +2677,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
// ensure that subsequent merges don't lose type information - such as becoming an
// interface from a class that would lose information relevant to field checks.
const RegType& orig_type = work_line_->GetRegisterType(this, instance_of_inst->VRegB_22c());
- const RegType& cast_type = ResolveClassAndCheckAccess(
+ const RegType& cast_type = ResolveClass<CheckAccess::kYes>(
dex::TypeIndex(instance_of_inst->VRegC_22c()));
if (!orig_type.Equals(cast_type) &&
@@ -3723,7 +3725,8 @@ inline bool MethodVerifier::IsInstantiableOrPrimitive(mirror::Class* klass) {
return klass->IsInstantiable() || klass->IsPrimitive();
}
-const RegType& MethodVerifier::ResolveClassAndCheckAccess(dex::TypeIndex class_idx) {
+template <MethodVerifier::CheckAccess C>
+const RegType& MethodVerifier::ResolveClass(dex::TypeIndex class_idx) {
mirror::Class* klass = can_load_classes_
? Runtime::Current()->GetClassLinker()->ResolveType(
*dex_file_, class_idx, dex_cache_, class_loader_)
@@ -3760,13 +3763,15 @@ const RegType& MethodVerifier::ResolveClassAndCheckAccess(dex::TypeIndex class_i
// Record result of class resolution attempt.
VerifierDeps::MaybeRecordClassResolution(*dex_file_, class_idx, klass);
- // Check if access is allowed. Unresolved types use xxxWithAccessCheck to
- // check at runtime if access is allowed and so pass here. If result is
- // primitive, skip the access check.
- if (result->IsNonZeroReferenceTypes() && !result->IsUnresolvedTypes()) {
+ // If requested, check if access is allowed. Unresolved types are included in this check, as the
+ // interpreter only tests whether access is allowed when a class is not pre-verified and runs in
+ // the access-checks interpreter. If result is primitive, skip the access check.
+ //
+ // Note: we do this for unresolved classes to trigger re-verification at runtime.
+ if (C == CheckAccess::kYes && result->IsNonZeroReferenceTypes()) {
const RegType& referrer = GetDeclaringClass();
- if (!referrer.IsUnresolvedTypes() && !referrer.CanAccess(*result)) {
- Fail(VERIFY_ERROR_ACCESS_CLASS) << "illegal class access: '"
+ if (!referrer.CanAccess(*result)) {
+ Fail(VERIFY_ERROR_ACCESS_CLASS) << "(possibly) illegal class access: '"
<< referrer << "' -> '" << *result << "'";
}
}
@@ -3785,7 +3790,8 @@ const RegType& MethodVerifier::GetCaughtExceptionType() {
if (!iterator.GetHandlerTypeIndex().IsValid()) {
common_super = &reg_types_.JavaLangThrowable(false);
} else {
- const RegType& exception = ResolveClassAndCheckAccess(iterator.GetHandlerTypeIndex());
+ const RegType& exception =
+ ResolveClass<CheckAccess::kYes>(iterator.GetHandlerTypeIndex());
if (!reg_types_.JavaLangThrowable(false).IsAssignableFrom(exception, this)) {
DCHECK(!exception.IsUninitializedTypes()); // Comes from dex, shouldn't be uninit.
if (exception.IsUnresolvedTypes()) {
@@ -3827,7 +3833,7 @@ const RegType& MethodVerifier::GetCaughtExceptionType() {
ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess(
uint32_t dex_method_idx, MethodType method_type) {
const DexFile::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx);
- const RegType& klass_type = ResolveClassAndCheckAccess(method_id.class_idx_);
+ const RegType& klass_type = ResolveClass<CheckAccess::kYes>(method_id.class_idx_);
if (klass_type.IsConflict()) {
std::string append(" in attempt to access method ");
append += dex_file_->GetMethodName(method_id);
@@ -4598,7 +4604,7 @@ void MethodVerifier::VerifyNewArray(const Instruction* inst, bool is_filled, boo
DCHECK_EQ(inst->Opcode(), Instruction::FILLED_NEW_ARRAY_RANGE);
type_idx = dex::TypeIndex(inst->VRegB_3rc());
}
- const RegType& res_type = ResolveClassAndCheckAccess(type_idx);
+ const RegType& res_type = ResolveClass<CheckAccess::kYes>(type_idx);
if (res_type.IsConflict()) { // bad class
DCHECK_NE(failures_.size(), 0U);
} else {
@@ -4812,7 +4818,7 @@ void MethodVerifier::VerifyAPut(const Instruction* inst,
ArtField* MethodVerifier::GetStaticField(int field_idx) {
const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx);
// Check access to class
- const RegType& klass_type = ResolveClassAndCheckAccess(field_id.class_idx_);
+ const RegType& klass_type = ResolveClass<CheckAccess::kYes>(field_id.class_idx_);
if (klass_type.IsConflict()) { // bad class
AppendToLastFailMessage(StringPrintf(" in attempt to access static field %d (%s) in %s",
field_idx, dex_file_->GetFieldName(field_id),
@@ -4820,6 +4826,9 @@ ArtField* MethodVerifier::GetStaticField(int field_idx) {
return nullptr;
}
if (klass_type.IsUnresolvedTypes()) {
+ // Accessibility checks depend on resolved fields.
+ DCHECK(klass_type.Equals(GetDeclaringClass()) || !failures_.empty());
+
return nullptr; // Can't resolve Class so no more to do here, will do checking at runtime.
}
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -4850,7 +4859,7 @@ ArtField* MethodVerifier::GetStaticField(int field_idx) {
ArtField* MethodVerifier::GetInstanceField(const RegType& obj_type, int field_idx) {
const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx);
// Check access to class.
- const RegType& klass_type = ResolveClassAndCheckAccess(field_id.class_idx_);
+ const RegType& klass_type = ResolveClass<CheckAccess::kYes>(field_id.class_idx_);
if (klass_type.IsConflict()) {
AppendToLastFailMessage(StringPrintf(" in attempt to access instance field %d (%s) in %s",
field_idx, dex_file_->GetFieldName(field_id),
@@ -4858,6 +4867,9 @@ ArtField* MethodVerifier::GetInstanceField(const RegType& obj_type, int field_id
return nullptr;
}
if (klass_type.IsUnresolvedTypes()) {
+ // Accessibility checks depend on resolved fields.
+ DCHECK(klass_type.Equals(GetDeclaringClass()) || !failures_.empty());
+
return nullptr; // Can't resolve Class so no more to do here
}
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -4994,6 +5006,31 @@ void MethodVerifier::VerifyISFieldAccess(const Instruction* inst, const RegType&
DCHECK(!can_load_classes_ || self_->IsExceptionPending());
self_->ClearException();
}
+ } else {
+ // If we don't have the field (it seems we failed resolution) and this is a PUT, we need to
+ // redo verification at runtime as the field may be final, unless the field id shows it's in
+ // the same class.
+ //
+ // For simplicity, it is OK to not distinguish compile-time vs runtime, and post this an
+ // ACCESS_FIELD failure at runtime. This has the same effect as NO_FIELD - punting the class
+ // to the access-checks interpreter.
+ //
+ // Note: see b/34966607. This and above may be changed in the future.
+ if (kAccType == FieldAccessType::kAccPut) {
+ const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx);
+ const char* field_class_descriptor = dex_file_->GetFieldDeclaringClassDescriptor(field_id);
+ const RegType* field_class_type = &reg_types_.FromDescriptor(GetClassLoader(),
+ field_class_descriptor,
+ false);
+ if (!field_class_type->Equals(GetDeclaringClass())) {
+ Fail(VERIFY_ERROR_ACCESS_FIELD) << "could not check field put for final field modify of "
+ << field_class_descriptor
+ << "."
+ << dex_file_->GetFieldName(field_id)
+ << " from other class "
+ << GetDeclaringClass();
+ }
+ }
}
if (field_type == nullptr) {
const DexFile::FieldId& field_id = dex_file_->GetFieldId(field_idx);
diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h
index ea8729cb3e..da4102a786 100644
--- a/runtime/verifier/method_verifier.h
+++ b/runtime/verifier/method_verifier.h
@@ -576,9 +576,14 @@ class MethodVerifier {
void VerifyQuickFieldAccess(const Instruction* inst, const RegType& insn_type, bool is_primitive)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Resolves a class based on an index and performs access checks to ensure the referrer can
- // access the resolved class.
- const RegType& ResolveClassAndCheckAccess(dex::TypeIndex class_idx)
+ enum class CheckAccess { // private.
+ kYes,
+ kNo,
+ };
+ // Resolves a class based on an index and, if C is kYes, performs access checks to ensure
+ // the referrer can access the resolved class.
+ template <CheckAccess C>
+ const RegType& ResolveClass(dex::TypeIndex class_idx)
REQUIRES_SHARED(Locks::mutator_lock_);
/*
diff --git a/test/004-JniTest/build b/test/004-JniTest/build
index c8440fcb73..e563d734c2 100755
--- a/test/004-JniTest/build
+++ b/test/004-JniTest/build
@@ -38,4 +38,30 @@ function javac_wrapper {
export -f javac_wrapper
export JAVAC=javac_wrapper
+######################################################################
+
+# Use the original dx with no extra magic or pessimizing flags.
+# This ensures that any default optimizations that dx do would not break JNI.
+
+export ORIGINAL_DX="$DX"
+
+# Filter out --debug flag from dx.
+function dx_wrapper {
+ local args=("$@")
+ local args_filtered=()
+ for i in "${args[@]}"; do
+ case "$i" in
+ --debug)
+ ;;
+ *)
+ args_filtered+=("$i")
+ ;;
+ esac
+ done
+ "$ORIGINAL_DX" "${args_filtered[@]}"
+}
+
+export -f dx_wrapper
+export DX=dx_wrapper
+
./default-build "$@"
diff --git a/test/004-JniTest/expected.txt b/test/004-JniTest/expected.txt
index f7e404d30b..7e85ab1041 100644
--- a/test/004-JniTest/expected.txt
+++ b/test/004-JniTest/expected.txt
@@ -58,3 +58,5 @@ EXCEPTION OCCURED: java.lang.IncompatibleClassChangeError: Conflicting default m
hi-lambda: λ
hi-default δλ
hi-default δλ
+Clinit Lookup: ClassWithoutClinit: <NSME Exception>
+Clinit Lookup: ClassWithClinit: Main$ClassWithClinit()(Class: class java.lang.reflect.Constructor)
diff --git a/test/004-JniTest/jni_test.cc b/test/004-JniTest/jni_test.cc
index a34d633590..bc5a0a64e8 100644
--- a/test/004-JniTest/jni_test.cc
+++ b/test/004-JniTest/jni_test.cc
@@ -776,6 +776,18 @@ static jint Java_Main_intCriticalNativeMethod(jint a, jint b, jint c) {
return a + b + c;
}
+extern "C" JNIEXPORT jobject JNICALL Java_Main_lookupClinit(JNIEnv* env, jclass, jclass kls) {
+ jmethodID clinit_id = env->GetStaticMethodID(kls, "<clinit>", "()V");
+
+ if (clinit_id != nullptr) {
+ jobject obj = env->ToReflectedMethod(kls, clinit_id, /*isStatic*/ true);
+ CHECK(obj != nullptr);
+ return obj;
+ } else {
+ return nullptr;
+ }
+}
+
extern "C" JNIEXPORT jboolean JNICALL Java_Main_isSlowDebug(JNIEnv*, jclass) {
// Return whether slow-debug is on. Only relevant for debug builds.
if (kIsDebugBuild) {
diff --git a/test/004-JniTest/src/Main.java b/test/004-JniTest/src/Main.java
index 0c4ed89f81..fe5f4e3dc6 100644
--- a/test/004-JniTest/src/Main.java
+++ b/test/004-JniTest/src/Main.java
@@ -56,6 +56,8 @@ public class Main {
registerNativesJniTest();
testFastNativeMethods();
testCriticalNativeMethods();
+
+ testClinitMethodLookup();
}
private static native boolean registerNativesJniTest();
@@ -314,6 +316,36 @@ public class Main {
}
private static native boolean isSlowDebug();
+
+ private static void testClinitMethodLookup() {
+ // Expect this to print <NSME Exception>
+ try {
+ System.out.println("Clinit Lookup: ClassWithoutClinit: " + methodString(lookupClinit(ClassWithoutClinit.class)));
+ } catch (NoSuchMethodError e) {
+ System.out.println("Clinit Lookup: ClassWithoutClinit: <NSME Exception>");
+ }
+ // Expect this to print <clinit>
+ try {
+ System.out.println("Clinit Lookup: ClassWithClinit: " + methodString(lookupClinit(ClassWithClinit.class)));
+ } catch (NoSuchMethodError e) {
+ System.out.println("Clinit Lookup: ClassWithClinit: <NSME Exception>");
+ }
+ }
+
+ private static String methodString(java.lang.reflect.Executable method) {
+ if (method == null) {
+ return "<<null>>";
+ } else {
+ return method.toString() + "(Class: " + method.getClass().toString() + ")";
+ }
+ }
+ private static native java.lang.reflect.Executable lookupClinit(Class kls);
+
+ private static class ClassWithoutClinit {
+ }
+ private static class ClassWithClinit {
+ static {}
+ }
}
@FunctionalInterface
diff --git a/test/1923-frame-pop/expected.txt b/test/1923-frame-pop/expected.txt
new file mode 100644
index 0000000000..7bc001da6c
--- /dev/null
+++ b/test/1923-frame-pop/expected.txt
@@ -0,0 +1,8 @@
+public static void art.Test1923.recurTimesA(int,java.lang.Runnable) pop. Line=44 exception:false
+Ran recurTimes(10) without errors!
+public static void art.Test1923.recurTimesF(int,java.lang.Runnable) pop. Line=84 exception:false
+Ran recurTimes(10) without errors!
+public static void art.Test1923.recurTimesK(int,java.lang.Runnable) pop. Line=124 exception:false
+Ran recurTimes(10) without errors!
+public static void art.Test1923.recurTimesF(int,java.lang.Runnable) pop. Line=83 exception:true
+Caught exception art.Test1923$RecursionError: Unable recur further. Still 90 outstanding! while running recurTimes(100)
diff --git a/test/1923-frame-pop/info.txt b/test/1923-frame-pop/info.txt
new file mode 100644
index 0000000000..b4984d9e4f
--- /dev/null
+++ b/test/1923-frame-pop/info.txt
@@ -0,0 +1,3 @@
+Tests notify frame pop JVMTI functionality.
+
+This tests the normal use case.
diff --git a/test/597-deopt-invoke-stub/run b/test/1923-frame-pop/run
index bc04498bfe..51875a7e86 100644..100755
--- a/test/597-deopt-invoke-stub/run
+++ b/test/1923-frame-pop/run
@@ -1,12 +1,12 @@
#!/bin/bash
#
-# Copyright (C) 2017 The Android Open Source Project
+# Copyright 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# 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,
@@ -14,5 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# We want to run in debuggable mode and compiled.
-exec ${RUN} --jit -Xcompiler-option --debuggable "${@}"
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1923-frame-pop/src/Main.java b/test/1923-frame-pop/src/Main.java
new file mode 100644
index 0000000000..8881483ae4
--- /dev/null
+++ b/test/1923-frame-pop/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1923.run();
+ }
+}
diff --git a/test/1923-frame-pop/src/art/Breakpoint.java b/test/1923-frame-pop/src/art/Breakpoint.java
new file mode 100644
index 0000000000..bbb89f707f
--- /dev/null
+++ b/test/1923-frame-pop/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+ public static class Manager {
+ public static class BP {
+ public final Executable method;
+ public final long location;
+
+ public BP(Executable method) {
+ this(method, getStartLocation(method));
+ }
+
+ public BP(Executable method, long location) {
+ this.method = method;
+ this.location = location;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof BP) &&
+ method.equals(((BP)other).method) &&
+ location == ((BP)other).location;
+ }
+
+ @Override
+ public String toString() {
+ return method.toString() + " @ " + getLine();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(method, location);
+ }
+
+ public int getLine() {
+ try {
+ LineNumber[] lines = getLineNumberTable(method);
+ int best = -1;
+ for (LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+ }
+
+ private Set<BP> breaks = new HashSet<>();
+
+ public void setBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.add(b)) {
+ Breakpoint.setBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void setBreakpoint(Executable method, long location) {
+ setBreakpoints(new BP(method, location));
+ }
+
+ public void clearBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.remove(b)) {
+ Breakpoint.clearBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void clearBreakpoint(Executable method, long location) {
+ clearBreakpoints(new BP(method, location));
+ }
+
+ public void clearAllBreakpoints() {
+ clearBreakpoints(breaks.toArray(new BP[0]));
+ }
+ }
+
+ public static void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ Thread thr) {
+ startBreakpointWatch(methodClass, breakpointReached, false, thr);
+ }
+
+ /**
+ * Enables the trapping of breakpoint events.
+ *
+ * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+ */
+ public static native void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ boolean allowRecursive,
+ Thread thr);
+ public static native void stopBreakpointWatch(Thread thr);
+
+ public static final class LineNumber implements Comparable<LineNumber> {
+ public final long location;
+ public final int line;
+
+ private LineNumber(long loc, int line) {
+ this.location = loc;
+ this.line = line;
+ }
+
+ public boolean equals(Object other) {
+ return other instanceof LineNumber && ((LineNumber)other).line == line &&
+ ((LineNumber)other).location == location;
+ }
+
+ public int compareTo(LineNumber other) {
+ int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+ if (v != 0) {
+ return v;
+ } else {
+ return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+ }
+ }
+ }
+
+ public static native void setBreakpoint(Executable m, long loc);
+ public static void setBreakpoint(Executable m, LineNumber l) {
+ setBreakpoint(m, l.location);
+ }
+
+ public static native void clearBreakpoint(Executable m, long loc);
+ public static void clearBreakpoint(Executable m, LineNumber l) {
+ clearBreakpoint(m, l.location);
+ }
+
+ private static native Object[] getLineNumberTableNative(Executable m);
+ public static LineNumber[] getLineNumberTable(Executable m) {
+ Object[] nativeTable = getLineNumberTableNative(m);
+ long[] location = (long[])(nativeTable[0]);
+ int[] lines = (int[])(nativeTable[1]);
+ if (lines.length != location.length) {
+ throw new Error("Lines and locations have different lengths!");
+ }
+ LineNumber[] out = new LineNumber[lines.length];
+ for (int i = 0; i < lines.length; i++) {
+ out[i] = new LineNumber(location[i], lines[i]);
+ }
+ return out;
+ }
+
+ public static native long getStartLocation(Executable m);
+
+ public static int locationToLine(Executable m, long location) {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ int best = -1;
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ public static long lineToLocation(Executable m, int line) throws Exception {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.line == line) {
+ return l.location;
+ }
+ }
+ throw new Exception("Unable to find line " + line + " in " + m);
+ } catch (Exception e) {
+ throw new Exception("Unable to get line number info for " + m, e);
+ }
+ }
+}
+
diff --git a/test/1923-frame-pop/src/art/FramePop.java b/test/1923-frame-pop/src/art/FramePop.java
new file mode 100644
index 0000000000..86bf226b31
--- /dev/null
+++ b/test/1923-frame-pop/src/art/FramePop.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+
+public class FramePop {
+ public static native void enableFramePopEvent(Class klass, Method method, Thread thr)
+ throws Exception;
+ public static native void notifyFramePop(Thread target, int depth) throws Exception;
+}
diff --git a/test/1923-frame-pop/src/art/Locals.java b/test/1923-frame-pop/src/art/Locals.java
new file mode 100644
index 0000000000..22e21be398
--- /dev/null
+++ b/test/1923-frame-pop/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+ public static native void EnableLocalVariableAccess();
+
+ public static class VariableDescription {
+ public final long start_location;
+ public final int length;
+ public final String name;
+ public final String signature;
+ public final String generic_signature;
+ public final int slot;
+
+ public VariableDescription(
+ long start, int length, String name, String sig, String gen_sig, int slot) {
+ this.start_location = start;
+ this.length = length;
+ this.name = name;
+ this.signature = sig;
+ this.generic_signature = gen_sig;
+ this.slot = slot;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "VariableDescription { " +
+ "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+ "}",
+ this.signature,
+ this.name,
+ this.generic_signature,
+ this.slot,
+ this.start_location,
+ this.length);
+ }
+ public boolean equals(Object other) {
+ if (!(other instanceof VariableDescription)) {
+ return false;
+ } else {
+ VariableDescription v = (VariableDescription)other;
+ return Objects.equals(v.signature, signature) &&
+ Objects.equals(v.name, name) &&
+ Objects.equals(v.generic_signature, generic_signature) &&
+ v.slot == slot &&
+ v.start_location == start_location &&
+ v.length == length;
+ }
+ }
+ public int hashCode() {
+ return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+ this.start_location, this.length);
+ }
+ }
+
+ public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+ public static VariableDescription GetVariableAtLine(
+ Executable e, String name, String sig, int line) throws Exception {
+ return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+ }
+
+ public static VariableDescription GetVariableAtLocation(
+ Executable e, String name, String sig, long loc) {
+ VariableDescription[] vars = GetLocalVariableTable(e);
+ for (VariableDescription var : vars) {
+ if (var.start_location <= loc &&
+ var.length + var.start_location > loc &&
+ var.name.equals(name) &&
+ var.signature.equals(sig)) {
+ return var;
+ }
+ }
+ throw new Error(
+ "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+ }
+
+ public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+ public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+ public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+ public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+ public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+ public static native Object GetLocalInstance(Thread thr, int depth);
+
+ public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+ }
+ public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+ }
+ public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+ }
+ public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+ }
+ public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+ public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+ public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+ public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+ public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1923-frame-pop/src/art/StackTrace.java b/test/1923-frame-pop/src/art/StackTrace.java
new file mode 100644
index 0000000000..2ea2f201e8
--- /dev/null
+++ b/test/1923-frame-pop/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+ public static class StackFrameData {
+ public final Thread thr;
+ public final Executable method;
+ public final long current_location;
+ public final int depth;
+
+ public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+ this.thr = thr;
+ this.method = e;
+ this.current_location = loc;
+ this.depth = depth;
+ }
+ @Override
+ public String toString() {
+ return String.format(
+ "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+ this.thr,
+ this.method,
+ this.current_location,
+ this.depth);
+ }
+ }
+
+ public static native int GetStackDepth(Thread thr);
+
+ private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+ public static StackFrameData[] GetStackTrace(Thread thr) {
+ // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+ // suspended. The spec says that not being suspended is fine but since we want this to be
+ // consistent we will suspend for the RI.
+ boolean suspend_thread =
+ !System.getProperty("java.vm.name").equals("Dalvik") &&
+ !thr.equals(Thread.currentThread()) &&
+ !Suspension.isSuspended(thr);
+ if (suspend_thread) {
+ Suspension.suspend(thr);
+ }
+ StackFrameData[] out = nativeGetStackTrace(thr);
+ if (suspend_thread) {
+ Suspension.resume(thr);
+ }
+ return out;
+ }
+}
+
diff --git a/test/1923-frame-pop/src/art/Suspension.java b/test/1923-frame-pop/src/art/Suspension.java
new file mode 100644
index 0000000000..16e62ccac9
--- /dev/null
+++ b/test/1923-frame-pop/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1923-frame-pop/src/art/Test1923.java b/test/1923-frame-pop/src/art/Test1923.java
new file mode 100644
index 0000000000..b5265b7e9d
--- /dev/null
+++ b/test/1923-frame-pop/src/art/Test1923.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.function.IntUnaryOperator;
+import java.util.function.Function;
+
+public class Test1923 {
+ public static void handleFramePop(Executable m, boolean exception, long location) {
+ System.out.println(
+ m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception);
+ }
+
+ public static void recurTimesA(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesB(times - 1, safepoint);
+ }
+
+ public static void recurTimesB(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesC(times - 1, safepoint);
+ }
+
+ public static void recurTimesC(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesD(times - 1, safepoint);
+ }
+
+ public static void recurTimesD(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesE(times - 1, safepoint);
+ }
+
+ public static void recurTimesE(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesF(times - 1, safepoint);
+ }
+
+ public static void recurTimesF(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesG(times - 1, safepoint);
+ }
+
+ public static void recurTimesG(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesH(times - 1, safepoint);
+ }
+
+ public static void recurTimesH(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesI(times - 1, safepoint);
+ }
+
+ public static void recurTimesI(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesJ(times - 1, safepoint);
+ }
+
+ public static void recurTimesJ(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesK(times - 1, safepoint);
+ }
+
+ public static class RecursionError extends Error {
+ public RecursionError(String s) { super(s); }
+ }
+ public static void recurTimesK(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ safepoint.run();
+ throw new RecursionError("Unable recur further. Still " + times + " outstanding!");
+ }
+
+ public static class ThreadPauser implements Runnable {
+ public final Semaphore sem_wakeup_main;
+ public final Semaphore sem_wait;
+
+ public ThreadPauser() {
+ sem_wakeup_main = new Semaphore(0);
+ sem_wait = new Semaphore(0);
+ }
+
+ public void run() {
+ try {
+ sem_wakeup_main.release();
+ sem_wait.acquire();
+ } catch (Exception e) {
+ throw new Error("Error with semaphores!", e);
+ }
+ }
+
+ public void waitForOtherThreadToPause() throws Exception {
+ sem_wakeup_main.acquire();
+ }
+
+ public void wakeupOtherThread() throws Exception {
+ sem_wait.release();
+ }
+ }
+
+ public static void doRecurTestWith(final int times, int watch_frame) throws Exception {
+ final String target_method_name_start = "recurTimes";
+ final ThreadPauser safepoint = new ThreadPauser();
+ Thread target = new Thread(() -> {
+ try {
+ recurTimesA(times, safepoint);
+ System.out.println("Ran recurTimes(" + times + ") without errors!");
+ } catch (RecursionError e) {
+ System.out.println("Caught exception " + e + " while running recurTimes(" + times + ")");
+ }
+ });
+ target.start();
+ safepoint.waitForOtherThreadToPause();
+ Suspension.suspend(target);
+ // Safe block
+ int cnt = 0;
+ StackTrace.StackFrameData target_frame = null;
+ for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(target)) {
+ if (frame.method.getName().startsWith(target_method_name_start)) {
+ if (times - cnt == watch_frame) {
+ target_frame = frame;
+ break;
+ } else {
+ cnt++;
+ }
+ }
+ }
+ if (target_frame != null) {
+ FramePop.notifyFramePop(target, target_frame.depth);
+ } else {
+ System.out.println(
+ "Unable to find stack frame for " + watch_frame + " depth of "
+ + target_method_name_start);
+ }
+ Suspension.resume(target);
+ safepoint.wakeupOtherThread();
+ target.join();
+ }
+
+ public static void run() throws Exception {
+ // TODO Investigate what thread argument means for FramePop event enable.
+ // Listen for events on all threads.
+ FramePop.enableFramePopEvent(
+ Test1923.class,
+ Test1923.class.getDeclaredMethod(
+ "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE),
+ null);
+ doRecurTestWith(10, 0);
+ doRecurTestWith(10, 5);
+ doRecurTestWith(10, 10);
+ doRecurTestWith(100, 95);
+ }
+}
diff --git a/test/1923-frame-pop/src/art/Trace.java b/test/1923-frame-pop/src/art/Trace.java
new file mode 100644
index 0000000000..ba3d397b0b
--- /dev/null
+++ b/test/1923-frame-pop/src/art/Trace.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class Trace {
+ public static native void enableTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Method fieldAccess,
+ Method fieldModify,
+ Method singleStep,
+ Thread thr);
+ public static native void disableTracing(Thread thr);
+
+ public static void enableFieldTracing(Class<?> methodClass,
+ Method fieldAccess,
+ Method fieldModify,
+ Thread thr) {
+ enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
+ }
+
+ public static void enableMethodTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Thread thr) {
+ enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+ }
+
+ public static void enableSingleStepTracing(Class<?> methodClass,
+ Method singleStep,
+ Thread thr) {
+ enableTracing(methodClass, null, null, null, null, singleStep, thr);
+ }
+
+ public static native void watchFieldAccess(Field f);
+ public static native void watchFieldModification(Field f);
+ public static native void watchAllFieldAccesses();
+ public static native void watchAllFieldModifications();
+}
diff --git a/test/1924-frame-pop-toggle/expected.txt b/test/1924-frame-pop-toggle/expected.txt
new file mode 100644
index 0000000000..64ca9db74b
--- /dev/null
+++ b/test/1924-frame-pop-toggle/expected.txt
@@ -0,0 +1,8 @@
+public static void art.Test1924.recurTimesA(int,java.lang.Runnable) pop. Line=44 exception:false
+Ran recurTimes(10) without errors!
+public static void art.Test1924.recurTimesF(int,java.lang.Runnable) pop. Line=84 exception:false
+Ran recurTimes(10) without errors!
+public static void art.Test1924.recurTimesK(int,java.lang.Runnable) pop. Line=124 exception:false
+Ran recurTimes(10) without errors!
+public static void art.Test1924.recurTimesF(int,java.lang.Runnable) pop. Line=83 exception:true
+Caught exception art.Test1924$RecursionError: Unable recur further. Still 90 outstanding! while running recurTimes(100)
diff --git a/test/1924-frame-pop-toggle/frame_pop_toggle.cc b/test/1924-frame-pop-toggle/frame_pop_toggle.cc
new file mode 100644
index 0000000000..1fb7a1f72b
--- /dev/null
+++ b/test/1924-frame-pop-toggle/frame_pop_toggle.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pthread.h>
+
+#include <cstdio>
+#include <iostream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "jvmti.h"
+
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1924FramePop {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1924_toggleFramePop(
+ JNIEnv* env, jclass, jthread thr) {
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_DISABLE, JVMTI_EVENT_FRAME_POP, thr));
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_FRAME_POP, thr));
+}
+
+} // namespace Test1924FramePop
+} // namespace art
+
diff --git a/test/1924-frame-pop-toggle/info.txt b/test/1924-frame-pop-toggle/info.txt
new file mode 100644
index 0000000000..1f7480f969
--- /dev/null
+++ b/test/1924-frame-pop-toggle/info.txt
@@ -0,0 +1,3 @@
+Tests notify frame pop JVMTI functionality.
+
+This tests toggling frame pop off and on.
diff --git a/test/597-deopt-busy-loop/run b/test/1924-frame-pop-toggle/run
index bc04498bfe..51875a7e86 100644..100755
--- a/test/597-deopt-busy-loop/run
+++ b/test/1924-frame-pop-toggle/run
@@ -1,12 +1,12 @@
#!/bin/bash
#
-# Copyright (C) 2017 The Android Open Source Project
+# Copyright 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# 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,
@@ -14,5 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# We want to run in debuggable mode and compiled.
-exec ${RUN} --jit -Xcompiler-option --debuggable "${@}"
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1924-frame-pop-toggle/src/Main.java b/test/1924-frame-pop-toggle/src/Main.java
new file mode 100644
index 0000000000..f48fc2d308
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1924.run();
+ }
+}
diff --git a/test/1924-frame-pop-toggle/src/art/Breakpoint.java b/test/1924-frame-pop-toggle/src/art/Breakpoint.java
new file mode 100644
index 0000000000..bbb89f707f
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+ public static class Manager {
+ public static class BP {
+ public final Executable method;
+ public final long location;
+
+ public BP(Executable method) {
+ this(method, getStartLocation(method));
+ }
+
+ public BP(Executable method, long location) {
+ this.method = method;
+ this.location = location;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof BP) &&
+ method.equals(((BP)other).method) &&
+ location == ((BP)other).location;
+ }
+
+ @Override
+ public String toString() {
+ return method.toString() + " @ " + getLine();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(method, location);
+ }
+
+ public int getLine() {
+ try {
+ LineNumber[] lines = getLineNumberTable(method);
+ int best = -1;
+ for (LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+ }
+
+ private Set<BP> breaks = new HashSet<>();
+
+ public void setBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.add(b)) {
+ Breakpoint.setBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void setBreakpoint(Executable method, long location) {
+ setBreakpoints(new BP(method, location));
+ }
+
+ public void clearBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.remove(b)) {
+ Breakpoint.clearBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void clearBreakpoint(Executable method, long location) {
+ clearBreakpoints(new BP(method, location));
+ }
+
+ public void clearAllBreakpoints() {
+ clearBreakpoints(breaks.toArray(new BP[0]));
+ }
+ }
+
+ public static void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ Thread thr) {
+ startBreakpointWatch(methodClass, breakpointReached, false, thr);
+ }
+
+ /**
+ * Enables the trapping of breakpoint events.
+ *
+ * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+ */
+ public static native void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ boolean allowRecursive,
+ Thread thr);
+ public static native void stopBreakpointWatch(Thread thr);
+
+ public static final class LineNumber implements Comparable<LineNumber> {
+ public final long location;
+ public final int line;
+
+ private LineNumber(long loc, int line) {
+ this.location = loc;
+ this.line = line;
+ }
+
+ public boolean equals(Object other) {
+ return other instanceof LineNumber && ((LineNumber)other).line == line &&
+ ((LineNumber)other).location == location;
+ }
+
+ public int compareTo(LineNumber other) {
+ int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+ if (v != 0) {
+ return v;
+ } else {
+ return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+ }
+ }
+ }
+
+ public static native void setBreakpoint(Executable m, long loc);
+ public static void setBreakpoint(Executable m, LineNumber l) {
+ setBreakpoint(m, l.location);
+ }
+
+ public static native void clearBreakpoint(Executable m, long loc);
+ public static void clearBreakpoint(Executable m, LineNumber l) {
+ clearBreakpoint(m, l.location);
+ }
+
+ private static native Object[] getLineNumberTableNative(Executable m);
+ public static LineNumber[] getLineNumberTable(Executable m) {
+ Object[] nativeTable = getLineNumberTableNative(m);
+ long[] location = (long[])(nativeTable[0]);
+ int[] lines = (int[])(nativeTable[1]);
+ if (lines.length != location.length) {
+ throw new Error("Lines and locations have different lengths!");
+ }
+ LineNumber[] out = new LineNumber[lines.length];
+ for (int i = 0; i < lines.length; i++) {
+ out[i] = new LineNumber(location[i], lines[i]);
+ }
+ return out;
+ }
+
+ public static native long getStartLocation(Executable m);
+
+ public static int locationToLine(Executable m, long location) {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ int best = -1;
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ public static long lineToLocation(Executable m, int line) throws Exception {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.line == line) {
+ return l.location;
+ }
+ }
+ throw new Exception("Unable to find line " + line + " in " + m);
+ } catch (Exception e) {
+ throw new Exception("Unable to get line number info for " + m, e);
+ }
+ }
+}
+
diff --git a/test/1924-frame-pop-toggle/src/art/FramePop.java b/test/1924-frame-pop-toggle/src/art/FramePop.java
new file mode 100644
index 0000000000..86bf226b31
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/art/FramePop.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+
+public class FramePop {
+ public static native void enableFramePopEvent(Class klass, Method method, Thread thr)
+ throws Exception;
+ public static native void notifyFramePop(Thread target, int depth) throws Exception;
+}
diff --git a/test/1924-frame-pop-toggle/src/art/Locals.java b/test/1924-frame-pop-toggle/src/art/Locals.java
new file mode 100644
index 0000000000..22e21be398
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+ public static native void EnableLocalVariableAccess();
+
+ public static class VariableDescription {
+ public final long start_location;
+ public final int length;
+ public final String name;
+ public final String signature;
+ public final String generic_signature;
+ public final int slot;
+
+ public VariableDescription(
+ long start, int length, String name, String sig, String gen_sig, int slot) {
+ this.start_location = start;
+ this.length = length;
+ this.name = name;
+ this.signature = sig;
+ this.generic_signature = gen_sig;
+ this.slot = slot;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "VariableDescription { " +
+ "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+ "}",
+ this.signature,
+ this.name,
+ this.generic_signature,
+ this.slot,
+ this.start_location,
+ this.length);
+ }
+ public boolean equals(Object other) {
+ if (!(other instanceof VariableDescription)) {
+ return false;
+ } else {
+ VariableDescription v = (VariableDescription)other;
+ return Objects.equals(v.signature, signature) &&
+ Objects.equals(v.name, name) &&
+ Objects.equals(v.generic_signature, generic_signature) &&
+ v.slot == slot &&
+ v.start_location == start_location &&
+ v.length == length;
+ }
+ }
+ public int hashCode() {
+ return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+ this.start_location, this.length);
+ }
+ }
+
+ public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+ public static VariableDescription GetVariableAtLine(
+ Executable e, String name, String sig, int line) throws Exception {
+ return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+ }
+
+ public static VariableDescription GetVariableAtLocation(
+ Executable e, String name, String sig, long loc) {
+ VariableDescription[] vars = GetLocalVariableTable(e);
+ for (VariableDescription var : vars) {
+ if (var.start_location <= loc &&
+ var.length + var.start_location > loc &&
+ var.name.equals(name) &&
+ var.signature.equals(sig)) {
+ return var;
+ }
+ }
+ throw new Error(
+ "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+ }
+
+ public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+ public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+ public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+ public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+ public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+ public static native Object GetLocalInstance(Thread thr, int depth);
+
+ public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+ }
+ public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+ }
+ public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+ }
+ public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+ }
+ public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+ public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+ public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+ public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+ public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1924-frame-pop-toggle/src/art/StackTrace.java b/test/1924-frame-pop-toggle/src/art/StackTrace.java
new file mode 100644
index 0000000000..2ea2f201e8
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+ public static class StackFrameData {
+ public final Thread thr;
+ public final Executable method;
+ public final long current_location;
+ public final int depth;
+
+ public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+ this.thr = thr;
+ this.method = e;
+ this.current_location = loc;
+ this.depth = depth;
+ }
+ @Override
+ public String toString() {
+ return String.format(
+ "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+ this.thr,
+ this.method,
+ this.current_location,
+ this.depth);
+ }
+ }
+
+ public static native int GetStackDepth(Thread thr);
+
+ private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+ public static StackFrameData[] GetStackTrace(Thread thr) {
+ // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+ // suspended. The spec says that not being suspended is fine but since we want this to be
+ // consistent we will suspend for the RI.
+ boolean suspend_thread =
+ !System.getProperty("java.vm.name").equals("Dalvik") &&
+ !thr.equals(Thread.currentThread()) &&
+ !Suspension.isSuspended(thr);
+ if (suspend_thread) {
+ Suspension.suspend(thr);
+ }
+ StackFrameData[] out = nativeGetStackTrace(thr);
+ if (suspend_thread) {
+ Suspension.resume(thr);
+ }
+ return out;
+ }
+}
+
diff --git a/test/1924-frame-pop-toggle/src/art/Suspension.java b/test/1924-frame-pop-toggle/src/art/Suspension.java
new file mode 100644
index 0000000000..16e62ccac9
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1924-frame-pop-toggle/src/art/Test1924.java b/test/1924-frame-pop-toggle/src/art/Test1924.java
new file mode 100644
index 0000000000..0ffbfc621c
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/art/Test1924.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.function.IntUnaryOperator;
+import java.util.function.Function;
+
+public class Test1924 {
+ public static void handleFramePop(Executable m, boolean exception, long location) {
+ System.out.println(
+ m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception);
+ }
+
+ public static void recurTimesA(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesB(times - 1, safepoint);
+ }
+
+ public static void recurTimesB(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesC(times - 1, safepoint);
+ }
+
+ public static void recurTimesC(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesD(times - 1, safepoint);
+ }
+
+ public static void recurTimesD(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesE(times - 1, safepoint);
+ }
+
+ public static void recurTimesE(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesF(times - 1, safepoint);
+ }
+
+ public static void recurTimesF(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesG(times - 1, safepoint);
+ }
+
+ public static void recurTimesG(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesH(times - 1, safepoint);
+ }
+
+ public static void recurTimesH(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesI(times - 1, safepoint);
+ }
+
+ public static void recurTimesI(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesJ(times - 1, safepoint);
+ }
+
+ public static void recurTimesJ(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesK(times - 1, safepoint);
+ }
+
+ public static class RecursionError extends Error {
+ public RecursionError(String s) { super(s); }
+ }
+ public static void recurTimesK(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ safepoint.run();
+ throw new RecursionError("Unable recur further. Still " + times + " outstanding!");
+ }
+
+ public static class ThreadPauser implements Runnable {
+ public final Semaphore sem_wakeup_main;
+ public final Semaphore sem_wait;
+
+ public ThreadPauser() {
+ sem_wakeup_main = new Semaphore(0);
+ sem_wait = new Semaphore(0);
+ }
+
+ public void run() {
+ try {
+ sem_wakeup_main.release();
+ sem_wait.acquire();
+ } catch (Exception e) {
+ throw new Error("Error with semaphores!", e);
+ }
+ }
+
+ public void waitForOtherThreadToPause() throws Exception {
+ sem_wakeup_main.acquire();
+ }
+
+ public void wakeupOtherThread() throws Exception {
+ sem_wait.release();
+ }
+ }
+
+ public static void doRecurTestWith(final int times, int watch_frame) throws Exception {
+ final String target_method_name_start = "recurTimes";
+ final ThreadPauser safepoint = new ThreadPauser();
+ Thread target = new Thread(() -> {
+ try {
+ recurTimesA(times, safepoint);
+ System.out.println("Ran recurTimes(" + times + ") without errors!");
+ } catch (RecursionError e) {
+ System.out.println("Caught exception " + e + " while running recurTimes(" + times + ")");
+ }
+ });
+ target.start();
+ safepoint.waitForOtherThreadToPause();
+ Suspension.suspend(target);
+ // Safe block
+ int cnt = 0;
+ StackTrace.StackFrameData target_frame = null;
+ for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(target)) {
+ if (frame.method.getName().startsWith(target_method_name_start)) {
+ if (times - cnt == watch_frame) {
+ target_frame = frame;
+ break;
+ } else {
+ cnt++;
+ }
+ }
+ }
+ if (target_frame != null) {
+ FramePop.notifyFramePop(target, target_frame.depth);
+ } else {
+ System.out.println(
+ "Unable to find stack frame for " + watch_frame + " depth of "
+ + target_method_name_start);
+ }
+ Suspension.resume(target);
+ toggleFramePop(null);
+ safepoint.wakeupOtherThread();
+ target.join();
+ }
+
+ public static void run() throws Exception {
+ // TODO Investigate what thread argument means for FramePop event enable.
+ // Listen for events on all threads.
+ FramePop.enableFramePopEvent(
+ Test1924.class,
+ Test1924.class.getDeclaredMethod(
+ "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE),
+ null);
+ doRecurTestWith(10, 0);
+ doRecurTestWith(10, 5);
+ doRecurTestWith(10, 10);
+ doRecurTestWith(100, 95);
+ }
+
+ public static native void toggleFramePop(Thread thr);
+}
diff --git a/test/1924-frame-pop-toggle/src/art/Trace.java b/test/1924-frame-pop-toggle/src/art/Trace.java
new file mode 100644
index 0000000000..ba3d397b0b
--- /dev/null
+++ b/test/1924-frame-pop-toggle/src/art/Trace.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class Trace {
+ public static native void enableTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Method fieldAccess,
+ Method fieldModify,
+ Method singleStep,
+ Thread thr);
+ public static native void disableTracing(Thread thr);
+
+ public static void enableFieldTracing(Class<?> methodClass,
+ Method fieldAccess,
+ Method fieldModify,
+ Thread thr) {
+ enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
+ }
+
+ public static void enableMethodTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Thread thr) {
+ enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+ }
+
+ public static void enableSingleStepTracing(Class<?> methodClass,
+ Method singleStep,
+ Thread thr) {
+ enableTracing(methodClass, null, null, null, null, singleStep, thr);
+ }
+
+ public static native void watchFieldAccess(Field f);
+ public static native void watchFieldModification(Field f);
+ public static native void watchAllFieldAccesses();
+ public static native void watchAllFieldModifications();
+}
diff --git a/test/1925-self-frame-pop/expected.txt b/test/1925-self-frame-pop/expected.txt
new file mode 100644
index 0000000000..f154205a0f
--- /dev/null
+++ b/test/1925-self-frame-pop/expected.txt
@@ -0,0 +1,4 @@
+public static void art.Test1925.recurTimesE(int,java.lang.Runnable) pop. Line=76 exception:false
+Ran recurTimes(10) without errors!
+public static void art.Test1925.recurTimesE(int,java.lang.Runnable) pop. Line=75 exception:true
+Caught exception art.Test1925$RecursionError: Unable recur further. Still 90 outstanding! while running recurTimes(100)
diff --git a/test/1925-self-frame-pop/info.txt b/test/1925-self-frame-pop/info.txt
new file mode 100644
index 0000000000..ccca498fc5
--- /dev/null
+++ b/test/1925-self-frame-pop/info.txt
@@ -0,0 +1,3 @@
+Tests notify frame pop JVMTI functionality.
+
+This tests setting frame-pop on the current thread
diff --git a/test/1925-self-frame-pop/run b/test/1925-self-frame-pop/run
new file mode 100755
index 0000000000..51875a7e86
--- /dev/null
+++ b/test/1925-self-frame-pop/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1925-self-frame-pop/src/Main.java b/test/1925-self-frame-pop/src/Main.java
new file mode 100644
index 0000000000..2761aefa85
--- /dev/null
+++ b/test/1925-self-frame-pop/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1925.run();
+ }
+}
diff --git a/test/1925-self-frame-pop/src/art/Breakpoint.java b/test/1925-self-frame-pop/src/art/Breakpoint.java
new file mode 100644
index 0000000000..bbb89f707f
--- /dev/null
+++ b/test/1925-self-frame-pop/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+ public static class Manager {
+ public static class BP {
+ public final Executable method;
+ public final long location;
+
+ public BP(Executable method) {
+ this(method, getStartLocation(method));
+ }
+
+ public BP(Executable method, long location) {
+ this.method = method;
+ this.location = location;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof BP) &&
+ method.equals(((BP)other).method) &&
+ location == ((BP)other).location;
+ }
+
+ @Override
+ public String toString() {
+ return method.toString() + " @ " + getLine();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(method, location);
+ }
+
+ public int getLine() {
+ try {
+ LineNumber[] lines = getLineNumberTable(method);
+ int best = -1;
+ for (LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+ }
+
+ private Set<BP> breaks = new HashSet<>();
+
+ public void setBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.add(b)) {
+ Breakpoint.setBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void setBreakpoint(Executable method, long location) {
+ setBreakpoints(new BP(method, location));
+ }
+
+ public void clearBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.remove(b)) {
+ Breakpoint.clearBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void clearBreakpoint(Executable method, long location) {
+ clearBreakpoints(new BP(method, location));
+ }
+
+ public void clearAllBreakpoints() {
+ clearBreakpoints(breaks.toArray(new BP[0]));
+ }
+ }
+
+ public static void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ Thread thr) {
+ startBreakpointWatch(methodClass, breakpointReached, false, thr);
+ }
+
+ /**
+ * Enables the trapping of breakpoint events.
+ *
+ * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+ */
+ public static native void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ boolean allowRecursive,
+ Thread thr);
+ public static native void stopBreakpointWatch(Thread thr);
+
+ public static final class LineNumber implements Comparable<LineNumber> {
+ public final long location;
+ public final int line;
+
+ private LineNumber(long loc, int line) {
+ this.location = loc;
+ this.line = line;
+ }
+
+ public boolean equals(Object other) {
+ return other instanceof LineNumber && ((LineNumber)other).line == line &&
+ ((LineNumber)other).location == location;
+ }
+
+ public int compareTo(LineNumber other) {
+ int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+ if (v != 0) {
+ return v;
+ } else {
+ return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+ }
+ }
+ }
+
+ public static native void setBreakpoint(Executable m, long loc);
+ public static void setBreakpoint(Executable m, LineNumber l) {
+ setBreakpoint(m, l.location);
+ }
+
+ public static native void clearBreakpoint(Executable m, long loc);
+ public static void clearBreakpoint(Executable m, LineNumber l) {
+ clearBreakpoint(m, l.location);
+ }
+
+ private static native Object[] getLineNumberTableNative(Executable m);
+ public static LineNumber[] getLineNumberTable(Executable m) {
+ Object[] nativeTable = getLineNumberTableNative(m);
+ long[] location = (long[])(nativeTable[0]);
+ int[] lines = (int[])(nativeTable[1]);
+ if (lines.length != location.length) {
+ throw new Error("Lines and locations have different lengths!");
+ }
+ LineNumber[] out = new LineNumber[lines.length];
+ for (int i = 0; i < lines.length; i++) {
+ out[i] = new LineNumber(location[i], lines[i]);
+ }
+ return out;
+ }
+
+ public static native long getStartLocation(Executable m);
+
+ public static int locationToLine(Executable m, long location) {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ int best = -1;
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ public static long lineToLocation(Executable m, int line) throws Exception {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.line == line) {
+ return l.location;
+ }
+ }
+ throw new Exception("Unable to find line " + line + " in " + m);
+ } catch (Exception e) {
+ throw new Exception("Unable to get line number info for " + m, e);
+ }
+ }
+}
+
diff --git a/test/1925-self-frame-pop/src/art/FramePop.java b/test/1925-self-frame-pop/src/art/FramePop.java
new file mode 100644
index 0000000000..86bf226b31
--- /dev/null
+++ b/test/1925-self-frame-pop/src/art/FramePop.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+
+public class FramePop {
+ public static native void enableFramePopEvent(Class klass, Method method, Thread thr)
+ throws Exception;
+ public static native void notifyFramePop(Thread target, int depth) throws Exception;
+}
diff --git a/test/1925-self-frame-pop/src/art/Locals.java b/test/1925-self-frame-pop/src/art/Locals.java
new file mode 100644
index 0000000000..22e21be398
--- /dev/null
+++ b/test/1925-self-frame-pop/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+ public static native void EnableLocalVariableAccess();
+
+ public static class VariableDescription {
+ public final long start_location;
+ public final int length;
+ public final String name;
+ public final String signature;
+ public final String generic_signature;
+ public final int slot;
+
+ public VariableDescription(
+ long start, int length, String name, String sig, String gen_sig, int slot) {
+ this.start_location = start;
+ this.length = length;
+ this.name = name;
+ this.signature = sig;
+ this.generic_signature = gen_sig;
+ this.slot = slot;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "VariableDescription { " +
+ "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+ "}",
+ this.signature,
+ this.name,
+ this.generic_signature,
+ this.slot,
+ this.start_location,
+ this.length);
+ }
+ public boolean equals(Object other) {
+ if (!(other instanceof VariableDescription)) {
+ return false;
+ } else {
+ VariableDescription v = (VariableDescription)other;
+ return Objects.equals(v.signature, signature) &&
+ Objects.equals(v.name, name) &&
+ Objects.equals(v.generic_signature, generic_signature) &&
+ v.slot == slot &&
+ v.start_location == start_location &&
+ v.length == length;
+ }
+ }
+ public int hashCode() {
+ return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+ this.start_location, this.length);
+ }
+ }
+
+ public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+ public static VariableDescription GetVariableAtLine(
+ Executable e, String name, String sig, int line) throws Exception {
+ return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+ }
+
+ public static VariableDescription GetVariableAtLocation(
+ Executable e, String name, String sig, long loc) {
+ VariableDescription[] vars = GetLocalVariableTable(e);
+ for (VariableDescription var : vars) {
+ if (var.start_location <= loc &&
+ var.length + var.start_location > loc &&
+ var.name.equals(name) &&
+ var.signature.equals(sig)) {
+ return var;
+ }
+ }
+ throw new Error(
+ "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+ }
+
+ public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+ public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+ public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+ public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+ public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+ public static native Object GetLocalInstance(Thread thr, int depth);
+
+ public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+ }
+ public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+ }
+ public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+ }
+ public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+ }
+ public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+ public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+ public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+ public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+ public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1925-self-frame-pop/src/art/StackTrace.java b/test/1925-self-frame-pop/src/art/StackTrace.java
new file mode 100644
index 0000000000..2ea2f201e8
--- /dev/null
+++ b/test/1925-self-frame-pop/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+ public static class StackFrameData {
+ public final Thread thr;
+ public final Executable method;
+ public final long current_location;
+ public final int depth;
+
+ public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+ this.thr = thr;
+ this.method = e;
+ this.current_location = loc;
+ this.depth = depth;
+ }
+ @Override
+ public String toString() {
+ return String.format(
+ "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+ this.thr,
+ this.method,
+ this.current_location,
+ this.depth);
+ }
+ }
+
+ public static native int GetStackDepth(Thread thr);
+
+ private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+ public static StackFrameData[] GetStackTrace(Thread thr) {
+ // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+ // suspended. The spec says that not being suspended is fine but since we want this to be
+ // consistent we will suspend for the RI.
+ boolean suspend_thread =
+ !System.getProperty("java.vm.name").equals("Dalvik") &&
+ !thr.equals(Thread.currentThread()) &&
+ !Suspension.isSuspended(thr);
+ if (suspend_thread) {
+ Suspension.suspend(thr);
+ }
+ StackFrameData[] out = nativeGetStackTrace(thr);
+ if (suspend_thread) {
+ Suspension.resume(thr);
+ }
+ return out;
+ }
+}
+
diff --git a/test/1925-self-frame-pop/src/art/Suspension.java b/test/1925-self-frame-pop/src/art/Suspension.java
new file mode 100644
index 0000000000..16e62ccac9
--- /dev/null
+++ b/test/1925-self-frame-pop/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1925-self-frame-pop/src/art/Test1925.java b/test/1925-self-frame-pop/src/art/Test1925.java
new file mode 100644
index 0000000000..b413d06545
--- /dev/null
+++ b/test/1925-self-frame-pop/src/art/Test1925.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.function.IntUnaryOperator;
+import java.util.function.Function;
+
+public class Test1925 {
+ public static void handleFramePop(Executable m, boolean exception, long location) {
+ System.out.println(
+ m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception);
+ }
+
+ public static void recurTimesA(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesB(times - 1, safepoint);
+ }
+
+ public static void recurTimesB(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesC(times - 1, safepoint);
+ }
+
+ public static void recurTimesC(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesD(times - 1, safepoint);
+ }
+
+ public static void recurTimesD(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesE(times - 1, safepoint);
+ }
+
+ public static void recurTimesE(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesF(times - 1, safepoint);
+ }
+
+ public static void recurTimesF(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesG(times - 1, safepoint);
+ }
+
+ public static void recurTimesG(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesH(times - 1, safepoint);
+ }
+
+ public static void recurTimesH(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesI(times - 1, safepoint);
+ }
+
+ public static void recurTimesI(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesJ(times - 1, safepoint);
+ }
+
+ public static void recurTimesJ(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesK(times - 1, safepoint);
+ }
+
+ public static class RecursionError extends Error {
+ public RecursionError(String s) { super(s); }
+ }
+ public static void recurTimesK(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ safepoint.run();
+ throw new RecursionError("Unable recur further. Still " + times + " outstanding!");
+ }
+
+ public static void doRecurTestWith(final int times, int watch_frame) throws Exception {
+ final String target_method_name_start = "recurTimes";
+ final Runnable safepoint = () -> {
+ StackTrace.StackFrameData target_frame = null;
+ int cnt = 0;
+ for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) {
+ if (frame.method.getName().startsWith(target_method_name_start)) {
+ if (times - cnt == watch_frame) {
+ target_frame = frame;
+ break;
+ } else {
+ cnt++;
+ }
+ }
+ }
+ try {
+ FramePop.notifyFramePop(null, target_frame.depth);
+ } catch (Exception e) {
+ throw new Error("Unexpected error in notifyFramePop!", e);
+ }
+ };
+ try {
+ recurTimesA(times, safepoint);
+ System.out.println("Ran recurTimes(" + times + ") without errors!");
+ } catch (Throwable e) {
+ System.out.println("Caught exception " + e + " while running recurTimes(" + times + ")");
+ }
+ }
+
+ public static void run() throws Exception {
+ FramePop.enableFramePopEvent(
+ Test1925.class,
+ Test1925.class.getDeclaredMethod(
+ "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE),
+ null);
+ doRecurTestWith(10, 5);
+ doRecurTestWith(100, 95);
+ }
+}
diff --git a/test/1925-self-frame-pop/src/art/Trace.java b/test/1925-self-frame-pop/src/art/Trace.java
new file mode 100644
index 0000000000..ba3d397b0b
--- /dev/null
+++ b/test/1925-self-frame-pop/src/art/Trace.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class Trace {
+ public static native void enableTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Method fieldAccess,
+ Method fieldModify,
+ Method singleStep,
+ Thread thr);
+ public static native void disableTracing(Thread thr);
+
+ public static void enableFieldTracing(Class<?> methodClass,
+ Method fieldAccess,
+ Method fieldModify,
+ Thread thr) {
+ enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
+ }
+
+ public static void enableMethodTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Thread thr) {
+ enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+ }
+
+ public static void enableSingleStepTracing(Class<?> methodClass,
+ Method singleStep,
+ Thread thr) {
+ enableTracing(methodClass, null, null, null, null, singleStep, thr);
+ }
+
+ public static native void watchFieldAccess(Field f);
+ public static native void watchFieldModification(Field f);
+ public static native void watchAllFieldAccesses();
+ public static native void watchAllFieldModifications();
+}
diff --git a/test/1926-missed-frame-pop/expected.txt b/test/1926-missed-frame-pop/expected.txt
new file mode 100644
index 0000000000..20e723f3ee
--- /dev/null
+++ b/test/1926-missed-frame-pop/expected.txt
@@ -0,0 +1,9 @@
+Ran recurTimes(10) without errors after disabling frame pop event!
+renabling frame pop event with similar stack.
+Ran recurTimes(10) without errors!
+Ran recurTimes(10) without errors after disabling frame pop event!
+renabling frame pop event with similar stack.
+Ran recurTimes(10) without errors!
+Ran recurTimes(10) without errors after disabling frame pop event!
+renabling frame pop event with similar stack.
+Ran recurTimes(10) without errors!
diff --git a/test/1926-missed-frame-pop/frame_pop_missed.cc b/test/1926-missed-frame-pop/frame_pop_missed.cc
new file mode 100644
index 0000000000..c5104de1fa
--- /dev/null
+++ b/test/1926-missed-frame-pop/frame_pop_missed.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pthread.h>
+
+#include <cstdio>
+#include <iostream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "jvmti.h"
+
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1926FramePopMissed {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1926_disableFramePop(
+ JNIEnv* env, jclass, jthread thr) {
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_DISABLE, JVMTI_EVENT_FRAME_POP, thr));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1926_reenableFramePop(
+ JNIEnv* env, jclass, jthread thr) {
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_FRAME_POP, thr));
+}
+
+} // namespace Test1926FramePopMissed
+} // namespace art
+
diff --git a/test/1926-missed-frame-pop/info.txt b/test/1926-missed-frame-pop/info.txt
new file mode 100644
index 0000000000..b4984d9e4f
--- /dev/null
+++ b/test/1926-missed-frame-pop/info.txt
@@ -0,0 +1,3 @@
+Tests notify frame pop JVMTI functionality.
+
+This tests the normal use case.
diff --git a/test/1926-missed-frame-pop/run b/test/1926-missed-frame-pop/run
new file mode 100755
index 0000000000..51875a7e86
--- /dev/null
+++ b/test/1926-missed-frame-pop/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1926-missed-frame-pop/src/Main.java b/test/1926-missed-frame-pop/src/Main.java
new file mode 100644
index 0000000000..f924079f2d
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1926.run();
+ }
+}
diff --git a/test/1926-missed-frame-pop/src/art/Breakpoint.java b/test/1926-missed-frame-pop/src/art/Breakpoint.java
new file mode 100644
index 0000000000..bbb89f707f
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+ public static class Manager {
+ public static class BP {
+ public final Executable method;
+ public final long location;
+
+ public BP(Executable method) {
+ this(method, getStartLocation(method));
+ }
+
+ public BP(Executable method, long location) {
+ this.method = method;
+ this.location = location;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof BP) &&
+ method.equals(((BP)other).method) &&
+ location == ((BP)other).location;
+ }
+
+ @Override
+ public String toString() {
+ return method.toString() + " @ " + getLine();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(method, location);
+ }
+
+ public int getLine() {
+ try {
+ LineNumber[] lines = getLineNumberTable(method);
+ int best = -1;
+ for (LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+ }
+
+ private Set<BP> breaks = new HashSet<>();
+
+ public void setBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.add(b)) {
+ Breakpoint.setBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void setBreakpoint(Executable method, long location) {
+ setBreakpoints(new BP(method, location));
+ }
+
+ public void clearBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.remove(b)) {
+ Breakpoint.clearBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void clearBreakpoint(Executable method, long location) {
+ clearBreakpoints(new BP(method, location));
+ }
+
+ public void clearAllBreakpoints() {
+ clearBreakpoints(breaks.toArray(new BP[0]));
+ }
+ }
+
+ public static void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ Thread thr) {
+ startBreakpointWatch(methodClass, breakpointReached, false, thr);
+ }
+
+ /**
+ * Enables the trapping of breakpoint events.
+ *
+ * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+ */
+ public static native void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ boolean allowRecursive,
+ Thread thr);
+ public static native void stopBreakpointWatch(Thread thr);
+
+ public static final class LineNumber implements Comparable<LineNumber> {
+ public final long location;
+ public final int line;
+
+ private LineNumber(long loc, int line) {
+ this.location = loc;
+ this.line = line;
+ }
+
+ public boolean equals(Object other) {
+ return other instanceof LineNumber && ((LineNumber)other).line == line &&
+ ((LineNumber)other).location == location;
+ }
+
+ public int compareTo(LineNumber other) {
+ int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+ if (v != 0) {
+ return v;
+ } else {
+ return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+ }
+ }
+ }
+
+ public static native void setBreakpoint(Executable m, long loc);
+ public static void setBreakpoint(Executable m, LineNumber l) {
+ setBreakpoint(m, l.location);
+ }
+
+ public static native void clearBreakpoint(Executable m, long loc);
+ public static void clearBreakpoint(Executable m, LineNumber l) {
+ clearBreakpoint(m, l.location);
+ }
+
+ private static native Object[] getLineNumberTableNative(Executable m);
+ public static LineNumber[] getLineNumberTable(Executable m) {
+ Object[] nativeTable = getLineNumberTableNative(m);
+ long[] location = (long[])(nativeTable[0]);
+ int[] lines = (int[])(nativeTable[1]);
+ if (lines.length != location.length) {
+ throw new Error("Lines and locations have different lengths!");
+ }
+ LineNumber[] out = new LineNumber[lines.length];
+ for (int i = 0; i < lines.length; i++) {
+ out[i] = new LineNumber(location[i], lines[i]);
+ }
+ return out;
+ }
+
+ public static native long getStartLocation(Executable m);
+
+ public static int locationToLine(Executable m, long location) {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ int best = -1;
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ public static long lineToLocation(Executable m, int line) throws Exception {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.line == line) {
+ return l.location;
+ }
+ }
+ throw new Exception("Unable to find line " + line + " in " + m);
+ } catch (Exception e) {
+ throw new Exception("Unable to get line number info for " + m, e);
+ }
+ }
+}
+
diff --git a/test/1926-missed-frame-pop/src/art/FramePop.java b/test/1926-missed-frame-pop/src/art/FramePop.java
new file mode 100644
index 0000000000..86bf226b31
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/art/FramePop.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+
+public class FramePop {
+ public static native void enableFramePopEvent(Class klass, Method method, Thread thr)
+ throws Exception;
+ public static native void notifyFramePop(Thread target, int depth) throws Exception;
+}
diff --git a/test/1926-missed-frame-pop/src/art/Locals.java b/test/1926-missed-frame-pop/src/art/Locals.java
new file mode 100644
index 0000000000..22e21be398
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+ public static native void EnableLocalVariableAccess();
+
+ public static class VariableDescription {
+ public final long start_location;
+ public final int length;
+ public final String name;
+ public final String signature;
+ public final String generic_signature;
+ public final int slot;
+
+ public VariableDescription(
+ long start, int length, String name, String sig, String gen_sig, int slot) {
+ this.start_location = start;
+ this.length = length;
+ this.name = name;
+ this.signature = sig;
+ this.generic_signature = gen_sig;
+ this.slot = slot;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "VariableDescription { " +
+ "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+ "}",
+ this.signature,
+ this.name,
+ this.generic_signature,
+ this.slot,
+ this.start_location,
+ this.length);
+ }
+ public boolean equals(Object other) {
+ if (!(other instanceof VariableDescription)) {
+ return false;
+ } else {
+ VariableDescription v = (VariableDescription)other;
+ return Objects.equals(v.signature, signature) &&
+ Objects.equals(v.name, name) &&
+ Objects.equals(v.generic_signature, generic_signature) &&
+ v.slot == slot &&
+ v.start_location == start_location &&
+ v.length == length;
+ }
+ }
+ public int hashCode() {
+ return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+ this.start_location, this.length);
+ }
+ }
+
+ public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+ public static VariableDescription GetVariableAtLine(
+ Executable e, String name, String sig, int line) throws Exception {
+ return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+ }
+
+ public static VariableDescription GetVariableAtLocation(
+ Executable e, String name, String sig, long loc) {
+ VariableDescription[] vars = GetLocalVariableTable(e);
+ for (VariableDescription var : vars) {
+ if (var.start_location <= loc &&
+ var.length + var.start_location > loc &&
+ var.name.equals(name) &&
+ var.signature.equals(sig)) {
+ return var;
+ }
+ }
+ throw new Error(
+ "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+ }
+
+ public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+ public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+ public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+ public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+ public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+ public static native Object GetLocalInstance(Thread thr, int depth);
+
+ public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+ }
+ public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+ }
+ public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+ }
+ public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+ SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+ }
+ public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+ public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+ public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+ public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+ public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1926-missed-frame-pop/src/art/StackTrace.java b/test/1926-missed-frame-pop/src/art/StackTrace.java
new file mode 100644
index 0000000000..2ea2f201e8
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/art/StackTrace.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+ public static class StackFrameData {
+ public final Thread thr;
+ public final Executable method;
+ public final long current_location;
+ public final int depth;
+
+ public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+ this.thr = thr;
+ this.method = e;
+ this.current_location = loc;
+ this.depth = depth;
+ }
+ @Override
+ public String toString() {
+ return String.format(
+ "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+ this.thr,
+ this.method,
+ this.current_location,
+ this.depth);
+ }
+ }
+
+ public static native int GetStackDepth(Thread thr);
+
+ private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+ public static StackFrameData[] GetStackTrace(Thread thr) {
+ // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+ // suspended. The spec says that not being suspended is fine but since we want this to be
+ // consistent we will suspend for the RI.
+ boolean suspend_thread =
+ !System.getProperty("java.vm.name").equals("Dalvik") &&
+ !thr.equals(Thread.currentThread()) &&
+ !Suspension.isSuspended(thr);
+ if (suspend_thread) {
+ Suspension.suspend(thr);
+ }
+ StackFrameData[] out = nativeGetStackTrace(thr);
+ if (suspend_thread) {
+ Suspension.resume(thr);
+ }
+ return out;
+ }
+}
+
diff --git a/test/1926-missed-frame-pop/src/art/Suspension.java b/test/1926-missed-frame-pop/src/art/Suspension.java
new file mode 100644
index 0000000000..16e62ccac9
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1926-missed-frame-pop/src/art/Test1926.java b/test/1926-missed-frame-pop/src/art/Test1926.java
new file mode 100644
index 0000000000..cb210729a9
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/art/Test1926.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.function.IntUnaryOperator;
+import java.util.function.Function;
+
+public class Test1926 {
+ public static void handleFramePop(Executable m, boolean exception, long location) {
+ System.out.println(
+ m + " pop. Line=" + Breakpoint.locationToLine(m, location) + " exception:" + exception);
+ }
+
+ public static void recurTimesA(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesB(times - 1, safepoint);
+ }
+
+ public static void recurTimesB(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesC(times - 1, safepoint);
+ }
+
+ public static void recurTimesC(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesD(times - 1, safepoint);
+ }
+
+ public static void recurTimesD(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesE(times - 1, safepoint);
+ }
+
+ public static void recurTimesE(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesF(times - 1, safepoint);
+ }
+
+ public static void recurTimesF(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesG(times - 1, safepoint);
+ }
+
+ public static void recurTimesG(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesH(times - 1, safepoint);
+ }
+
+ public static void recurTimesH(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesI(times - 1, safepoint);
+ }
+
+ public static void recurTimesI(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesJ(times - 1, safepoint);
+ }
+
+ public static void recurTimesJ(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesK(times - 1, safepoint);
+ }
+
+ public static void recurTimesK(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ recurTimesL(times - 1, safepoint);
+ }
+
+ public static class RecursionError extends Error {
+ public RecursionError(String s) { super(s); }
+ }
+
+ public static void recurTimesL(int times, Runnable safepoint) {
+ if (times == 0) {
+ safepoint.run();
+ return;
+ }
+ safepoint.run();
+ throw new RecursionError("Unable recur further. Still " + times + " outstanding!");
+ }
+
+ public static class ThreadPauser implements Runnable {
+ public final Semaphore sem_wakeup_main;
+ public final Semaphore sem_wait;
+
+ public ThreadPauser() {
+ sem_wakeup_main = new Semaphore(0);
+ sem_wait = new Semaphore(0);
+ }
+
+ public void run() {
+ try {
+ sem_wakeup_main.release();
+ sem_wait.acquire();
+ } catch (Exception e) {
+ throw new Error("Error with semaphores!", e);
+ }
+ }
+
+ public void waitForOtherThreadToPause() throws Exception {
+ sem_wakeup_main.acquire();
+ }
+
+ public void wakeupOtherThread() throws Exception {
+ sem_wait.release();
+ }
+ }
+
+ public static void doRecurTestWith(final int times, int watch_frame) throws Exception {
+ final String target_method_name_start = "recurTimes";
+ final ThreadPauser safepoint = new ThreadPauser();
+ Thread target = new Thread(() -> {
+ recurTimesA(times, () -> {
+ safepoint.run();
+ disableFramePop(null);
+ });
+ System.out.println("Ran recurTimes(" + times + ") without errors after disabling " +
+ "frame pop event!");
+ System.out.println("renabling frame pop event with similar stack.");
+ recurTimesB(times, () -> { reenableFramePop(null); });
+ System.out.println("Ran recurTimes(" + times + ") without errors!");
+ });
+ target.start();
+ safepoint.waitForOtherThreadToPause();
+ Suspension.suspend(target);
+ // Safe block
+ int cnt = 0;
+ StackTrace.StackFrameData target_frame = null;
+ for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(target)) {
+ if (frame.method.getName().startsWith(target_method_name_start)) {
+ if (times - cnt == watch_frame) {
+ target_frame = frame;
+ break;
+ } else {
+ cnt++;
+ }
+ }
+ }
+ if (target_frame != null) {
+ FramePop.notifyFramePop(target, target_frame.depth);
+ } else {
+ System.out.println(
+ "Unable to find stack frame for " + watch_frame + " depth of "
+ + target_method_name_start);
+ }
+ Suspension.resume(target);
+ safepoint.wakeupOtherThread();
+ target.join();
+ }
+
+ public static void run() throws Exception {
+ // Listen for events on all threads.
+ FramePop.enableFramePopEvent(
+ Test1926.class,
+ Test1926.class.getDeclaredMethod(
+ "handleFramePop", Executable.class, Boolean.TYPE, Long.TYPE),
+ null);
+ doRecurTestWith(10, 0);
+ doRecurTestWith(10, 5);
+ doRecurTestWith(10, 10);
+ }
+
+ public static native void disableFramePop(Thread thr);
+ public static native void reenableFramePop(Thread thr);
+}
diff --git a/test/1926-missed-frame-pop/src/art/Trace.java b/test/1926-missed-frame-pop/src/art/Trace.java
new file mode 100644
index 0000000000..ba3d397b0b
--- /dev/null
+++ b/test/1926-missed-frame-pop/src/art/Trace.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class Trace {
+ public static native void enableTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Method fieldAccess,
+ Method fieldModify,
+ Method singleStep,
+ Thread thr);
+ public static native void disableTracing(Thread thr);
+
+ public static void enableFieldTracing(Class<?> methodClass,
+ Method fieldAccess,
+ Method fieldModify,
+ Thread thr) {
+ enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
+ }
+
+ public static void enableMethodTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Thread thr) {
+ enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+ }
+
+ public static void enableSingleStepTracing(Class<?> methodClass,
+ Method singleStep,
+ Thread thr) {
+ enableTracing(methodClass, null, null, null, null, singleStep, thr);
+ }
+
+ public static native void watchFieldAccess(Field f);
+ public static native void watchFieldModification(Field f);
+ public static native void watchAllFieldAccesses();
+ public static native void watchAllFieldModifications();
+}
diff --git a/test/597-deopt-busy-loop/expected.txt b/test/597-deopt-busy-loop/expected.txt
deleted file mode 100644
index f993efcdad..0000000000
--- a/test/597-deopt-busy-loop/expected.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-JNI_OnLoad called
-Finishing
diff --git a/test/597-deopt-busy-loop/info.txt b/test/597-deopt-busy-loop/info.txt
deleted file mode 100644
index 2c50dbbe79..0000000000
--- a/test/597-deopt-busy-loop/info.txt
+++ /dev/null
@@ -1 +0,0 @@
-Test deoptimizing when returning from suspend-check runtime method.
diff --git a/test/597-deopt-busy-loop/src/Main.java b/test/597-deopt-busy-loop/src/Main.java
deleted file mode 100644
index 46b6bbf4f3..0000000000
--- a/test/597-deopt-busy-loop/src/Main.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-public class Main implements Runnable {
- static final int numberOfThreads = 2;
- volatile static boolean sExitFlag = false;
- volatile static boolean sEntered = false;
- int threadIndex;
-
- private static native void deoptimizeAll();
- private static native void assertIsInterpreted();
- private static native void assertIsManaged();
- private static native void ensureJitCompiled(Class<?> cls, String methodName);
-
- Main(int index) {
- threadIndex = index;
- }
-
- public static void main(String[] args) throws Exception {
- System.loadLibrary(args[0]);
-
- final Thread[] threads = new Thread[numberOfThreads];
- for (int t = 0; t < threads.length; t++) {
- threads[t] = new Thread(new Main(t));
- threads[t].start();
- }
- for (Thread t : threads) {
- t.join();
- }
- System.out.println("Finishing");
- }
-
- public void $noinline$busyLoop() {
- assertIsManaged();
- sEntered = true;
- for (;;) {
- if (sExitFlag) {
- break;
- }
- }
- assertIsInterpreted();
- }
-
- public void run() {
- if (threadIndex == 0) {
- while (!sEntered) {
- Thread.yield();
- }
- deoptimizeAll();
- sExitFlag = true;
- } else {
- ensureJitCompiled(Main.class, "$noinline$busyLoop");
- $noinline$busyLoop();
- }
- }
-}
diff --git a/test/597-deopt-invoke-stub/expected.txt b/test/597-deopt-invoke-stub/expected.txt
deleted file mode 100644
index f993efcdad..0000000000
--- a/test/597-deopt-invoke-stub/expected.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-JNI_OnLoad called
-Finishing
diff --git a/test/597-deopt-invoke-stub/info.txt b/test/597-deopt-invoke-stub/info.txt
deleted file mode 100644
index 31960a988b..0000000000
--- a/test/597-deopt-invoke-stub/info.txt
+++ /dev/null
@@ -1 +0,0 @@
-Test deoptimizing when returning from a quick-to-interpreter bridge.
diff --git a/test/597-deopt-invoke-stub/src/Main.java b/test/597-deopt-invoke-stub/src/Main.java
deleted file mode 100644
index 075178361b..0000000000
--- a/test/597-deopt-invoke-stub/src/Main.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-public class Main implements Runnable {
- static final int numberOfThreads = 2;
- volatile static boolean sExitFlag = false;
- volatile static boolean sEntered = false;
- int threadIndex;
-
- private static native void deoptimizeAll();
- private static native void assertIsInterpreted();
- private static native void assertIsManaged();
- private static native void ensureJitCompiled(Class<?> cls, String methodName);
-
- Main(int index) {
- threadIndex = index;
- }
-
- public static void main(String[] args) throws Exception {
- System.loadLibrary(args[0]);
-
- final Thread[] threads = new Thread[numberOfThreads];
- for (int t = 0; t < threads.length; t++) {
- threads[t] = new Thread(new Main(t));
- threads[t].start();
- }
- for (Thread t : threads) {
- t.join();
- }
- System.out.println("Finishing");
- }
-
- private static int $noinline$bar() {
- // Should be entered via interpreter bridge.
- assertIsInterpreted();
- sEntered = true;
- while (!sExitFlag) {}
- assertIsInterpreted();
- return 0x1234;
- }
-
- public void $noinline$foo() {
- assertIsManaged();
- if ($noinline$bar() != 0x1234) {
- System.out.println("Bad return value");
- }
- assertIsInterpreted();
- }
-
- public void run() {
- if (threadIndex == 0) {
- while (!sEntered) {
- Thread.yield();
- }
- deoptimizeAll();
- sExitFlag = true;
- } else {
- ensureJitCompiled(Main.class, "$noinline$foo");
- $noinline$foo();
- }
- }
-}
diff --git a/test/620-checker-bce-intrinsics/src/Main.java b/test/620-checker-bce-intrinsics/src/Main.java
index afc3c656bc..e0b5986bc3 100644
--- a/test/620-checker-bce-intrinsics/src/Main.java
+++ b/test/620-checker-bce-intrinsics/src/Main.java
@@ -34,15 +34,15 @@ public class Main {
return x;
}
- /// CHECK-START: int Main.oneArrayAbs(int[], int) BCE (before)
+ /// CHECK-START: int Main.oneArrayAbs(int[], int[]) BCE (before)
/// CHECK-DAG: BoundsCheck loop:<<Loop:B\d+>> outer_loop:none
//
- /// CHECK-START: int Main.oneArrayAbs(int[], int) BCE (after)
+ /// CHECK-START: int Main.oneArrayAbs(int[], int[]) BCE (after)
/// CHECK-NOT: BoundsCheck
/// CHECK-NOT: Deoptimize
- static int oneArrayAbs(int[] a, int lo) {
+ static int oneArrayAbs(int[] a, int[] b) {
int x = 0;
- for (int i = Math.abs(lo); i < a.length; i++) {
+ for (int i = Math.abs(b.length); i < a.length; i++) {
x += a[i];
}
return x;
@@ -221,15 +221,15 @@ public class Main {
int[] b = { 6, 7, 8, 9, 4, 2 };
int[] c = { 1, 2, 3 };
int[] d = { 8, 5, 3, 2 };
+ int[] e = { };
expectEquals(15, oneArray(a));
expectEquals(36, oneArray(b));
expectEquals(6, oneArray(c));
expectEquals(18, oneArray(d));
- expectEquals(5, oneArrayAbs(a, -4));
- expectEquals(15, oneArrayAbs(a, 0));
- expectEquals(5, oneArrayAbs(a, 4));
+ expectEquals(15, oneArrayAbs(a, e));
+ expectEquals(5, oneArrayAbs(a, d));
expectEquals(30, twoArrays(a, a));
expectEquals(49, twoArrays(a, b));
diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java
index 0e8561299c..056ed914a8 100644
--- a/test/623-checker-loop-regressions/src/Main.java
+++ b/test/623-checker-loop-regressions/src/Main.java
@@ -464,6 +464,15 @@ public class Main {
return r;
}
+ static int absCanBeNegative(int x) {
+ int a[] = { 1, 2, 3 };
+ int y = 0;
+ for (int i = Math.abs(x); i < a.length; i++) {
+ y += a[i];
+ }
+ return y;
+ }
+
public static void main(String[] args) {
expectEquals(10, earlyExitFirst(-1));
for (int i = 0; i <= 10; i++) {
@@ -586,6 +595,24 @@ public class Main {
expectEquals(11, f[i]);
}
+ expectEquals(0, absCanBeNegative(-3));
+ expectEquals(3, absCanBeNegative(-2));
+ expectEquals(5, absCanBeNegative(-1));
+ expectEquals(6, absCanBeNegative(0));
+ expectEquals(5, absCanBeNegative(1));
+ expectEquals(3, absCanBeNegative(2));
+ expectEquals(0, absCanBeNegative(3));
+ expectEquals(0, absCanBeNegative(Integer.MAX_VALUE));
+ // Abs(min_int) = min_int.
+ int verify = 0;
+ try {
+ absCanBeNegative(Integer.MIN_VALUE);
+ verify = 1;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ verify = 2;
+ }
+ expectEquals(2, verify);
+
System.out.println("passed");
}
diff --git a/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali b/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali
index 882f0e9256..26044726ad 100644
--- a/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali
+++ b/test/954-invoke-polymorphic-verifier/smali/Unresolved.smali
@@ -23,7 +23,7 @@
.line 23
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
# Get an unresolvable instance (abstract class)
- invoke-static {}, LAbstract;->getUnresolvedInstance()Lother/thing/Foo;
+ invoke-static {}, LUnresolved;->getUnresolvedInstance()Lother/thing/Foo;
move-result-object v0
const-string v1, "1"
const-string v2, "2"
diff --git a/test/Android.bp b/test/Android.bp
index d0c05652b1..2aed50c455 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -248,6 +248,7 @@ art_cc_defaults {
"ti-agent/test_env.cc",
"ti-agent/breakpoint_helper.cc",
"ti-agent/common_helper.cc",
+ "ti-agent/frame_pop_helper.cc",
"ti-agent/locals_helper.cc",
"ti-agent/redefinition_helper.cc",
"ti-agent/suspension_helper.cc",
@@ -295,6 +296,8 @@ art_cc_defaults {
"1920-suspend-native-monitor/native_suspend_monitor.cc",
"1921-suspend-native-recursive-monitor/native_suspend_recursive_monitor.cc",
"1922-owned-monitors-info/owned_monitors.cc",
+ "1924-frame-pop-toggle/frame_pop_toggle.cc",
+ "1926-missed-frame-pop/frame_pop_missed.cc",
],
shared_libs: [
"libbase",
diff --git a/test/knownfailures.json b/test/knownfailures.json
index d76103a226..df7544e6e0 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -207,9 +207,7 @@
"variant": "trace | stream"
},
{
- "tests": ["597-deopt-busy-loop",
- "597-deopt-invoke-stub",
- "604-hot-static-interface",
+ "tests": ["604-hot-static-interface",
"612-jit-dex-cache",
"613-inlining-dex-cache",
"626-set-resolved-string"],
@@ -683,6 +681,16 @@
"env_vars": {"SANITIZE_HOST": "address"}
},
{
+ "tests": [
+ "1923-frame-pop",
+ "1924-frame-pop-toggle"
+ ],
+ "description": "ASAN seems to make these tests overflow their stacks.",
+ "variant": "64",
+ "bug": "b/65189092",
+ "env_vars": {"SANITIZE_HOST": "address"}
+ },
+ {
"tests": ["988-method-trace"],
"variant": "redefine-stress | jvmti-stress",
"description": "Test disabled due to redefine-stress disabling intrinsics which changes the trace output slightly."
diff --git a/test/run-test b/test/run-test
index e6196a0213..9996986a92 100755
--- a/test/run-test
+++ b/test/run-test
@@ -407,6 +407,10 @@ while true; do
elif [ "x$1" = "x--random-profile" ]; then
run_args="${run_args} --random-profile"
shift
+ elif [ "x$1" = "x--dex2oat-jobs" ]; then
+ shift
+ run_args="${run_args} -Xcompiler-option -j$1"
+ shift
elif expr "x$1" : "x--" >/dev/null 2>&1; then
echo "unknown $0 option: $1" 1>&2
usage="yes"
@@ -702,6 +706,7 @@ if [ "$usage" = "yes" ]; then
echo " --bisection-search Perform bisection bug search."
echo " --vdex Test using vdex as in input to dex2oat. Only works with --prebuild."
echo " --suspend-timeout Change thread suspend timeout ms (default 500000)."
+ echo " --dex2oat-jobs Number of dex2oat jobs."
) 1>&2 # Direct to stderr so usage is not printed if --quiet is set.
exit 1
fi
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 68e1856adb..f425097b57 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -127,6 +127,7 @@ build = False
gdb = False
gdb_arg = ''
stop_testrunner = False
+dex2oat_jobs = -1 # -1 corresponds to default threads for dex2oat
def gather_test_info():
"""The method gathers test information about the test to be run which includes
@@ -341,6 +342,9 @@ def run_tests(tests):
if gdb_arg:
options_all += ' --gdb-arg ' + gdb_arg
+ if dex2oat_jobs != -1:
+ options_all += ' --dex2oat-jobs ' + str(dex2oat_jobs)
+
config = itertools.product(tests, TARGET_TYPES, RUN_TYPES, PREBUILD_TYPES,
COMPILER_TYPES, RELOCATE_TYPES, TRACE_TYPES,
GC_TYPES, JNI_TYPES, IMAGE_TYPES, PICTEST_TYPES,
@@ -860,6 +864,7 @@ def parse_option():
global gdb
global gdb_arg
global timeout
+ global dex2oat_jobs
parser = argparse.ArgumentParser(description="Runs all or a subset of the ART test suite.")
parser.add_argument('-t', '--test', dest='test', help='name of the test')
@@ -887,6 +892,8 @@ def parse_option():
parser.set_defaults(build = env.ART_TEST_RUN_TEST_BUILD)
parser.add_argument('--gdb', action='store_true', dest='gdb')
parser.add_argument('--gdb-arg', dest='gdb_arg')
+ parser.add_argument('--dex2oat-jobs', type=int, dest='dex2oat_jobs',
+ help='Number of dex2oat jobs')
options = vars(parser.parse_args())
if options['build_target']:
@@ -987,6 +994,8 @@ def parse_option():
if options['gdb_arg']:
gdb_arg = options['gdb_arg']
timeout = options['timeout']
+ if options['dex2oat_jobs']:
+ dex2oat_jobs = options['dex2oat_jobs']
return test
diff --git a/test/ti-agent/frame_pop_helper.cc b/test/ti-agent/frame_pop_helper.cc
new file mode 100644
index 0000000000..4571032ce6
--- /dev/null
+++ b/test/ti-agent/frame_pop_helper.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common_helper.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace common_frame_pop {
+
+struct FramePopData {
+ jclass test_klass;
+ jmethodID pop_method;
+};
+
+static void framePopCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jmethodID method ATTRIBUTE_UNUSED,
+ jboolean was_popped_by_exception) {
+ FramePopData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ jlong location;
+ jmethodID frame_method;
+ if (JvmtiErrorToException(jnienv,
+ jvmti,
+ jvmti->GetFrameLocation(thr, 0, &frame_method, &location))) {
+ return;
+ }
+ CHECK(data->pop_method != nullptr);
+ jobject method_arg = GetJavaMethod(jvmti, jnienv, frame_method);
+ jnienv->CallStaticVoidMethod(data->test_klass,
+ data->pop_method,
+ method_arg,
+ was_popped_by_exception,
+ location);
+ jnienv->DeleteLocalRef(method_arg);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_FramePop_enableFramePopEvent(
+ JNIEnv* env, jclass, jclass klass, jobject notify_method, jthread thr) {
+ FramePopData* data = nullptr;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->Allocate(sizeof(FramePopData),
+ reinterpret_cast<unsigned char**>(&data)))) {
+ return;
+ }
+ memset(data, 0, sizeof(FramePopData));
+ data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass));
+ data->pop_method = env->FromReflectedMethod(notify_method);
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ void* old_data = nullptr;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+ return;
+ } else if (old_data != nullptr) {
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+ return;
+ }
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
+ return;
+ }
+ jvmtiCapabilities caps;
+ memset(&caps, 0, sizeof(caps));
+ caps.can_generate_frame_pop_events = 1;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
+ return;
+ }
+ jvmtiEventCallbacks cb;
+ memset(&cb, 0, sizeof(cb));
+ cb.FramePop = framePopCB;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ return;
+ }
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_FRAME_POP,
+ thr));
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_FramePop_makeJvmtiEnvForFramePop(JNIEnv* env, jclass) {
+ JavaVM* vm;
+ jvmtiEnv* out_jvmti_env = nullptr;
+ if (env->GetJavaVM(&vm) != JNI_OK ||
+ vm->GetEnv(reinterpret_cast<void**>(&out_jvmti_env), JVMTI_VERSION_1_0) != JNI_OK) {
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ if (rt_exception.get() == nullptr) {
+ // CNFE should be pending.
+ return 0L;
+ }
+ env->ThrowNew(rt_exception.get(), "Unable to create new jvmti_env");
+ return 0L;
+ }
+ SetAllCapabilities(out_jvmti_env);
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(out_jvmti_env));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_FramePop_notifyFramePop(
+ JNIEnv* env, jclass, jthread thr, jint depth) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->NotifyFramePop(thr, depth));
+}
+
+} // namespace common_frame_pop
+} // namespace art
+
diff --git a/tools/cpp-define-generator/constant_globals.def b/tools/cpp-define-generator/constant_globals.def
index dbaf33cdef..5018f52937 100644
--- a/tools/cpp-define-generator/constant_globals.def
+++ b/tools/cpp-define-generator/constant_globals.def
@@ -19,6 +19,7 @@
#if defined(DEFINE_INCLUDE_DEPENDENCIES)
#include <atomic> // std::memory_order_relaxed
#include "globals.h" // art::kObjectAlignment
+#include "modifiers.h"
#endif
DEFINE_EXPR(STD_MEMORY_ORDER_RELAXED, int32_t, std::memory_order_relaxed)
@@ -30,5 +31,8 @@ DEFINE_OBJECT_EXPR(ALIGNMENT_MASK, size_t, art::kObjectAlignment - 1)
DEFINE_OBJECT_EXPR(ALIGNMENT_MASK_TOGGLED, uint32_t, ~static_cast<uint32_t>(art::kObjectAlignment - 1))
DEFINE_OBJECT_EXPR(ALIGNMENT_MASK_TOGGLED64, uint64_t, ~static_cast<uint64_t>(art::kObjectAlignment - 1))
+DEFINE_EXPR(ACC_OBSOLETE_METHOD, int32_t, art::kAccObsoleteMethod)
+DEFINE_EXPR(ACC_OBSOLETE_METHOD_SHIFT, int32_t, art::WhichPowerOf2(art::kAccObsoleteMethod))
+
#undef DEFINE_OBJECT_EXPR
diff --git a/tools/cpp-define-generator/offset_dexcache.def b/tools/cpp-define-generator/offset_art_method.def
index 43f94344cc..e6a0907759 100644
--- a/tools/cpp-define-generator/offset_dexcache.def
+++ b/tools/cpp-define-generator/offset_art_method.def
@@ -33,10 +33,10 @@
DEFINE_EXPR(DECLARING_CLASS_ ## field_name ## _OFFSET, int32_t, art::mirror::Class::method_name##Offset().Int32Value())
// New macro suffix Method Name (of the Offset method)
-DEFINE_ART_METHOD_OFFSET_SIZED(DEX_CACHE_METHODS, DexCacheResolvedMethods)
DEFINE_ART_METHOD_OFFSET_SIZED(JNI, EntryPointFromJni)
DEFINE_ART_METHOD_OFFSET_SIZED(QUICK_CODE, EntryPointFromQuickCompiledCode)
DEFINE_ART_METHOD_OFFSET(DECLARING_CLASS, DeclaringClass)
+DEFINE_ART_METHOD_OFFSET(ACCESS_FLAGS, AccessFlags)
#undef DEFINE_ART_METHOD_OFFSET
#undef DEFINE_ART_METHOD_OFFSET_32
diff --git a/tools/cpp-define-generator/offset_mirror_class.def b/tools/cpp-define-generator/offset_mirror_class.def
new file mode 100644
index 0000000000..9b7bfce0c0
--- /dev/null
+++ b/tools/cpp-define-generator/offset_mirror_class.def
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Offsets within java.lang.Class (mirror::Class).
+
+#if defined(DEFINE_INCLUDE_DEPENDENCIES)
+#include "mirror/class.h" // art::mirror::Object
+#endif
+
+#include "common.def" // DEFINE_OFFSET_EXPR
+
+#define DEFINE_MIRROR_CLASS_OFFSET(field_name, method_name) \
+ DEFINE_OFFSET_EXPR(MIRROR_CLASS, field_name, int32_t, art::mirror::Class::method_name##Offset().Int32Value())
+
+// New macro suffix Method Name (of the Offset method)
+DEFINE_MIRROR_CLASS_OFFSET(DEX_CACHE, DexCache)
+
+#undef DEFINE_MIRROR_CLASS_OFFSET
+#include "common_undef.def" // undef DEFINE_OFFSET_EXPR
diff --git a/tools/cpp-define-generator/offset_mirror_dex_cache.def b/tools/cpp-define-generator/offset_mirror_dex_cache.def
new file mode 100644
index 0000000000..8f008bb631
--- /dev/null
+++ b/tools/cpp-define-generator/offset_mirror_dex_cache.def
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Offsets within java.lang.DexCache (mirror::DexCache).
+
+#if defined(DEFINE_INCLUDE_DEPENDENCIES)
+#include "mirror/class.h" // art::mirror::Object
+#endif
+
+#include "common.def" // DEFINE_OFFSET_EXPR
+
+#define DEFINE_MIRROR_DEX_CACHE_OFFSET(field_name, method_name) \
+ DEFINE_OFFSET_EXPR(MIRROR_DEX_CACHE, field_name, int32_t, art::mirror::DexCache::method_name##Offset().Int32Value())
+
+// New macro suffix Method Name (of the Offset method)
+DEFINE_MIRROR_DEX_CACHE_OFFSET(RESOLVED_METHODS, ResolvedMethods)
+
+#undef DEFINE_MIRROR_CLASS_OFFSET
+#include "common_undef.def" // undef DEFINE_OFFSET_EXPR
diff --git a/tools/cpp-define-generator/offsets_all.def b/tools/cpp-define-generator/offsets_all.def
index b8947de2dc..c2e8c9728c 100644
--- a/tools/cpp-define-generator/offsets_all.def
+++ b/tools/cpp-define-generator/offsets_all.def
@@ -42,12 +42,13 @@
// #include "offset_shadow_frame.def"
#include "offset_codeitem.def"
// TODO: MIRROR_OBJECT_HEADER_SIZE (depends on #ifdef read barrier)
-// TODO: MIRROR_CLASS offsets (see above)
+#include "offset_mirror_class.def"
+#include "offset_mirror_dex_cache.def"
#include "offset_mirror_object.def"
#include "constant_class.def"
// TODO: MIRROR_*_ARRAY offsets (depends on header size)
// TODO: MIRROR_STRING offsets (depends on header size)
-#include "offset_dexcache.def"
+#include "offset_art_method.def"
#include "constant_dexcache.def"
#include "constant_card_table.def"
#include "constant_heap.def"