Apply String.equals() optimizations on arm, arm64 and x86-64.
This is a follow-up to
https://android-review.googlesource.com/174192
Change-Id: Ie71197df22548d6eb0ca773de6f19fcbb975f065
diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc
index de04175..5e5e973 100644
--- a/compiler/optimizing/intrinsics_arm.cc
+++ b/compiler/optimizing/intrinsics_arm.cc
@@ -1055,17 +1055,22 @@
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
- // Check if input is null, return false if it is.
- __ CompareAndBranchIfZero(arg, &return_false);
+ StringEqualsOptimizations optimizations(invoke);
+ if (!optimizations.GetArgumentNotNull()) {
+ // Check if input is null, return false if it is.
+ __ CompareAndBranchIfZero(arg, &return_false);
+ }
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
- __ ldr(temp, Address(str, class_offset));
- __ ldr(temp1, Address(arg, class_offset));
- __ cmp(temp, ShifterOperand(temp1));
- __ b(&return_false, NE);
+ if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
+ __ ldr(temp, Address(str, class_offset));
+ __ ldr(temp1, Address(arg, class_offset));
+ __ cmp(temp, ShifterOperand(temp1));
+ __ b(&return_false, NE);
+ }
// Load lengths of this and argument strings.
__ ldr(temp, Address(str, count_offset));
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index 6cd1726..30ab111 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -1327,21 +1327,26 @@
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
- // Check if input is null, return false if it is.
- __ Cbz(arg, &return_false);
+ StringEqualsOptimizations optimizations(invoke);
+ if (!optimizations.GetArgumentNotNull()) {
+ // Check if input is null, return false if it is.
+ __ Cbz(arg, &return_false);
+ }
// Reference equality check, return true if same reference.
__ Cmp(str, arg);
__ B(&return_true, eq);
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
- __ Ldr(temp, MemOperand(str.X(), class_offset));
- __ Ldr(temp1, MemOperand(arg.X(), class_offset));
- __ Cmp(temp, temp1);
- __ B(&return_false, ne);
+ if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
+ __ Ldr(temp, MemOperand(str.X(), class_offset));
+ __ Ldr(temp1, MemOperand(arg.X(), class_offset));
+ __ Cmp(temp, temp1);
+ __ B(&return_false, ne);
+ }
// Load lengths of this and argument strings.
__ Ldr(temp, MemOperand(str.X(), count_offset));
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 99bc40e..e530b90 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -1319,11 +1319,11 @@
__ j(kEqual, &return_false);
}
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
__ movl(ecx, Address(str, class_offset));
__ cmpl(ecx, Address(arg, class_offset));
__ j(kNotEqual, &return_false);
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index 06e9cc2..2b626be 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -1416,17 +1416,22 @@
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
- // Check if input is null, return false if it is.
- __ testl(arg, arg);
- __ j(kEqual, &return_false);
+ StringEqualsOptimizations optimizations(invoke);
+ if (!optimizations.GetArgumentNotNull()) {
+ // Check if input is null, return false if it is.
+ __ testl(arg, arg);
+ __ j(kEqual, &return_false);
+ }
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
- __ movl(rcx, Address(str, class_offset));
- __ cmpl(rcx, Address(arg, class_offset));
- __ j(kNotEqual, &return_false);
+ if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
+ __ movl(rcx, Address(str, class_offset));
+ __ cmpl(rcx, Address(arg, class_offset));
+ __ j(kNotEqual, &return_false);
+ }
// Reference equality check, return true if same reference.
__ cmpl(str, arg);
diff --git a/test/536-checker-intrinsic-optimization/src/Main.java b/test/536-checker-intrinsic-optimization/src/Main.java
index 15a9504..24ed2fe 100644
--- a/test/536-checker-intrinsic-optimization/src/Main.java
+++ b/test/536-checker-intrinsic-optimization/src/Main.java
@@ -107,8 +107,28 @@
}
/// CHECK-START-X86: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
- /// CHECK: InvokeVirtual {{.*\.equals.*}}
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
/// CHECK-NOT: test
+
+ /// CHECK-START-X86_64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
+ /// CHECK-NOT: test
+
+ /// CHECK-START-ARM: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
+ // CompareAndBranchIfZero() may emit either CBZ or CMP+BEQ.
+ /// CHECK-NOT: cbz
+ /// CHECK-NOT: cmp {{r\d+}}, #0
+ // Terminate the scope for the CHECK-NOT search at the reference or length comparison,
+ // whichever comes first.
+ /// CHECK: cmp {{r\d+}}, {{r\d+}}
+
+ /// CHECK-START-ARM64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
+ /// CHECK-NOT: cbz
+ // Terminate the scope for the CHECK-NOT search at the reference or length comparison,
+ // whichever comes first.
+ /// CHECK: cmp {{w.*,}} {{w.*}}
public static boolean stringArgumentNotNull(Object obj) {
obj.getClass();
return "foo".equals(obj);
@@ -116,12 +136,53 @@
// Test is very brittle as it depends on the order we emit instructions.
/// CHECK-START-X86: boolean Main.stringArgumentIsString() disassembly (after)
- /// CHECK: InvokeVirtual
- /// CHECK: test
- /// CHECK: jz/eq
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: test
+ /// CHECK: jz/eq
// Check that we don't try to compare the classes.
- /// CHECK-NOT: mov
- /// CHECK: cmp
+ /// CHECK-NOT: mov
+ /// CHECK: cmp
+
+ // Test is very brittle as it depends on the order we emit instructions.
+ /// CHECK-START-X86_64: boolean Main.stringArgumentIsString() disassembly (after)
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: test
+ /// CHECK: jz/eq
+ // Check that we don't try to compare the classes.
+ /// CHECK-NOT: mov
+ /// CHECK: cmp
+
+ // Test is brittle as it depends on the class offset being 0.
+ /// CHECK-START-ARM: boolean Main.stringArgumentIsString() disassembly (after)
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: {{cbz|cmp}}
+ // Check that we don't try to compare the classes.
+ // The dissassembler currently explicitly emits the offset 0 but don't rely on it.
+ // We want to terminate the CHECK-NOT search after two CMPs, one for reference
+ // equality and one for length comparison but these may be emitted in different order,
+ // so repeat the check twice.
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}]
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0]
+ /// CHECK: cmp {{r\d+}}, {{r\d+}}
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}]
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0]
+ /// CHECK: cmp {{r\d+}}, {{r\d+}}
+
+ // Test is brittle as it depends on the class offset being 0.
+ /// CHECK-START-ARM64: boolean Main.stringArgumentIsString() disassembly (after)
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: cbz
+ // Check that we don't try to compare the classes.
+ // The dissassembler currently does not explicitly emits the offset 0 but don't rely on it.
+ // We want to terminate the CHECK-NOT search after two CMPs, one for reference
+ // equality and one for length comparison but these may be emitted in different order,
+ // so repeat the check twice.
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}]
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0]
+ /// CHECK: cmp {{w\d+}}, {{w\d+}}
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}]
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0]
+ /// CHECK: cmp {{w\d+}}, {{w\d+}}
public static boolean stringArgumentIsString() {
return "foo".equals(myString);
}