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);
+}