blob: 6e18f742f2dc49347121d82a7d9cf3812046f119 [file] [log] [blame]
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize.string;
import static com.android.tools.r8.ir.optimize.string.StringOptimizer.isStringLength;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.Const16;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.ir.code.SingleConstant;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
class StringLengthTestMain {
@ForceInline
static String simpleInlinable() {
return "Shared";
}
@NeverInline
static int npe() {
String n = null;
// Cannot be computed at compile time.
return n.length();
}
public static void main(String[] args) {
String s1 = "GONE";
// Can be computed at compile time: constCount++
System.out.println(s1.length());
String s2 = simpleInlinable();
// Depends on inlining: constCount++
System.out.println(s2.length());
String s3 = simpleInlinable();
System.out.println(s3);
String s4 = "Another_shared";
// Can be computed at compile time: constCount++
System.out.println(s4.length());
System.out.println(s4);
String s5 = "\uD800\uDC00"; // U+10000
// Can be computed at compile time: constCount++
System.out.println(s5.length());
// Even reusable: should not increase any counts.
System.out.println(s5.codePointCount(0, s5.length()));
System.out.println(s5);
// Make sure this is not optimized in DEBUG mode.
int l = "ABC".length();
System.out.println(l);
try {
npe();
} catch (NullPointerException npe) {
// expected
}
}
}
@RunWith(Parameterized.class)
public class StringLengthTest extends TestBase {
private final Backend backend;
List<byte[]> classes;
@Parameterized.Parameters(name = "Backend: {0}")
public static Collection<Backend> data() {
return Arrays.asList(Backend.values());
}
public StringLengthTest(Backend backend) {
this.backend = backend;
}
@Before
public void setUp() throws Exception {
classes = ImmutableList.of(
ToolHelper.getClassAsBytes(ForceInline.class),
ToolHelper.getClassAsBytes(NeverInline.class),
ToolHelper.getClassAsBytes(StringLengthTestMain.class)
);
}
private int countStringLength(Code code) {
int count = 0;
if (code.isDexCode()) {
DexCode dexCode = code.asDexCode();
for (Instruction instr : dexCode.instructions) {
if (instr instanceof InvokeVirtual) {
InvokeVirtual invoke = (InvokeVirtual) instr;
if (isStringLength(invoke.getMethod(), null)) {
count++;
}
}
}
return count;
}
assert code.isCfCode();
CfCode cfCode = code.asCfCode();
for (CfInstruction instr : cfCode.getInstructions()) {
if (instr instanceof CfInvoke) {
CfInvoke invoke = (CfInvoke) instr;
if (isStringLength(invoke.getMethod(), null)) {
count++;
}
}
}
return count;
}
private int countConstNumber(Code code) {
int count = 0;
if (code.isDexCode()) {
DexCode dexCode = code.asDexCode();
for (Instruction instr : dexCode.instructions) {
if (instr instanceof Const4 || instr instanceof Const16) {
int constValue = ((SingleConstant) instr).decodedValue();
if (constValue != 0) {
count++;
}
}
}
return count;
}
assert code.isCfCode();
CfCode cfCode = code.asCfCode();
for (CfInstruction instr : cfCode.getInstructions()) {
if (instr instanceof CfConstNumber) {
CfConstNumber constNumber = (CfConstNumber) instr;
if (constNumber.getIntValue() != 0) {
count++;
}
}
}
return count;
}
private void test(
AndroidApp processedApp,
int expectedStringLengthCount,
int expectedConstNumberCount)
throws Exception {
String main = StringLengthTestMain.class.getCanonicalName();
ProcessResult javaOutput = runOnJavaRaw(main, classes, ImmutableList.of());
assertEquals(0, javaOutput.exitCode);
ProcessResult output =
backend == Backend.DEX
? runOnArtRaw(processedApp, main)
: runOnJavaRaw(processedApp, main, ImmutableList.of());
assertEquals(0, output.exitCode);
CodeInspector codeInspector = new CodeInspector(processedApp);
ClassSubject mainClass = codeInspector.clazz(main);
MethodSubject mainMethod = mainClass.mainMethod();
assertThat(mainMethod, isPresent());
Code code = mainMethod.getMethod().getCode();
int count = countStringLength(code);
assertEquals(expectedStringLengthCount, count);
count = countConstNumber(code);
assertEquals(expectedConstNumberCount, count);
}
@Test
public void testD8() throws Exception {
if (backend == Backend.CF) {
return;
}
AndroidApp app = buildAndroidApp(classes);
D8Command.Builder builder = ToolHelper.prepareD8CommandBuilder(app);
builder.setMode(CompilationMode.RELEASE);
AndroidApp processedApp = ToolHelper.runD8(builder);
test(processedApp, 1, 4);
builder = ToolHelper.prepareD8CommandBuilder(app);
builder.setMode(CompilationMode.DEBUG);
processedApp = ToolHelper.runD8(builder);
test(processedApp, 6, 0);
}
@Test
public void testR8() throws Exception {
AndroidApp app = buildAndroidApp(classes);
ProgramConsumer programConsumer;
Path library;
if (backend == Backend.DEX) {
programConsumer = DexIndexedConsumer.emptyConsumer();
library = ToolHelper.getDefaultAndroidJar();
} else {
assert backend == Backend.CF;
programConsumer = ClassFileConsumer.emptyConsumer();
library = ToolHelper.getJava8RuntimeJar();
}
R8Command.Builder builder =
ToolHelper.prepareR8CommandBuilder(app, programConsumer).addLibraryFiles(library);
ToolHelper.allowTestProguardOptions(builder);
String pgConf = keepMainProguardConfigurationWithInliningAnnotation(StringLengthTestMain.class);
builder.addProguardConfiguration(ImmutableList.of(pgConf), Origin.unknown());
AndroidApp processedApp = ToolHelper.runR8(builder.build());
test(processedApp, 0, 5);
}
}