Simplify const-string.indexOf().

Simplify String.indexOf() called on empty or single-char
constant string. We see these patterns in inlined
    java.net.URI$Parser.scan(int, int, String, String)
called with constant strings from other URI$Parser methods.
The empty string simplification allows constant folding
and DCE to remove some of the code. The single-character
string simplification avoids entrypoint call overhead.

Test: New tests in 458-checker-instruct-simplification
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Change-Id: I4d3c997a8d6220202d481bbf8cbf280832c27cd7
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index d532eee..c979a5a 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -117,6 +117,7 @@
   void SimplifyFP2Int(HInvoke* invoke);
   void SimplifyStringCharAt(HInvoke* invoke);
   void SimplifyStringIsEmptyOrLength(HInvoke* invoke);
+  void SimplifyStringIndexOf(HInvoke* invoke);
   void SimplifyNPEOnArgN(HInvoke* invoke, size_t);
   void SimplifyReturnThis(HInvoke* invoke);
   void SimplifyAllocationIntrinsic(HInvoke* invoke);
@@ -2417,6 +2418,43 @@
   invoke->GetBlock()->ReplaceAndRemoveInstructionWith(invoke, replacement);
 }
 
+void InstructionSimplifierVisitor::SimplifyStringIndexOf(HInvoke* invoke) {
+  DCHECK(invoke->GetIntrinsic() == Intrinsics::kStringIndexOf ||
+         invoke->GetIntrinsic() == Intrinsics::kStringIndexOfAfter);
+  if (invoke->InputAt(0)->IsLoadString()) {
+    HLoadString* load_string = invoke->InputAt(0)->AsLoadString();
+    const DexFile& dex_file = load_string->GetDexFile();
+    uint32_t utf16_length;
+    const char* data =
+        dex_file.StringDataAndUtf16LengthByIdx(load_string->GetStringIndex(), &utf16_length);
+    if (utf16_length == 0) {
+      invoke->ReplaceWith(GetGraph()->GetIntConstant(-1));
+      invoke->GetBlock()->RemoveInstruction(invoke);
+      RecordSimplification();
+      return;
+    }
+    if (utf16_length == 1 && invoke->GetIntrinsic() == Intrinsics::kStringIndexOf) {
+      // Simplify to HSelect(HEquals(., load_string.charAt(0)), 0, -1).
+      // If the sought character is supplementary, this gives the correct result, i.e. -1.
+      uint32_t c = GetUtf16FromUtf8(&data);
+      DCHECK_EQ(GetTrailingUtf16Char(c), 0u);
+      DCHECK_EQ(GetLeadingUtf16Char(c), c);
+      uint32_t dex_pc = invoke->GetDexPc();
+      ArenaAllocator* allocator = GetGraph()->GetAllocator();
+      HEqual* equal =
+          new (allocator) HEqual(invoke->InputAt(1), GetGraph()->GetIntConstant(c), dex_pc);
+      invoke->GetBlock()->InsertInstructionBefore(equal, invoke);
+      HSelect* result = new (allocator) HSelect(equal,
+                                                GetGraph()->GetIntConstant(0),
+                                                GetGraph()->GetIntConstant(-1),
+                                                dex_pc);
+      invoke->GetBlock()->ReplaceAndRemoveInstructionWith(invoke, result);
+      RecordSimplification();
+      return;
+    }
+  }
+}
+
 // This method should only be used on intrinsics whose sole way of throwing an
 // exception is raising a NPE when the nth argument is null. If that argument
 // is provably non-null, we can clear the flag.
@@ -2554,6 +2592,10 @@
     case Intrinsics::kStringLength:
       SimplifyStringIsEmptyOrLength(instruction);
       break;
+    case Intrinsics::kStringIndexOf:
+    case Intrinsics::kStringIndexOfAfter:
+      SimplifyStringIndexOf(instruction);
+      break;
     case Intrinsics::kStringStringIndexOf:
     case Intrinsics::kStringStringIndexOfAfter:
       SimplifyNPEOnArgN(instruction, 1);  // 0th has own NullCheck
