ART: Fix infinite recursion for deopt at dex pc 0.
Previously, the interpreter checked for dex pc 0 to see if
the method was just entered. If we deopt at dex pc 0, the
instrumentation would emit an erroneous MethodEnteredEvent
and the JIT would have received a MethodEntered() call. For
JIT-on-first-use, the method would be compiled the same way
as before, leading to the same deopt until stack overflow.
We fix this by using a new `from_deoptimize` flag passed
by the caller.
Test: 680-checker-deopt-dex-pc-0
Test: testrunner.py --host \
--jit --runtime-option=-Xjitthreshold:0
Bug: 62611253
Change-Id: I50b88f15484aeae16e1375a1d80f6563fb9066e7
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index 5ff31ce..719904d 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -576,6 +576,11 @@
}
StartAttributeStream() << input_list;
}
+ if (instruction->GetDexPc() != kNoDexPc) {
+ StartAttributeStream("dex_pc") << instruction->GetDexPc();
+ } else {
+ StartAttributeStream("dex_pc") << "n/a";
+ }
instruction->Accept(this);
if (instruction->HasEnvironment()) {
StringList envs;
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 735c0e8..f23304c 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -243,11 +243,13 @@
const CodeItemDataAccessor& accessor,
ShadowFrame& shadow_frame,
JValue result_register,
- bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_) {
+ bool stay_in_interpreter = false,
+ bool from_deoptimize = false) REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(!shadow_frame.GetMethod()->IsAbstract());
DCHECK(!shadow_frame.GetMethod()->IsNative());
- if (LIKELY(shadow_frame.GetDexPC() == 0)) { // Entering the method, but not via deoptimization.
+ if (LIKELY(!from_deoptimize)) { // Entering the method, but not via deoptimization.
if (kIsDebugBuild) {
+ CHECK_EQ(shadow_frame.GetDexPC(), 0u);
self->AssertNoPendingException();
}
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
@@ -568,7 +570,12 @@
}
if (new_dex_pc != dex::kDexNoIndex) {
shadow_frame->SetDexPC(new_dex_pc);
- value = Execute(self, accessor, *shadow_frame, value);
+ value = Execute(self,
+ accessor,
+ *shadow_frame,
+ value,
+ /* stay_in_interpreter */ true,
+ /* from_deoptimize */ true);
}
ShadowFrame* old_frame = shadow_frame;
shadow_frame = shadow_frame->GetLink();
diff --git a/test/680-checker-deopt-dex-pc-0/expected.txt b/test/680-checker-deopt-dex-pc-0/expected.txt
new file mode 100644
index 0000000..805857d
--- /dev/null
+++ b/test/680-checker-deopt-dex-pc-0/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+passed
diff --git a/test/680-checker-deopt-dex-pc-0/info.txt b/test/680-checker-deopt-dex-pc-0/info.txt
new file mode 100644
index 0000000..8eae156
--- /dev/null
+++ b/test/680-checker-deopt-dex-pc-0/info.txt
@@ -0,0 +1,2 @@
+Regression test for deoptimization at dex pc 0 causing infinite recursion
+for JIT-at-first-use.
diff --git a/test/680-checker-deopt-dex-pc-0/src/Main.java b/test/680-checker-deopt-dex-pc-0/src/Main.java
new file mode 100644
index 0000000..d5a6a90
--- /dev/null
+++ b/test/680-checker-deopt-dex-pc-0/src/Main.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ // We run this test for AOT to verify that there is a HDeoptimize with dex pc 0.
+ /// CHECK-START: int Main.$noinline$getInt(byte[], int) BCE (after)
+ /// CHECK: Deoptimize dex_pc:0
+ public static int $noinline$getInt(byte[] array, int offset) {
+ // The aget for `array[offset]` is at dex pc 0, so the Deoptimize
+ // from dynamic BCE shall also be at dex pc 0.
+ return ((array[offset ] & 0xFF) << 0) +
+ ((array[offset + 1] & 0xFF) << 8) +
+ ((array[offset + 2] & 0xFF) << 16) +
+ ((array[offset + 3] & 0xFF) << 24);
+ }
+
+ public static void main(String[] args) {
+ System.loadLibrary(args[0]);
+ if (hasJit()) {
+ byte[] array = { 0, 1, 2, 3 };
+ while (!hasJitCompiledEntrypoint(Main.class, "$noinline$getInt")) {
+ for (int i = 0; i < 10000; ++i) {
+ if ($noinline$getInt(array, 0) != 0x03020100) {
+ throw new Error();
+ }
+ }
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {}
+ }
+ try {
+ // The HDeoptimize at dex pc 0 was previously handled poorly as the dex pc 0
+ // was used to detect whether we entered the method. This meant that the
+ // instrumentation would have reported MethodEnteredEvent and we would have
+ // told JIT that the method was entered. With JIT-on-first-use we would also
+ // immediatelly recompile the method and run the compiled code leading to
+ // a an infinite deoptimization recursion, yielding StackOverflowError.
+ $noinline$getInt(array, 1);
+ } catch (ArrayIndexOutOfBoundsException ignored) {}
+ }
+ System.out.println("passed");
+ }
+
+ public static native boolean hasJit();
+ public native static boolean hasJitCompiledEntrypoint(Class<?> cls, String methodName);
+}