Move and reuse static-get in NewArrayEmpty rewriting
Also extend tests to hit the limit of the reused values
for the rewriting.
Bug: b/299292313
Bug: b/300882718
Change-Id: I17e0476396288d5a3caeee7a9e76621bcfc47248
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
index 13e590b..e269dc1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
@@ -4,10 +4,10 @@
package com.android.tools.r8.ir.conversion.passes;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.NewArrayFilled;
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
@@ -303,13 +304,15 @@
private static class ConstantMaterializingInstructionCache {
- // All DEX aput instructions takes an 8-bit wide register value for the source.
- private final int MAX_MATERIALIZING_CONSTANTS = Constants.U8BIT_MAX - 16;
+ private final RewriteArrayOptions rewriteArrayOptions;
+
// Track constants as DexItems, DexString for string constants and DexType for class constants.
private final Map<DexItem, Integer> constantOccurrences = new IdentityHashMap<>();
private final Map<DexItem, Value> constantValue = new IdentityHashMap<>();
- private ConstantMaterializingInstructionCache(NewArrayFilled newArrayFilled) {
+ private ConstantMaterializingInstructionCache(
+ RewriteArrayOptions rewriteArrayOptions, NewArrayFilled newArrayFilled) {
+ this.rewriteArrayOptions = rewriteArrayOptions;
for (Value elementValue : newArrayFilled.inValues()) {
if (elementValue.hasAnyUsers()) {
continue;
@@ -318,6 +321,8 @@
addOccurrence(elementValue.getDefinition().asConstString().getValue());
} else if (elementValue.isConstClass()) {
addOccurrence(elementValue.getDefinition().asConstClass().getValue());
+ } else if (elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)) {
+ addOccurrence(elementValue.getDefinition().asStaticGet().getField());
}
// Don't canonicalize numbers, as on DEX FilledNewArray is supported for primitives
// on all versions.
@@ -339,25 +344,45 @@
seenOcourence(type);
return value;
}
+ } else if (elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)) {
+ DexField field = elementValue.getDefinition().asStaticGet().getField();
+ Value value = constantValue.get(field);
+ if (value != null) {
+ seenOcourence(field);
+ return value;
+ }
}
return null;
}
+ // Order: String > field > class.
private DexItem smallestConstant(DexItem c1, DexItem c2) {
if (c1 instanceof DexString) {
if (c2 instanceof DexString) {
return ((DexString) c1).compareTo((DexString) c2) < 0 ? c1 : c2;
} else {
- assert c2 instanceof DexType;
- return c2; // String larger than class.
+ assert c2 instanceof DexField || c2 instanceof DexType;
+ return c2; // String larger than field and class.
+ }
+ } else if (c1 instanceof DexField) {
+ if (c2 instanceof DexField) {
+ return ((DexField) c1).compareTo((DexField) c2) < 0 ? c1 : c2;
+ } else {
+ // Field less than string, larger than class
+ if (c2 instanceof DexString) {
+ return c1;
+ } else {
+ assert c2 instanceof DexType;
+ return c2;
+ }
}
} else {
assert c1 instanceof DexType;
if (c2 instanceof DexType) {
return ((DexType) c1).compareTo((DexType) c2) < 0 ? c1 : c2;
} else {
- assert c2 instanceof DexString;
- return c1; // String larger than class.
+ assert c2 instanceof DexString || c2 instanceof DexField;
+ return c1; // Class less than string and field.
}
}
}
@@ -366,6 +391,8 @@
Instruction instruction = value.getDefinition();
if (instruction.isConstString()) {
return instruction.asConstString().getValue();
+ } else if (instruction.isStaticGet()) {
+ return instruction.asStaticGet().getField();
} else {
assert instruction.isConstClass();
return instruction.asConstClass().getValue();
@@ -376,10 +403,10 @@
DexItem constant = getConstant(value);
assert constantOccurrences.containsKey(constant);
assert !constantValue.containsKey(constant);
- if (constantValue.size() < MAX_MATERIALIZING_CONSTANTS) {
+ if (constantValue.size() < rewriteArrayOptions.maxMaterializingConstants) {
constantValue.put(constant, value);
} else {
- assert constantValue.size() == MAX_MATERIALIZING_CONSTANTS;
+ assert constantValue.size() == rewriteArrayOptions.maxMaterializingConstants;
// Find the least valuable active constant.
int leastOccurrences = Integer.MAX_VALUE;
DexItem valueWithLeastOccurrences = null;
@@ -403,7 +430,7 @@
constantValue.remove(valueWithLeastOccurrences);
constantValue.put(constant, value);
}
- assert constantValue.size() == MAX_MATERIALIZING_CONSTANTS;
+ assert constantValue.size() == rewriteArrayOptions.maxMaterializingConstants;
}
seenOcourence(constant);
}
@@ -437,7 +464,7 @@
NewArrayEmpty newArrayEmpty = rewriteToNewArrayEmpty(code, instructionIterator, newArrayFilled);
ConstantMaterializingInstructionCache constantMaterializingInstructionCache =
- new ConstantMaterializingInstructionCache(newArrayFilled);
+ new ConstantMaterializingInstructionCache(rewriteArrayOptions, newArrayFilled);
int index = 0;
for (Value elementValue : newArrayFilled.inValues()) {
@@ -484,25 +511,29 @@
if (elementValue.hasAnyUsers()
|| !(elementValue.isConstString()
|| elementValue.isConstNumber()
- || elementValue.isConstClass())) {
+ || elementValue.isConstClass()
+ || elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet))) {
return elementValue;
}
Value existingValue = constantMaterializingInstructionCache.getValue(elementValue);
if (existingValue != null) {
- addToRemove(elementValue.definition);
+ addToRemove(elementValue.getDefinition());
return existingValue;
}
Instruction copy;
if (elementValue.isConstNumber()) {
- copy = ConstNumber.copyOf(code, elementValue.definition.asConstNumber());
+ copy = ConstNumber.copyOf(code, elementValue.getDefinition().asConstNumber());
} else if (elementValue.isConstString()) {
- copy = ConstString.copyOf(code, elementValue.definition.asConstString());
+ copy = ConstString.copyOf(code, elementValue.getDefinition().asConstString());
constantMaterializingInstructionCache.putNewValue(copy.asConstString().outValue());
} else if (elementValue.isConstClass()) {
- copy = ConstClass.copyOf(code, elementValue.definition.asConstClass());
+ copy = ConstClass.copyOf(code, elementValue.getDefinition().asConstClass());
constantMaterializingInstructionCache.putNewValue(copy.asConstClass().outValue());
+ } else if (elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)) {
+ copy = StaticGet.copyOf(code, elementValue.getDefinition().asStaticGet());
+ constantMaterializingInstructionCache.putNewValue(copy.asStaticGet().outValue());
} else {
assert false;
return elementValue;
@@ -510,7 +541,7 @@
copy.setBlock(instructionIterator.getBlock());
copy.setPosition(newArrayEmpty.getPosition());
instructionIterator.add(copy);
- addToRemove(elementValue.definition);
+ addToRemove(elementValue.getDefinition());
return copy.outValue();
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 0e163e2..3ea71fd 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Backend;
import com.android.tools.r8.dex.Marker.Tool;
@@ -1551,6 +1552,8 @@
public int maxSizeForFilledNewArrayOfInts = 200;
public int maxSizeForFilledNewArrayOfIntsWhenNewArrayFilledDataApplicable = 5;
public int maxSizeForFilledNewArrayOfReferences = 200;
+ // All DEX aput instructions takes an 8-bit wide register value for the source.
+ public int maxMaterializingConstants = Constants.U8BIT_MAX - 16;
// Arbitrary limits of number of inputs to fill-array-data.
public int minSizeForFilledArrayData = 2;
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
index cc339a8..fc8b2a5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
@@ -9,12 +9,15 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,11 +34,15 @@
@Parameter(1)
public CompilationMode compilationMode;
- @Parameters(name = "{0}, mode = {1}")
+ @Parameter(2)
+ public Integer maxMaterializingConstants;
+
+ @Parameters(name = "{0}, mode = {1}, maxMaterializingConstants = {2}")
public static Iterable<?> data() {
return buildParameters(
getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
- CompilationMode.values());
+ CompilationMode.values(),
+ ImmutableList.of(Constants.U8BIT_MAX - 16, 2));
}
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "104");
@@ -62,7 +69,17 @@
private void inspect(CodeInspector inspector) {
inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 26, 104, false);
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
+ maxMaterializingConstants == 2 ? 98 : 26,
+ 104,
+ false);
+ }
+
+ private void configure(TestCompilerBuilder<?, ?, ?, ?, ?> builder) {
+ builder.addOptionsModification(
+ options ->
+ options.rewriteArrayOptions().maxMaterializingConstants = maxMaterializingConstants);
}
@Test
@@ -71,6 +88,7 @@
testForD8(parameters.getBackend())
.addInnerClasses(getClass())
.setMinApi(parameters)
+ .apply(this::configure)
.run(parameters.getRuntime(), TestClass.class)
.inspect(this::inspect)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -84,6 +102,7 @@
.setMinApi(parameters)
.enableInliningAnnotations()
.addDontObfuscate()
+ .apply(this::configure)
.run(parameters.getRuntime(), TestClass.class)
.inspect(this::inspect)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
index 4dca11b..d8227dc 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
@@ -10,11 +10,13 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Iterator;
import org.junit.Test;
@@ -32,11 +34,15 @@
@Parameter(1)
public CompilationMode compilationMode;
- @Parameters(name = "{0}, mode = {1}")
+ @Parameter(2)
+ public Integer maxMaterializingConstants;
+
+ @Parameters(name = "{0}, mode = {1}, maxMaterializingConstants = {2}")
public static Iterable<?> data() {
return buildParameters(
getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
- CompilationMode.values());
+ CompilationMode.values(),
+ ImmutableList.of(Constants.U8BIT_MAX - 16, 2));
}
private static final String EXPECTED_OUTPUT =
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
new file mode 100644
index 0000000..8376245
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2023, 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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticGetArrayWithNonUniqueValuesTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public CompilationMode compilationMode;
+
+ @Parameter(2)
+ public Integer maxMaterializingConstants;
+
+ @Parameters(name = "{0}, mode = {1}, maxMaterializingConstants = {2}")
+ public static Iterable<?> data() {
+ return buildParameters(
+ getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
+ CompilationMode.values(),
+ ImmutableList.of(Constants.U8BIT_MAX - 16, 2));
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "50");
+
+ public boolean canUseFilledNewArrayOfObject(TestParameters parameters) {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ private void inspect(MethodSubject method, int staticGets, int puts, boolean insideCatchHandler) {
+ boolean expectingFilledNewArray =
+ canUseFilledNewArrayOfObject(parameters) && !insideCatchHandler;
+ assertEquals(
+ expectingFilledNewArray ? 0 : puts,
+ method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
+ assertEquals(
+ expectingFilledNewArray ? 1 : 0,
+ method.streamInstructions().filter(InstructionSubject::isFilledNewArray).count());
+ assertEquals(
+ staticGets, method.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+ }
+
+ private void configure(TestCompilerBuilder<?, ?, ?, ?, ?> builder) {
+ builder.addOptionsModification(
+ options ->
+ options.rewriteArrayOptions().maxMaterializingConstants = maxMaterializingConstants);
+ }
+
+ private void inspectD8(CodeInspector inspector) {
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
+ canUseFilledNewArrayOfObject(parameters) ? 100 : 1,
+ 100,
+ false);
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
+ canUseFilledNewArrayOfObject(parameters) ? 50 : (maxMaterializingConstants == 2 ? 42 : 10),
+ 50,
+ false);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .apply(this::configure)
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::inspectD8)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void inspectR8(CodeInspector inspector) {
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
+ maxMaterializingConstants == 2 && !canUseFilledNewArrayOfObject(parameters) ? 42 : 10,
+ 50,
+ false);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .enableInliningAnnotations()
+ .addDontObfuscate()
+ .apply(this::configure)
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::inspectR8)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ public static final class TestClass {
+
+ @NeverInline
+ public static void m1() {
+ A[] array =
+ new A[] {
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00, A.A00,
+ };
+ printArraySize(array);
+ }
+
+ @NeverInline
+ public static void m2() {
+ A[] array =
+ new A[] {
+ A.A00, A.A01, A.A02, A.A03, A.A04, A.A05, A.A06, A.A07, A.A08, A.A09,
+ A.A00, A.A01, A.A02, A.A03, A.A04, A.A05, A.A06, A.A07, A.A08, A.A09,
+ A.A00, A.A01, A.A02, A.A03, A.A04, A.A05, A.A06, A.A07, A.A08, A.A09,
+ A.A00, A.A01, A.A02, A.A03, A.A04, A.A05, A.A06, A.A07, A.A08, A.A09,
+ A.A00, A.A01, A.A02, A.A03, A.A04, A.A05, A.A06, A.A07, A.A08, A.A09,
+ };
+ printArraySize(array);
+ }
+
+ @NeverInline
+ public static void printArraySize(A[] array) {
+ System.out.println(Arrays.asList(array).size());
+ }
+
+ public static void main(String[] args) {
+ m1();
+ m2();
+ }
+ }
+
+ static class A {
+
+ private final String name;
+
+ private A(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ static A A00 = new A("A00");
+ static A A01 = new A("A01");
+ static A A02 = new A("A02");
+ static A A03 = new A("A03");
+ static A A04 = new A("A04");
+ static A A05 = new A("A05");
+ static A A06 = new A("A06");
+ static A A07 = new A("A07");
+ static A A08 = new A("A08");
+ static A A09 = new A("A09");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
new file mode 100644
index 0000000..b5fe270
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
@@ -0,0 +1,304 @@
+// Copyright (c) 2023, 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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Iterator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticGetArrayWithUniqueValuesTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public CompilationMode compilationMode;
+
+ @Parameter(2)
+ public Integer maxMaterializingConstants;
+
+ @Parameters(name = "{0}, mode = {1}, maxMaterializingConstants = {2}")
+ public static Iterable<?> data() {
+ return buildParameters(
+ getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
+ CompilationMode.values(),
+ ImmutableList.of(Constants.U8BIT_MAX - 16, 2));
+ }
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("[A00, A01, A02, A03, A04]", "[A00, A01, A02, A03, A04]", "100");
+
+ public boolean canUseFilledNewArrayOfObject(TestParameters parameters) {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ private enum State {
+ EXPECTING_GETSTATIC,
+ EXPECTING_APUTOBJECT
+ }
+
+ private void inspect(MethodSubject method, int puts, boolean insideCatchHandler) {
+ boolean expectingFilledNewArray =
+ canUseFilledNewArrayOfObject(parameters) && !insideCatchHandler;
+ assertEquals(
+ expectingFilledNewArray ? 0 : puts,
+ method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
+ assertEquals(
+ expectingFilledNewArray ? 1 : 0,
+ method.streamInstructions().filter(InstructionSubject::isFilledNewArray).count());
+ assertEquals(puts, method.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+ if (!expectingFilledNewArray) {
+ // Test that sget and aput instructions are interleaved by the lowering of
+ // filled-new-array.
+ int aputCount = 0;
+ State state = State.EXPECTING_GETSTATIC;
+ Iterator<InstructionSubject> iterator = method.iterateInstructions();
+ while (iterator.hasNext()) {
+ InstructionSubject instruction = iterator.next();
+ if (instruction.isStaticGet()) {
+ assertEquals(State.EXPECTING_GETSTATIC, state);
+ state = State.EXPECTING_APUTOBJECT;
+ } else if (instruction.isArrayPut()) {
+ assertEquals(State.EXPECTING_APUTOBJECT, state);
+ state = State.EXPECTING_GETSTATIC;
+ aputCount++;
+ }
+ }
+ assertEquals(State.EXPECTING_GETSTATIC, state);
+ assertEquals(puts, aputCount);
+ }
+ }
+
+ private void inspect(CodeInspector inspector) {
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, true);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100, false);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::inspect)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .enableInliningAnnotations()
+ .addDontObfuscate()
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::inspect)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ public static final class TestClass {
+
+ @NeverInline
+ public static void m1() {
+ A[] array = {A.A00, A.A01, A.A02, A.A03, A.A04};
+ printArray(array);
+ }
+
+ @NeverInline
+ public static void m2() {
+ try {
+ A[] array = {A.A00, A.A01, A.A02, A.A03, A.A04};
+ printArray(array);
+ } catch (Exception e) {
+ throw new RuntimeException();
+ }
+ }
+
+ @NeverInline
+ public static void m3() {
+ A[] array =
+ new A[] {
+ A.A00, A.A01, A.A02, A.A03, A.A04, A.A05, A.A06, A.A07,
+ A.A08, A.A09, A.A10, A.A11, A.A12, A.A13, A.A14, A.A15,
+ A.A16, A.A17, A.A18, A.A19, A.A20, A.A21, A.A22, A.A23,
+ A.A24, A.A25, A.A26, A.A27, A.A28, A.A29, A.A30, A.A31,
+ A.A32, A.A33, A.A34, A.A35, A.A36, A.A37, A.A38, A.A39,
+ A.A40, A.A41, A.A42, A.A43, A.A44, A.A45, A.A46, A.A47,
+ A.A48, A.A49, A.A50, A.A51, A.A52, A.A53, A.A54, A.A55,
+ A.A56, A.A57, A.A58, A.A59, A.A60, A.A61, A.A62, A.A63,
+ A.A64, A.A65, A.A66, A.A67, A.A68, A.A69, A.A70, A.A71,
+ A.A72, A.A73, A.A74, A.A75, A.A76, A.A77, A.A78, A.A79,
+ A.A80, A.A81, A.A82, A.A83, A.A84, A.A85, A.A86, A.A87,
+ A.A88, A.A89, A.A90, A.A91, A.A92, A.A93, A.A94, A.A95,
+ A.A96, A.A97, A.A98, A.A99,
+ };
+ printArraySize(array);
+ }
+
+ @NeverInline
+ public static void printArray(A[] array) {
+ System.out.print("[");
+ for (A a : array) {
+ if (a != array[0]) {
+ System.out.print(", ");
+ }
+ System.out.print(a);
+ }
+ System.out.println("]");
+ }
+
+ @NeverInline
+ public static void printArraySize(A[] array) {
+ System.out.println(Arrays.asList(array).size());
+ }
+
+ public static void main(String[] args) {
+ m1();
+ m2();
+ m3();
+ }
+ }
+
+ static class A {
+ private final String name;
+
+ private A(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ static A A00 = new A("A00");
+ static A A01 = new A("A01");
+ static A A02 = new A("A02");
+ static A A03 = new A("A03");
+ static A A04 = new A("A04");
+ static A A05 = new A("A05");
+ static A A06 = new A("A06");
+ static A A07 = new A("A07");
+ static A A08 = new A("A08");
+ static A A09 = new A("A09");
+ static A A10 = new A("A10");
+ static A A11 = new A("A11");
+ static A A12 = new A("A12");
+ static A A13 = new A("A13");
+ static A A14 = new A("A14");
+ static A A15 = new A("A15");
+ static A A16 = new A("A16");
+ static A A17 = new A("A17");
+ static A A18 = new A("A18");
+ static A A19 = new A("A19");
+ static A A20 = new A("A20");
+ static A A21 = new A("A21");
+ static A A22 = new A("A22");
+ static A A23 = new A("A23");
+ static A A24 = new A("A24");
+ static A A25 = new A("A25");
+ static A A26 = new A("A26");
+ static A A27 = new A("A27");
+ static A A28 = new A("A28");
+ static A A29 = new A("A29");
+ static A A30 = new A("A30");
+ static A A31 = new A("A31");
+ static A A32 = new A("A32");
+ static A A33 = new A("A33");
+ static A A34 = new A("A34");
+ static A A35 = new A("A35");
+ static A A36 = new A("A36");
+ static A A37 = new A("A37");
+ static A A38 = new A("A38");
+ static A A39 = new A("A39");
+ static A A40 = new A("A40");
+ static A A41 = new A("A41");
+ static A A42 = new A("A42");
+ static A A43 = new A("A43");
+ static A A44 = new A("A44");
+ static A A45 = new A("A45");
+ static A A46 = new A("A46");
+ static A A47 = new A("A47");
+ static A A48 = new A("A48");
+ static A A49 = new A("A49");
+ static A A50 = new A("A50");
+ static A A51 = new A("A51");
+ static A A52 = new A("A52");
+ static A A53 = new A("A53");
+ static A A54 = new A("A54");
+ static A A55 = new A("A55");
+ static A A56 = new A("A56");
+ static A A57 = new A("A57");
+ static A A58 = new A("A58");
+ static A A59 = new A("A59");
+ static A A60 = new A("A60");
+ static A A61 = new A("A61");
+ static A A62 = new A("A62");
+ static A A63 = new A("A63");
+ static A A64 = new A("A64");
+ static A A65 = new A("A65");
+ static A A66 = new A("A66");
+ static A A67 = new A("A67");
+ static A A68 = new A("A68");
+ static A A69 = new A("A69");
+ static A A70 = new A("A70");
+ static A A71 = new A("A71");
+ static A A72 = new A("A72");
+ static A A73 = new A("A73");
+ static A A74 = new A("A74");
+ static A A75 = new A("A75");
+ static A A76 = new A("A76");
+ static A A77 = new A("A77");
+ static A A78 = new A("A78");
+ static A A79 = new A("A79");
+ static A A80 = new A("A80");
+ static A A81 = new A("A81");
+ static A A82 = new A("A82");
+ static A A83 = new A("A83");
+ static A A84 = new A("A84");
+ static A A85 = new A("A85");
+ static A A86 = new A("A86");
+ static A A87 = new A("A87");
+ static A A88 = new A("A88");
+ static A A89 = new A("A89");
+ static A A90 = new A("A90");
+ static A A91 = new A("A91");
+ static A A92 = new A("A92");
+ static A A93 = new A("A93");
+ static A A94 = new A("A94");
+ static A A95 = new A("A95");
+ static A A96 = new A("A96");
+ static A A97 = new A("A97");
+ static A A98 = new A("A98");
+ static A A99 = new A("A99");
+ }
+
+ // public static void main(String[] args) {
+ // for (int i = 0; i < 100; i++) {
+ // System.out.println(" static A A" + i + " = new A(\"A" + i + "\")");
+ // }
+ // }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
index 220e5dc..8391ea5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
@@ -9,12 +9,15 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,11 +34,15 @@
@Parameter(1)
public CompilationMode compilationMode;
- @Parameters(name = "{0}, mode = {1}")
+ @Parameter(2)
+ public Integer maxMaterializingConstants;
+
+ @Parameters(name = "{0}, mode = {1}, maxMaterializingConstants = {2}")
public static Iterable<?> data() {
return buildParameters(
getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
- CompilationMode.values());
+ CompilationMode.values(),
+ ImmutableList.of(Constants.U8BIT_MAX - 16, 2));
}
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "104");
@@ -60,9 +67,19 @@
method.streamInstructions().filter(InstructionSubject::isConstString).count());
}
- private void inspect(CodeInspector inspector) {
+ private void configure(TestCompilerBuilder<?, ?, ?, ?, ?> builder) {
+ builder.addOptionsModification(
+ options ->
+ options.rewriteArrayOptions().maxMaterializingConstants = maxMaterializingConstants);
+ }
+
+ private void inspectD8(CodeInspector inspector) {
inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 26, 104, false);
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
+ maxMaterializingConstants == 2 ? 32 : 26,
+ 104,
+ false);
}
@Test
@@ -71,11 +88,23 @@
testForD8(parameters.getBackend())
.addInnerClasses(getClass())
.setMinApi(parameters)
+ .apply(this::configure)
.run(parameters.getRuntime(), TestClass.class)
- .inspect(this::inspect)
+ .inspect(this::inspectD8)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
+ private void inspectR8(CodeInspector inspector) {
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ // TODO(300882718): Constant canonicalizer is not run for R8 after NewArrayFilled
+ // lowering (98 vs 32 for D8).
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
+ maxMaterializingConstants == 2 ? 98 : 26,
+ 104,
+ false);
+ }
+
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
@@ -84,8 +113,9 @@
.setMinApi(parameters)
.enableInliningAnnotations()
.addDontObfuscate()
+ .apply(this::configure)
.run(parameters.getRuntime(), TestClass.class)
- .inspect(this::inspect)
+ .inspect(this::inspectR8)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
index c3a70be..2613ca5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
@@ -10,11 +10,13 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Iterator;
import org.junit.Test;
@@ -32,11 +34,15 @@
@Parameter(1)
public CompilationMode compilationMode;
- @Parameters(name = "{0}, mode = {1}")
+ @Parameter(2)
+ public Integer maxMaterializingConstants;
+
+ @Parameters(name = "{0}, mode = {1}, maxMaterializingConstants = {2}")
public static Iterable<?> data() {
return buildParameters(
getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
- CompilationMode.values());
+ CompilationMode.values(),
+ ImmutableList.of(Constants.U8BIT_MAX - 16, 2));
}
private static final String EXPECTED_OUTPUT =