/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.lang.reflect.Method; /** * Tests properties of some string operations represented by intrinsics. */ public class Main { static final String ABC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static final String XYZ = "XYZ"; // // Variant intrinsics remain in the loop, but invariant references are hoisted out of the loop. // /// CHECK-START: int Main.liveIndexOf() licm (before) /// CHECK-DAG: InvokeVirtual intrinsic:StringIndexOf loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringIndexOfAfter loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOf loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOfAfter loop:{{B\d+}} outer_loop:none // /// CHECK-START: int Main.liveIndexOf() licm (after) /// CHECK-DAG: InvokeVirtual intrinsic:StringIndexOf loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringIndexOfAfter loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOf loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOfAfter loop:none static int liveIndexOf() { int k = ABC.length() + XYZ.length(); // does LoadString before loops for (char c = 'A'; c <= 'Z'; c++) { k += ABC.indexOf(c); } for (char c = 'A'; c <= 'Z'; c++) { k += ABC.indexOf(c, 4); } for (char c = 'A'; c <= 'Z'; c++) { k += ABC.indexOf(XYZ); } for (char c = 'A'; c <= 'Z'; c++) { k += ABC.indexOf(XYZ, 2); } return k; } // // All dead intrinsics can be removed completely. // /// CHECK-START: int Main.deadIndexOf() dead_code_elimination$initial (before) /// CHECK-DAG: InvokeVirtual intrinsic:StringIndexOf loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringIndexOfAfter loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOf loop:{{B\d+}} outer_loop:none /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOfAfter loop:{{B\d+}} outer_loop:none // /// CHECK-START: int Main.deadIndexOf() dead_code_elimination$initial (after) /// CHECK-NOT: InvokeVirtual intrinsic:StringIndexOf /// CHECK-NOT: InvokeVirtual intrinsic:StringIndexOfAfter /// CHECK-NOT: InvokeVirtual intrinsic:StringStringIndexOf /// CHECK-NOT: InvokeVirtual intrinsic:StringStringIndexOfAfter static int deadIndexOf() { int k = ABC.length() + XYZ.length(); // does LoadString before loops for (char c = 'A'; c <= 'Z'; c++) { int d = ABC.indexOf(c); } for (char c = 'A'; c <= 'Z'; c++) { int d = ABC.indexOf(c, 4); } for (char c = 'A'; c <= 'Z'; c++) { int d = ABC.indexOf(XYZ); } for (char c = 'A'; c <= 'Z'; c++) { int d = ABC.indexOf(XYZ, 2); } return k; } // // Explicit null check on receiver, implicit null check on argument prevents hoisting. // /// CHECK-START: int Main.indexOfExceptions(java.lang.String, java.lang.String) licm (after) /// CHECK-DAG: <<String:l\d+>> NullCheck loop:<<Loop:B\d+>> outer_loop:none /// CHECK-DAG: InvokeVirtual [<<String>>,{{l\d+}}] intrinsic:StringStringIndexOf loop:<<Loop>> outer_loop:none static int indexOfExceptions(String s, String t) { int k = 0; for (char c = 'A'; c <= 'Z'; c++) { k += s.indexOf(t); } return k; } // // Allows combining of returned "this". Also ensures that similar looking append() calls // are not combined somehow through returned result. // /// CHECK-START: int Main.bufferLen2() instruction_simplifier (before) /// CHECK-DAG: <<New:l\d+>> NewInstance /// CHECK-DAG: <<String1:l\d+>> LoadString /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<New>>,<<String1>>] intrinsic:StringBufferAppend /// CHECK-DAG: <<String2:l\d+>> LoadString /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [{{l\d+}},<<String2>>] intrinsic:StringBufferAppend /// CHECK-DAG: InvokeVirtual [{{l\d+}}] intrinsic:StringBufferLength // /// CHECK-START: int Main.bufferLen2() instruction_simplifier (after) /// CHECK-DAG: <<New:l\d+>> NewInstance /// CHECK-DAG: <<String1:l\d+>> LoadString /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<New>>,<<String1>>] intrinsic:StringBufferAppend /// CHECK-DAG: <<String2:l\d+>> LoadString /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [<<New>>,<<String2>>] intrinsic:StringBufferAppend /// CHECK-DAG: InvokeVirtual [<<New>>] intrinsic:StringBufferLength static int bufferLen2() { StringBuffer s = new StringBuffer(); return s.append("x").append("y").length(); } static int bufferLen2Smali() throws Exception { Class<?> c = Class.forName("Smali"); Method m = c.getMethod("bufferLen2"); return (Integer) m.invoke(null); } // // Allows combining of returned "this". Also ensures that similar looking append() calls // are not combined somehow through returned result. // /// CHECK-START: int Main.builderLen2() instruction_simplifier (before) /// CHECK-DAG: <<New:l\d+>> NewInstance /// CHECK-DAG: <<String1:l\d+>> LoadString /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<New>>,<<String1>>] intrinsic:StringBuilderAppend /// CHECK-DAG: <<String2:l\d+>> LoadString /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [{{l\d+}},<<String2>>] intrinsic:StringBuilderAppend /// CHECK-DAG: InvokeVirtual [{{l\d+}}] intrinsic:StringBuilderLength // /// CHECK-START: int Main.builderLen2() instruction_simplifier (after) /// CHECK-DAG: <<New:l\d+>> NewInstance /// CHECK-DAG: <<String1:l\d+>> LoadString /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<New>>,<<String1>>] intrinsic:StringBuilderAppend /// CHECK-DAG: <<String2:l\d+>> LoadString /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [<<New>>,<<String2>>] intrinsic:StringBuilderAppend /// CHECK-DAG: InvokeVirtual [<<New>>] intrinsic:StringBuilderLength static int builderLen2() { StringBuilder s = new StringBuilder(); return s.append("x").append("y").length(); } static int builderLen2Smali() throws Exception { Class<?> c = Class.forName("Smali"); Method m = c.getMethod("builderLen2"); return (Integer) m.invoke(null); } // // Similar situation in a loop. // /// CHECK-START: int Main.bufferLoopAppender() instruction_simplifier (before) /// CHECK-DAG: <<New:l\d+>> NewInstance loop:none /// CHECK-DAG: <<String1:l\d+>> LoadString loop:<<Loop:B\d+>> /// CHECK-DAG: <<Null1:l\d+>> NullCheck [<<New>>] loop:<<Loop>> /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<Null1>>,<<String1>>] intrinsic:StringBufferAppend loop:<<Loop>> /// CHECK-DAG: <<String2:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [{{l\d+}},<<String2>>] intrinsic:StringBufferAppend loop:<<Loop>> /// CHECK-DAG: <<String3:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append3:l\d+>> InvokeVirtual [{{l\d+}},<<String3>>] intrinsic:StringBufferAppend loop:<<Loop>> /// CHECK-DAG: InvokeVirtual [{{l\d+}}] intrinsic:StringBufferLength loop:none // /// CHECK-START: int Main.bufferLoopAppender() instruction_simplifier (after) /// CHECK-DAG: <<New:l\d+>> NewInstance loop:none /// CHECK-DAG: <<String1:l\d+>> LoadString loop:<<Loop:B\d+>> /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<New>>,<<String1>>] intrinsic:StringBufferAppend loop:<<Loop>> /// CHECK-DAG: <<String2:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [<<New>>,<<String2>>] intrinsic:StringBufferAppend loop:<<Loop>> /// CHECK-DAG: <<String3:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append3:l\d+>> InvokeVirtual [<<New>>,<<String3>>] intrinsic:StringBufferAppend loop:<<Loop>> /// CHECK-DAG: InvokeVirtual [<<New>>] intrinsic:StringBufferLength loop:none static int bufferLoopAppender() { StringBuffer b = new StringBuffer(); for (int i = 0; i < 10; i++) { b.append("x").append("y").append("z"); } return b.length(); } static int bufferLoopAppenderSmali() throws Exception { Class<?> c = Class.forName("Smali"); Method m = c.getMethod("bufferLoopAppender"); return (Integer) m.invoke(null); } // // Similar situation in a loop. // /// CHECK-START: int Main.builderLoopAppender() instruction_simplifier (before) /// CHECK-DAG: <<New:l\d+>> NewInstance loop:none /// CHECK-DAG: <<String1:l\d+>> LoadString loop:<<Loop:B\d+>> /// CHECK-DAG: <<Null1:l\d+>> NullCheck [<<New>>] loop:<<Loop>> /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<Null1>>,<<String1>>] intrinsic:StringBuilderAppend loop:<<Loop>> /// CHECK-DAG: <<String2:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [{{l\d+}},<<String2>>] intrinsic:StringBuilderAppend loop:<<Loop>> /// CHECK-DAG: <<String3:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append3:l\d+>> InvokeVirtual [{{l\d+}},<<String3>>] intrinsic:StringBuilderAppend loop:<<Loop>> /// CHECK-DAG: InvokeVirtual [{{l\d+}}] intrinsic:StringBuilderLength loop:none // /// CHECK-START: int Main.builderLoopAppender() instruction_simplifier (after) /// CHECK-DAG: <<New:l\d+>> NewInstance loop:none /// CHECK-DAG: <<String1:l\d+>> LoadString loop:<<Loop:B\d+>> /// CHECK-DAG: <<Append1:l\d+>> InvokeVirtual [<<New>>,<<String1>>] intrinsic:StringBuilderAppend loop:<<Loop>> /// CHECK-DAG: <<String2:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append2:l\d+>> InvokeVirtual [<<New>>,<<String2>>] intrinsic:StringBuilderAppend loop:<<Loop>> /// CHECK-DAG: <<String3:l\d+>> LoadString loop:<<Loop>> /// CHECK-DAG: <<Append3:l\d+>> InvokeVirtual [<<New>>,<<String3>>] intrinsic:StringBuilderAppend loop:<<Loop>> /// CHECK-DAG: InvokeVirtual [<<New>>] intrinsic:StringBuilderLength loop:none static int builderLoopAppender() { StringBuilder b = new StringBuilder(); for (int i = 0; i < 10; i++) { b.append("x").append("y").append("z"); } return b.length(); } static int builderLoopAppenderSmali() throws Exception { Class<?> c = Class.forName("Smali"); Method m = c.getMethod("bufferLoopAppender"); return (Integer) m.invoke(null); } // // All calls in the loop-body and thus loop can be eliminated. // /// CHECK-START: int Main.bufferDeadLoop() instruction_simplifier (before) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> /// CHECK-DAG: InvokeVirtual intrinsic:StringBufferToString loop:<<Loop>> /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOfAfter loop:<<Loop>> // /// CHECK-START: int Main.bufferDeadLoop() loop_optimization (after) /// CHECK-NOT: Phi /// CHECK-NOT: InvokeVirtual intrinsic:StringBufferToString /// CHECK-NOT: InvokeVirtual intrinsic:StringStringIndexOfAfter static int bufferDeadLoop() { StringBuffer b = new StringBuffer(); String x = "x"; for (int i = 0; i < 10; i++) { int d = b.toString().indexOf(x, 1); } return b.length(); } // // All calls in the loop-body and thus loop can be eliminated. // /// CHECK-START: int Main.builderDeadLoop() instruction_simplifier (before) /// CHECK-DAG: Phi loop:<<Loop:B\d+>> /// CHECK-DAG: InvokeVirtual intrinsic:StringBuilderToString loop:<<Loop>> /// CHECK-DAG: InvokeVirtual intrinsic:StringStringIndexOfAfter loop:<<Loop>> // /// CHECK-START: int Main.builderDeadLoop() loop_optimization (after) /// CHECK-NOT: Phi /// CHECK-NOT: InvokeVirtual intrinsic:StringBuilderToString /// CHECK-NOT: InvokeVirtual intrinsic:StringStringIndexOfAfter static int builderDeadLoop() { StringBuilder b = new StringBuilder(); String x = "x"; for (int i = 0; i < 10; i++) { int d = b.toString().indexOf(x, 1); } return b.length(); } // Regression b/33656359: StringBuffer x is passed to constructor of String // (this caused old code to crash due to missing nullptr check). // /// CHECK-START: void Main.doesNothing() instruction_simplifier (before) /// CHECK-DAG: InvokeVirtual intrinsic:StringBufferToString // /// CHECK-START: void Main.doesNothing() instruction_simplifier (after) /// CHECK-DAG: InvokeVirtual intrinsic:StringBufferToString static void doesNothing() { StringBuffer x = new StringBuffer(); String y = new String(x); x.toString(); } public static void main(String[] args) throws Exception { expectEquals(1865, liveIndexOf()); expectEquals(29, deadIndexOf()); try { indexOfExceptions(null, XYZ); throw new Error("Expected: NPE"); } catch (NullPointerException e) { } try { indexOfExceptions(ABC, null); throw new Error("Expected: NPE"); } catch (NullPointerException e) { } expectEquals(598, indexOfExceptions(ABC, XYZ)); expectEquals(2, bufferLen2()); expectEquals(2, bufferLen2Smali()); expectEquals(2, builderLen2()); expectEquals(2, builderLen2Smali()); expectEquals(30, bufferLoopAppender()); expectEquals(30, bufferLoopAppenderSmali()); expectEquals(30, builderLoopAppender()); expectEquals(30, builderLoopAppenderSmali()); expectEquals(0, bufferDeadLoop()); expectEquals(0, builderDeadLoop()); doesNothing(); System.out.println("passed"); } private static void expectEquals(int expected, int result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); } } }