Merge "Add indexOf and lastIndexOf to compile-time string computation"
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index ebaad34..1757459 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -138,6 +138,8 @@
public final DexString equalsMethodName = createString("equals");
public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
public final DexString contentEqualsMethodName = createString("contentEquals");
+ public final DexString indexOfMethodName = createString("indexOf");
+ public final DexString lastIndexOfMethodName = createString("lastIndexOf");
public final DexString cloneMethodName = createString("clone");
public final DexString valueOfMethodName = createString("valueOf");
@@ -607,6 +609,11 @@
public final DexMethod equalsIgnoreCase;
public final DexMethod contentEqualsCharSequence;
+ public final DexMethod indexOfInt;
+ public final DexMethod indexOfString;
+ public final DexMethod lastIndexOfInt;
+ public final DexMethod lastIndexOfString;
+
public final DexMethod valueOf;
public final DexMethod toString;
@@ -619,6 +626,7 @@
DexString[] needsOneCharSequence = { charSequenceDescriptor };
DexString[] needsOneString = { stringDescriptor };
DexString[] needsOneObject = { objectDescriptor };
+ DexString[] needsOneInt = { intDescriptor };
contains = createMethod(
stringDescriptor, containsMethodName, booleanDescriptor, needsOneCharSequence);
@@ -633,6 +641,15 @@
contentEqualsCharSequence = createMethod(
stringDescriptor, contentEqualsMethodName, booleanDescriptor, needsOneCharSequence);
+ indexOfString =
+ createMethod(stringDescriptor, indexOfMethodName, intDescriptor, needsOneString);
+ indexOfInt =
+ createMethod(stringDescriptor, indexOfMethodName, intDescriptor, needsOneInt);
+ lastIndexOfString =
+ createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneString);
+ lastIndexOfInt =
+ createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneInt);
+
valueOf = createMethod(
stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
toString = createMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index fd326d9..3fc442a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -59,6 +59,10 @@
// boolean String#equals(str)
// boolean String#equalsIgnoreCase(str)
// boolean String#contentEquals(str)
+ // int String#indexOf(str)
+ // int String#indexOf(int)
+ // int String#lastIndexOf(str)
+ // int String#lastIndexOf(int)
public void computeTrivialOperationsOnConstString(IRCode code) {
if (!code.hasConstString) {
return;
@@ -73,6 +77,7 @@
DexMethod invokedMethod = invoke.getInvokedMethod();
Function<String, Integer> operatorWithNoArg = null;
BiFunction<String, String, Integer> operatorWithString = null;
+ BiFunction<String, Integer, Integer> operatorWithInt = null;
if (invokedMethod == factory.stringMethods.length) {
operatorWithNoArg = String::length;
} else if (invokedMethod == factory.stringMethods.isEmpty) {
@@ -89,8 +94,15 @@
operatorWithString = (rcv, arg) -> rcv.equalsIgnoreCase(arg) ? 1 : 0;
} else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
operatorWithString = (rcv, arg) -> rcv.contentEquals(arg) ? 1 : 0;
- }
- if (operatorWithNoArg == null && operatorWithString == null) {
+ } else if (invokedMethod == factory.stringMethods.indexOfInt) {
+ operatorWithInt = String::indexOf;
+ } else if (invokedMethod == factory.stringMethods.indexOfString) {
+ operatorWithString = String::indexOf;
+ } else if (invokedMethod == factory.stringMethods.lastIndexOfInt) {
+ operatorWithInt = String::lastIndexOf;
+ } else if (invokedMethod == factory.stringMethods.lastIndexOfString) {
+ operatorWithString = String::lastIndexOf;
+ } else {
continue;
}
Value rcv = invoke.getReceiver().getAliasedValue();
@@ -99,14 +111,14 @@
|| !rcv.isConstant()) {
continue;
}
+
+ ConstNumber constNumber;
if (operatorWithNoArg != null) {
assert invoke.inValues().size() == 1;
ConstString rcvString = rcv.definition.asConstString();
int v = operatorWithNoArg.apply(rcvString.getValue().toString());
- ConstNumber constNumber = code.createIntConstant(v);
- it.replaceCurrentInstruction(constNumber);
- } else {
- assert operatorWithString != null;
+ constNumber = code.createIntConstant(v);
+ } else if (operatorWithString != null) {
assert invoke.inValues().size() == 2;
Value arg = invoke.inValues().get(1).getAliasedValue();
if (arg.definition == null
@@ -118,9 +130,24 @@
ConstString argString = arg.definition.asConstString();
int v = operatorWithString.apply(
rcvString.getValue().toString(), argString.getValue().toString());
- ConstNumber constNumber = code.createIntConstant(v);
- it.replaceCurrentInstruction(constNumber);
+ constNumber = code.createIntConstant(v);
+ } else {
+ assert operatorWithInt != null;
+ assert invoke.inValues().size() == 2;
+ Value arg = invoke.inValues().get(1).getAliasedValue();
+ if (arg.definition == null
+ || !arg.definition.isConstNumber()
+ || !arg.isConstant()) {
+ continue;
+ }
+ ConstString rcvString = rcv.definition.asConstString();
+ ConstNumber argInt = arg.definition.asConstNumber();
+ int v = operatorWithInt.apply(
+ rcvString.getValue().toString(), argInt.getIntValue());
+ constNumber = code.createIntConstant(v);
}
+
+ it.replaceCurrentInstruction(constNumber);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index 284af11..cd3a131 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -33,7 +33,9 @@
&& "suffix".endsWith(arg)
&& "CONST".equals(arg)
&& "CONST".equalsIgnoreCase(arg)
- && "CONST".contentEquals(arg);
+ && "CONST".contentEquals(arg)
+ && "CONST".indexOf(arg) > 0
+ && "CONST".lastIndexOf(arg) > 0;
}
public static void main(String[] args) {
@@ -46,6 +48,10 @@
System.out.println(s1.equalsIgnoreCase("PREFIX-const-SUFFIX"));
System.out.println(s1.contentEquals("prefix-CONST-suffix"));
System.out.println(s1.contentEquals(new StringBuffer("prefix-CONST-suffix")));
+ System.out.println(s1.indexOf('f'));
+ System.out.println(s1.indexOf("ix"));
+ System.out.println(s1.lastIndexOf('f'));
+ System.out.println(s1.lastIndexOf("ix"));
}
{
@@ -58,6 +64,10 @@
System.out.println(s2.equalsIgnoreCase("pre-con-suf"));
System.out.println(s2.contentEquals("prefix-CONST-suffix"));
System.out.println(s2.contentEquals(new StringBuffer("prefix-CONST-suffix")));
+ System.out.println(s2.indexOf('f'));
+ System.out.println(s2.indexOf("ix"));
+ System.out.println(s2.lastIndexOf('f'));
+ System.out.println(s2.lastIndexOf("ix"));
}
{
@@ -94,6 +104,14 @@
"true",
// s1, contentEquals(StringBuffer)
"true",
+ // s1, indexOf(int)
+ "3",
+ // s1, indexOf(String)
+ "4",
+ // s1, lastIndexOf(int)
+ "16",
+ // s1, lastIndexOf(String)
+ "17",
// s2, contains
"false",
// s2, startsWith
@@ -108,6 +126,14 @@
"false",
// s2, contentEquals(StringBuffer)
"false",
+ // s2, indexOf(int)
+ "-1",
+ // s2, indexOf(String)
+ "-1",
+ // s2, lastIndexOf(int)
+ "-1",
+ // s2, lastIndexOf(String)
+ "-1",
// argCouldBeNull
"false"
);
@@ -131,13 +157,16 @@
private static boolean isStringContentChecker(DexMethod method) {
return method.getHolder().toDescriptorString().equals("Ljava/lang/String;")
&& method.getArity() == 1
- && method.proto.returnType.isBooleanType()
+ && (method.proto.returnType.isBooleanType()
+ || method.proto.returnType.isIntType())
&& (method.name.toString().equals("contains")
|| method.name.toString().equals("startsWith")
|| method.name.toString().equals("endsWith")
|| method.name.toString().equals("equals")
|| method.name.toString().equals("equalsIgnoreCase")
- || method.name.toString().equals("contentEquals"));
+ || method.name.toString().equals("contentEquals")
+ || method.name.toString().equals("indexOf")
+ || method.name.toString().equals("lastIndexOf"));
}
private long countStringContentChecker(MethodSubject method) {
@@ -161,7 +190,7 @@
"boolean", "argCouldBeNull", ImmutableList.of("java.lang.String"));
assertThat(argCouldBeNull, isPresent());
// Because of nullable argument, all checkers should remain.
- assertEquals(6, countStringContentChecker(argCouldBeNull));
+ assertEquals(8, countStringContentChecker(argCouldBeNull));
}
@Test
@@ -173,14 +202,14 @@
.addProgramClasses(CLASSES)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 14);
+ test(result, 22);
result = testForD8()
.release()
.addProgramClasses(CLASSES)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 8);
+ test(result, 12);
}
@Test
@@ -192,6 +221,6 @@
.addKeepMainRule(MAIN)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 8);
+ test(result, 12);
}
}