diff --git a/test/458-checker-instruct-simplification/src/Main.java b/test/458-checker-instruct-simplification/src/Main.java
index 40e3778..9e714f5 100644
--- a/test/458-checker-instruct-simplification/src/Main.java
+++ b/test/458-checker-instruct-simplification/src/Main.java
@@ -2458,6 +2458,77 @@
     return (byte)((int)(((long)(b & 0xff)) & 255L));
   }
 
+  /// CHECK-START: int Main.$noinline$emptyStringIndexOf(int) instruction_simplifier (before)
+  /// CHECK-DAG:      <<Arg:i\d+>>      ParameterValue
+  /// CHECK-DAG:      <<Empty:l\d+>>    LoadString
+  /// CHECK-DAG:      <<Equals:i\d+>>   InvokeVirtual [<<Empty>>,<<Arg>>] intrinsic:StringIndexOf
+  /// CHECK-DAG:                        Return [<<Equals>>]
+
+  /// CHECK-START: int Main.$noinline$emptyStringIndexOf(int) instruction_simplifier (after)
+  /// CHECK-NOT:                        InvokeVirtual
+
+  /// CHECK-START: int Main.$noinline$emptyStringIndexOf(int) instruction_simplifier (after)
+  /// CHECK-DAG:      <<Minus1:i\d+>>   IntConstant -1
+  /// CHECK-DAG:                        Return [<<Minus1>>]
+  public static int $noinline$emptyStringIndexOf(int ch) {
+    return "".indexOf(ch);
+  }
+
+  /// CHECK-START: int Main.$noinline$emptyStringIndexOfAfter(int, int) instruction_simplifier (before)
+  /// CHECK-DAG:      <<Arg1:i\d+>>     ParameterValue
+  /// CHECK-DAG:      <<Arg2:i\d+>>     ParameterValue
+  /// CHECK-DAG:      <<Empty:l\d+>>    LoadString
+  /// CHECK-DAG:      <<Equals:i\d+>>   InvokeVirtual [<<Empty>>,<<Arg1>>,<<Arg2>>] intrinsic:StringIndexOfAfter
+  /// CHECK-DAG:                        Return [<<Equals>>]
+
+  /// CHECK-START: int Main.$noinline$emptyStringIndexOfAfter(int, int) instruction_simplifier (after)
+  /// CHECK-NOT:                        InvokeVirtual
+
+  /// CHECK-START: int Main.$noinline$emptyStringIndexOfAfter(int, int) instruction_simplifier (after)
+  /// CHECK-DAG:      <<Minus1:i\d+>>   IntConstant -1
+  /// CHECK-DAG:                        Return [<<Minus1>>]
+  public static int $noinline$emptyStringIndexOfAfter(int ch, int fromIndex) {
+    return "".indexOf(ch, fromIndex);
+  }
+
+  /// CHECK-START: int Main.$noinline$singleCharStringIndexOf(int) instruction_simplifier (before)
+  /// CHECK-DAG:      <<Arg:i\d+>>      ParameterValue
+  /// CHECK-DAG:      <<Empty:l\d+>>    LoadString
+  /// CHECK-DAG:      <<Equals:i\d+>>   InvokeVirtual [<<Empty>>,<<Arg>>] intrinsic:StringIndexOf
+  /// CHECK-DAG:                        Return [<<Equals>>]
+
+  /// CHECK-START: int Main.$noinline$singleCharStringIndexOf(int) instruction_simplifier (after)
+  /// CHECK-NOT:                        InvokeVirtual
+
+  /// CHECK-START: int Main.$noinline$singleCharStringIndexOf(int) instruction_simplifier (after)
+  /// CHECK-DAG:      <<Arg:i\d+>>      ParameterValue
+  /// CHECK-DAG:      <<x:i\d+>>        IntConstant 120
+  /// CHECK-DAG:      <<Zero:i\d+>>     IntConstant 0
+  /// CHECK-DAG:      <<Minus1:i\d+>>   IntConstant -1
+  /// CHECK-DAG:      <<Eq:z\d+>>       Equal [<<Arg>>,<<x>>]
+  /// CHECK-DAG:      <<Select:i\d+>>   Select [<<Minus1>>,<<Zero>>,<<Eq>>]
+  /// CHECK-DAG:                        Return [<<Select>>]
+  public static int $noinline$singleCharStringIndexOf(int ch) {
+    return "x".indexOf(ch);
+  }
+
+  /// CHECK-START: int Main.$noinline$singleCharStringIndexOfAfter(int, int) instruction_simplifier (before)
+  /// CHECK-DAG:      <<Arg1:i\d+>>     ParameterValue
+  /// CHECK-DAG:      <<Arg2:i\d+>>     ParameterValue
+  /// CHECK-DAG:      <<Empty:l\d+>>    LoadString
+  /// CHECK-DAG:      <<Equals:i\d+>>   InvokeVirtual [<<Empty>>,<<Arg1>>,<<Arg2>>] intrinsic:StringIndexOfAfter
+  /// CHECK-DAG:                        Return [<<Equals>>]
+
+  /// CHECK-START: int Main.$noinline$singleCharStringIndexOfAfter(int, int) instruction_simplifier (after)
+  /// CHECK-DAG:      <<Arg1:i\d+>>     ParameterValue
+  /// CHECK-DAG:      <<Arg2:i\d+>>     ParameterValue
+  /// CHECK-DAG:      <<Empty:l\d+>>    LoadString
+  /// CHECK-DAG:      <<Equals:i\d+>>   InvokeVirtual [<<Empty>>,<<Arg1>>,<<Arg2>>] intrinsic:StringIndexOfAfter
+  /// CHECK-DAG:                        Return [<<Equals>>]
+  public static int $noinline$singleCharStringIndexOfAfter(int ch, int fromIndex) {
+    return "x".indexOf(ch, fromIndex);  // Not simplified.
+  }
+
   public static void main(String[] args) throws Exception {
     Class smaliTests2 = Class.forName("SmaliTests2");
     Method $noinline$XorAllOnes = smaliTests2.getMethod("$noinline$XorAllOnes", int.class);
@@ -2709,6 +2780,19 @@
     assertIntEquals(1, (int)$noinline$bug68142795Boolean.invoke(null, true));
     assertIntEquals(0x7f, $noinline$bug68142795Elaborate((byte) 0x7f));
     assertIntEquals((byte) 0x80, $noinline$bug68142795Elaborate((byte) 0x80));
+
+    assertIntEquals(-1, $noinline$emptyStringIndexOf('a'));
+    assertIntEquals(-1, $noinline$emptyStringIndexOf('Z'));
+    assertIntEquals(-1, $noinline$emptyStringIndexOfAfter('a', 0));
+    assertIntEquals(-1, $noinline$emptyStringIndexOfAfter('Z', -1));
+
+    assertIntEquals(-1, $noinline$singleCharStringIndexOf('a'));
+    assertIntEquals(0, $noinline$singleCharStringIndexOf('x'));
+    assertIntEquals(-1, $noinline$singleCharStringIndexOf('Z'));
+    assertIntEquals(-1, $noinline$singleCharStringIndexOfAfter('a', 0));
+    assertIntEquals(0, $noinline$singleCharStringIndexOfAfter('x', -1));
+    assertIntEquals(-1, $noinline$singleCharStringIndexOfAfter('x', 1));
+    assertIntEquals(-1, $noinline$singleCharStringIndexOfAfter('Z', -1));
   }
 
   private static boolean $inline$true() { return true; }