Don't fetch shorty when there are no listeners and  deopt isn't required

To keep the likely path of method exit hooks fast check for method exit
listeners before fetching the shorty or constructing the stack handle
scope.

This improves the performance by 2x on debuggable-cc config in golem
benchmarks

Bug: 253232638
Test: art/test.py
Change-Id: Iab740238d8a9f3ef550e5f56350f62841a5cf33f
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index a854ea0..ee339a2 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -2698,20 +2698,23 @@
   }
 
   DCHECK_EQ(reinterpret_cast<uintptr_t>(self), reinterpret_cast<uintptr_t>(Thread::Current()));
-  CHECK(gpr_result != nullptr);
-  CHECK(fpr_result != nullptr);
   // Instrumentation exit stub must not be entered with a pending exception.
   CHECK(!self->IsExceptionPending())
       << "Enter instrumentation exit stub with pending exception " << self->GetException()->Dump();
 
   instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
-  DCHECK(instr->AreExitStubsInstalled());
-  bool is_ref;
+  JValue return_value;
+  bool is_ref = false;
   ArtMethod* method = *sp;
-  JValue return_value = instr->GetReturnValue(method, &is_ref, gpr_result, fpr_result);
-  bool deoptimize = false;
-  {
+  bool deoptimize = instr->ShouldDeoptimizeCaller(self, sp, frame_size);
+  if (instr->HasMethodExitListeners()) {
     StackHandleScope<1> hs(self);
+
+    CHECK(gpr_result != nullptr);
+    CHECK(fpr_result != nullptr);
+    DCHECK(instr->AreExitStubsInstalled());
+
+    return_value = instr->GetReturnValue(method, &is_ref, gpr_result, fpr_result);
     MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr));
     if (is_ref) {
       // Take a handle to the return value so we won't lose it if we suspend.
@@ -2719,7 +2722,6 @@
     }
     DCHECK(!method->IsRuntimeMethod());
 
-    deoptimize = instr->ShouldDeoptimizeCaller(self, sp, frame_size);
     // If we need a deoptimization MethodExitEvent will be called by the interpreter when it
     // re-executes the return instruction. For native methods we have to process method exit
     // events here since deoptimization just removes the native frame.
@@ -2735,6 +2737,8 @@
       *reinterpret_cast<mirror::Object**>(gpr_result) = res.Get();
       return_value.SetL(res.Get());
     }
+  } else if (deoptimize) {
+    return_value = instr->GetReturnValue(method, &is_ref, gpr_result, fpr_result);
   }
 
   if (self->IsExceptionPending() || self->ObserveAsyncException()) {