Partial fragment deoptimization
We used to do either single frame deoptimization, or full fragment
deoptimization which deoptimizes all the frames in a fragment.
This change allows some methods to be not deoptimizeable, likely due
to some kind of optimization. So we need another deoptimization mode
that unwinds partial fragment. Deoptimizations are now generalized into
either full or partial fragment. A full fragment deoptimization will
deopt all frames in the fragment, and then returns from the invoke stub
to enter interpreter. A partial fragment deoptimization will deopt a
single frame, or all frames up to the method that's not deoptimizeable,
and then jumps to the interpreter bridge.
Currently code not deoptimizeable is the code in boot image since the
code may not be compiled with debuggable flag.
Bug: 28769520
Change-Id: I875c694791cc8ebd5121abcd92ce7b0db95aca38
diff --git a/test/602-deoptimizeable/expected.txt b/test/602-deoptimizeable/expected.txt
new file mode 100644
index 0000000..f993efc
--- /dev/null
+++ b/test/602-deoptimizeable/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Finishing
diff --git a/test/602-deoptimizeable/info.txt b/test/602-deoptimizeable/info.txt
new file mode 100644
index 0000000..d0952f9
--- /dev/null
+++ b/test/602-deoptimizeable/info.txt
@@ -0,0 +1 @@
+Test various cases for full/partial-fragment deoptimization.
diff --git a/test/602-deoptimizeable/src/Main.java b/test/602-deoptimizeable/src/Main.java
new file mode 100644
index 0000000..8032ce9
--- /dev/null
+++ b/test/602-deoptimizeable/src/Main.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+
+class DummyObject {
+ public static boolean sHashCodeInvoked = false;
+ private int i;
+
+ public DummyObject(int i) {
+ this.i = i;
+ }
+
+ public boolean equals(Object obj) {
+ return (obj instanceof DummyObject) && (i == ((DummyObject)obj).i);
+ }
+
+ public int hashCode() {
+ sHashCodeInvoked = true;
+ Main.assertIsManaged();
+ Main.deoptimizeAll();
+ Main.assertIsInterpreted();
+ Main.assertCallerIsManaged(); // Caller is from framework code HashMap.
+ return i % 64;
+ }
+}
+
+public class Main {
+ static boolean sFlag = false;
+
+ public static native void deoptimizeAll();
+ public static native void undeoptimizeAll();
+ public static native void assertIsInterpreted();
+ public static native void assertIsManaged();
+ public static native void assertCallerIsInterpreted();
+ public static native void assertCallerIsManaged();
+
+ public static void execute(Runnable runnable) throws Exception {
+ Thread t = new Thread(runnable);
+ t.start();
+ t.join();
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+ final HashMap<DummyObject, Long> map = new HashMap<DummyObject, Long>();
+
+ // Single-frame deoptimization that covers partial fragment.
+ execute(new Runnable() {
+ public void run() {
+ int[] arr = new int[3];
+ assertIsManaged();
+ int res = $noinline$run1(arr);
+ assertIsManaged(); // Only single frame is deoptimized.
+ if (res != 79) {
+ System.out.println("Failure 1!");
+ System.exit(0);
+ }
+ }
+ });
+
+ // Single-frame deoptimization that covers a full fragment.
+ execute(new Runnable() {
+ public void run() {
+ try {
+ int[] arr = new int[3];
+ assertIsManaged();
+ // Use reflection to call $noinline$run2 so that it does
+ // full-fragment deoptimization since that is an upcall.
+ Class<?> cls = Class.forName("Main");
+ Method method = cls.getDeclaredMethod("$noinline$run2", int[].class);
+ double res = (double)method.invoke(Main.class, arr);
+ assertIsManaged(); // Only single frame is deoptimized.
+ if (res != 79.3d) {
+ System.out.println("Failure 2!");
+ System.exit(0);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ // Full-fragment deoptimization.
+ execute(new Runnable() {
+ public void run() {
+ assertIsManaged();
+ float res = $noinline$run3B();
+ assertIsInterpreted(); // Every deoptimizeable method is deoptimized.
+ if (res != 0.034f) {
+ System.out.println("Failure 3!");
+ System.exit(0);
+ }
+ }
+ });
+
+ undeoptimizeAll(); // Make compiled code useable again.
+
+ // Partial-fragment deoptimization.
+ execute(new Runnable() {
+ public void run() {
+ try {
+ assertIsManaged();
+ map.put(new DummyObject(10), Long.valueOf(100));
+ assertIsInterpreted(); // Every deoptimizeable method is deoptimized.
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ undeoptimizeAll(); // Make compiled code useable again.
+
+ if (!DummyObject.sHashCodeInvoked) {
+ System.out.println("hashCode() method not invoked!");
+ }
+ if (map.get(new DummyObject(10)) != 100) {
+ System.out.println("Wrong hashmap value!");
+ }
+ System.out.println("Finishing");
+ }
+
+ public static int $noinline$run1(int[] arr) {
+ assertIsManaged();
+ // Prevent inlining.
+ if (sFlag) {
+ throw new Error();
+ }
+ boolean caught = false;
+ // BCE will use deoptimization for the code below.
+ try {
+ arr[0] = 1;
+ arr[1] = 1;
+ arr[2] = 1;
+ // This causes AIOOBE and triggers deoptimization from compiled code.
+ arr[3] = 1;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ assertIsInterpreted(); // Single-frame deoptimization triggered.
+ caught = true;
+ }
+ if (!caught) {
+ System.out.println("Expected exception");
+ }
+ assertIsInterpreted();
+ return 79;
+ }
+
+ public static double $noinline$run2(int[] arr) {
+ assertIsManaged();
+ // Prevent inlining.
+ if (sFlag) {
+ throw new Error();
+ }
+ boolean caught = false;
+ // BCE will use deoptimization for the code below.
+ try {
+ arr[0] = 1;
+ arr[1] = 1;
+ arr[2] = 1;
+ // This causes AIOOBE and triggers deoptimization from compiled code.
+ arr[3] = 1;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ assertIsInterpreted(); // Single-frame deoptimization triggered.
+ caught = true;
+ }
+ if (!caught) {
+ System.out.println("Expected exception");
+ }
+ assertIsInterpreted();
+ return 79.3d;
+ }
+
+ public static float $noinline$run3A() {
+ assertIsManaged();
+ // Prevent inlining.
+ if (sFlag) {
+ throw new Error();
+ }
+ // Deoptimize callers.
+ deoptimizeAll();
+ assertIsInterpreted();
+ assertCallerIsInterpreted(); // $noinline$run3B is deoptimizeable.
+ return 0.034f;
+ }
+
+ public static float $noinline$run3B() {
+ assertIsManaged();
+ // Prevent inlining.
+ if (sFlag) {
+ throw new Error();
+ }
+ float res = $noinline$run3A();
+ assertIsInterpreted();
+ return res;
+ }
+}
diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc
index 922eae6..85ea1c8 100644
--- a/test/common/stack_inspect.cc
+++ b/test/common/stack_inspect.cc
@@ -37,17 +37,20 @@
asserts_enabled = false;
}
-
-// public static native boolean isInterpreted();
-
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpreted(JNIEnv* env, jclass) {
+static jboolean IsInterpreted(JNIEnv* env, jclass, size_t level) {
ScopedObjectAccess soa(env);
- NthCallerVisitor caller(soa.Self(), 1, false);
+ NthCallerVisitor caller(soa.Self(), level, false);
caller.WalkStack();
CHECK(caller.caller != nullptr);
return caller.GetCurrentShadowFrame() != nullptr ? JNI_TRUE : JNI_FALSE;
}
+// public static native boolean isInterpreted();
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpreted(JNIEnv* env, jclass klass) {
+ return IsInterpreted(env, klass, 1);
+}
+
// public static native void assertIsInterpreted();
extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jclass klass) {
@@ -56,10 +59,7 @@
}
}
-
-// public static native boolean isManaged();
-
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_isManaged(JNIEnv* env, jclass cls) {
+static jboolean IsManaged(JNIEnv* env, jclass cls, size_t level) {
ScopedObjectAccess soa(env);
mirror::Class* klass = soa.Decode<mirror::Class*>(cls);
@@ -71,13 +71,19 @@
return JNI_FALSE;
}
- NthCallerVisitor caller(soa.Self(), 1, false);
+ NthCallerVisitor caller(soa.Self(), level, false);
caller.WalkStack();
CHECK(caller.caller != nullptr);
return caller.GetCurrentShadowFrame() != nullptr ? JNI_FALSE : JNI_TRUE;
}
+// public static native boolean isManaged();
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isManaged(JNIEnv* env, jclass cls) {
+ return IsManaged(env, cls, 1);
+}
+
// public static native void assertIsManaged();
extern "C" JNIEXPORT void JNICALL Java_Main_assertIsManaged(JNIEnv* env, jclass cls) {
@@ -86,4 +92,32 @@
}
}
+// public static native boolean isCallerInterpreted();
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerInterpreted(JNIEnv* env, jclass klass) {
+ return IsInterpreted(env, klass, 2);
+}
+
+// public static native void assertCallerIsInterpreted();
+
+extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsInterpreted(JNIEnv* env, jclass klass) {
+ if (asserts_enabled) {
+ CHECK(Java_Main_isCallerInterpreted(env, klass));
+ }
+}
+
+// public static native boolean isCallerManaged();
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerManaged(JNIEnv* env, jclass cls) {
+ return IsManaged(env, cls, 2);
+}
+
+// public static native void assertCallerIsManaged();
+
+extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsManaged(JNIEnv* env, jclass cls) {
+ if (asserts_enabled) {
+ CHECK(Java_Main_isCallerManaged(env, cls));
+ }
+}
+
} // namespace art