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