blob: 2a2fe32d1e14a948fa479eb0e2e7f5ff5923bc7f [file] [log] [blame]
// Copyright (c) 2016, 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.smali;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.DivIntLit8;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.RemIntLit8;
import com.android.tools.r8.code.Return;
import com.android.tools.r8.code.ReturnWide;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.Cmp.Bias;
import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.SingleConstant;
import com.android.tools.r8.ir.code.WideConstant;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
public class ConstantFoldingTest extends SmaliTestBase {
public void generateBinopTest(String type, String op, List<Long> values, Long result) {
boolean wide = type.equals("long") || type.equals("double");
StringBuilder source = new StringBuilder();
int factor = wide ? 2 : 1;
for (int i = 0; i < values.size(); i++) {
source.append(" ");
source.append(wide ? "const-wide " : "const ");
source.append("v" + (i * factor));
source.append(", ");
source.append("0x" + Long.toHexString(values.get(i)));
source.append(wide ? "L" : "");
source.append("\n");
}
for (int i = 0; i < values.size() - 1; i++) {
source.append(" ");
source.append(op + "-" + type + "/2addr ");
source.append("v" + ((i + 1) * factor));
source.append(", ");
source.append("v" + (i * factor));
source.append("\n");
}
source.append(" ");
source.append(wide ? "return-wide " : "return ");
source.append("v" + ((values.size() - 1) * factor));
DexEncodedMethod method = oneMethodApplication(
type, Collections.singletonList(type),
values.size() * factor,
source.toString());
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
if (wide) {
assertTrue(code.instructions[0] instanceof WideConstant);
assertEquals(result.longValue(), ((WideConstant) code.instructions[0]).decodedValue());
assertTrue(code.instructions[1] instanceof ReturnWide);
} else {
assertTrue(code.instructions[0] instanceof SingleConstant);
assertEquals(
result.longValue(), (long) ((SingleConstant) code.instructions[0]).decodedValue());
assertTrue(code.instructions[1] instanceof Return);
}
}
private long floatBits(float f) {
return Float.floatToIntBits(f);
}
private long doubleBits(double d) {
return Double.doubleToLongBits(d);
}
ImmutableList<Long> arguments = ImmutableList.of(1L, 2L, 3L, 4L);
ImmutableList<Long> floatArguments = ImmutableList.of(
floatBits(1.0f), floatBits(2.0f), floatBits(3.0f), floatBits(4.0f));
ImmutableList<Long> doubleArguments = ImmutableList.of(
doubleBits(1.0), doubleBits(2.0), doubleBits(3.0), doubleBits(4.0));
@Test
public void addFold() {
generateBinopTest("int", "add", arguments, 10L);
generateBinopTest("long", "add", arguments, 10L);
generateBinopTest("float", "add", floatArguments, floatBits(10.0f));
generateBinopTest("double", "add", doubleArguments, doubleBits(10.0));
}
@Test
public void mulFold() {
generateBinopTest("int", "mul", arguments, 24L);
generateBinopTest("long", "mul", arguments, 24L);
generateBinopTest("float", "mul", floatArguments, floatBits(24.0f));
generateBinopTest("double", "mul", doubleArguments, doubleBits(24.0));
}
@Test
public void subFold() {
generateBinopTest("int", "sub", arguments.reverse(), -2L);
generateBinopTest("long", "sub", arguments.reverse(), -2L);
generateBinopTest("float", "sub", floatArguments.reverse(), floatBits(-2.0f));
generateBinopTest("double", "sub", doubleArguments.reverse(), doubleBits(-2.0));
}
@Test
public void divFold() {
ImmutableList<Long> arguments = ImmutableList.of(2L, 24L, 48L, 4L);
ImmutableList<Long> floatArguments = ImmutableList.of(
floatBits(2.0f), floatBits(24.0f), floatBits(48.0f), floatBits(4.0f));
ImmutableList<Long> doubleArguments = ImmutableList.of(
doubleBits(2.0), doubleBits(24.0), doubleBits(48.0), doubleBits(4.0));
generateBinopTest("int", "div", arguments, 1L);
generateBinopTest("long", "div", arguments, 1L);
generateBinopTest("float", "div", floatArguments, floatBits(1.0f));
generateBinopTest("double", "div", doubleArguments, doubleBits(1.0));
}
@Test
public void remFold() {
ImmutableList<Long> arguments = ImmutableList.of(10L, 6L, 3L, 2L);
ImmutableList<Long> floatArguments = ImmutableList.of(
floatBits(10.0f), floatBits(6.0f), floatBits(3.0f), floatBits(2.0f));
ImmutableList<Long> doubleArguments = ImmutableList.of(
doubleBits(10.0), doubleBits(6.0), doubleBits(3.0), doubleBits(2.0));
generateBinopTest("int", "rem", arguments, 2L);
generateBinopTest("long", "rem", arguments, 2L);
generateBinopTest("float", "rem", floatArguments, floatBits(2.0f));
generateBinopTest("double", "rem", doubleArguments, doubleBits(2.0));
}
@Test
public void divIntFoldDivByZero() {
DexEncodedMethod method = oneMethodApplication(
"int", Collections.singletonList("int"),
2,
" const/4 v0, 1 ",
" const/4 v1, 0 ",
" div-int/2addr v0, v1 ",
" return v0\n "
);
DexCode code = method.getCode().asDexCode();
// Division by zero is not folded, but div-int/lit8 is used.
assertEquals(3, code.instructions.length);
assertTrue(code.instructions[0] instanceof Const4);
assertTrue(code.instructions[1] instanceof DivIntLit8);
assertEquals(0, ((DivIntLit8) code.instructions[1]).CC);
assertTrue(code.instructions[2] instanceof Return);
}
@Test
public void divIntFoldRemByZero() {
DexEncodedMethod method = oneMethodApplication(
"int", Collections.singletonList("int"),
2,
" const/4 v0, 1 ",
" const/4 v1, 0 ",
" rem-int/2addr v0, v1 ",
" return v0\n "
);
DexCode code = method.getCode().asDexCode();
// Division by zero is not folded, but rem-int/lit8 is used.
assertEquals(3, code.instructions.length);
assertTrue(code.instructions[0] instanceof Const4);
assertTrue(code.instructions[1] instanceof RemIntLit8);
assertEquals(0, ((RemIntLit8) code.instructions[1]).CC);
assertTrue(code.instructions[2] instanceof Return);
}
public void generateUnopTest(String type, String op, Long value, Long result) {
boolean wide = type.equals("long") || type.equals("double");
StringBuilder source = new StringBuilder();
source.append(" ");
source.append(wide ? "const-wide " : "const ");
source.append("v0 , ");
source.append("0x" + Long.toHexString(value));
source.append(wide ? "L" : "");
source.append("\n");
source.append(" ");
source.append(op + "-" + type + " v0, v0\n");
source.append(" ");
source.append(wide ? "return-wide v0" : "return v0");
DexEncodedMethod method = oneMethodApplication(
type, Collections.singletonList(type),
wide ? 2 : 1,
source.toString());
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
if (wide) {
assertTrue(code.instructions[0] instanceof WideConstant);
assertEquals(result.longValue(), ((WideConstant) code.instructions[0]).decodedValue());
assertTrue(code.instructions[1] instanceof ReturnWide);
} else {
assertTrue(code.instructions[0] instanceof SingleConstant);
assertEquals(
result.longValue(), (long) ((SingleConstant) code.instructions[0]).decodedValue());
assertTrue(code.instructions[1] instanceof Return);
}
}
@Test
public void negFold() {
generateUnopTest("int", "neg", 2L, -2L);
generateUnopTest("int", "neg", -2L, 2L);
generateUnopTest("long", "neg", 2L, -2L);
generateUnopTest("long", "neg", -2L, 2L);
generateUnopTest("float", "neg", floatBits(2.0f), floatBits(-2.0f));
generateUnopTest("float", "neg", floatBits(-2.0f), floatBits(2.0f));
generateUnopTest("float", "neg", floatBits(0.0f), floatBits(-0.0f));
generateUnopTest("float", "neg", floatBits(-0.0f), floatBits(0.0f));
generateUnopTest("double", "neg", doubleBits(2.0), doubleBits(-2.0));
generateUnopTest("double", "neg", doubleBits(-2.0), doubleBits(2.0));
generateUnopTest("double", "neg", doubleBits(0.0), doubleBits(-0.0));
generateUnopTest("double", "neg", doubleBits(-0.0), doubleBits(0.0));
}
private void assertConstValue(int expected, Instruction insn) {
assertTrue(insn instanceof SingleConstant);
assertEquals(expected, ((SingleConstant) insn).decodedValue());
}
private void assertConstValue(long expected, Instruction insn) {
assertTrue(insn instanceof WideConstant);
assertEquals(expected, ((WideConstant) insn).decodedValue());
}
public void testLogicalOperatorsFolding(String op, int[] v) {
int v0 = v[0];
int v1 = v[1];
int v2 = v[2];
int v3 = v[3];
int expected = 0;
switch (op) {
case "and":
expected = v0 & v1 & v2 & v3;
break;
case "or":
expected = v0 | v1 | v2 | v3;
break;
case "xor":
expected = v0 ^ v1 ^ v2 ^ v3;
break;
default:
fail("Unsupported logical binop " + op);
}
DexEncodedMethod method = oneMethodApplication(
"int", Collections.singletonList("int"),
4,
" const v0, " + v0,
" const v1, " + v1,
" const v2, " + v2,
" const v3, " + v3,
// E.g. and-int//2addr v1, v0
" " + op + "-int/2addr v1, v0 ",
" " + op + "-int/2addr v2, v1 ",
" " + op + "-int/2addr v3, v2 ",
" return v3\n "
);
DexCode code = method.getCode().asDexCode();
// Test that this just returns a constant.
assertEquals(2, code.instructions.length);
assertConstValue(expected, code.instructions[0]);
assertTrue(code.instructions[1] instanceof Return);
}
@Test
public void logicalOperatorsFolding() {
int[][] testValues = new int[][]{
new int[]{0x00, 0x00, 0x00, 0x00},
new int[]{0x0b, 0x06, 0x03, 0x00},
new int[]{0x0f, 0x07, 0x03, 0x01},
new int[]{0x08, 0x04, 0x02, 0x01},
};
for (int[] values : testValues) {
testLogicalOperatorsFolding("and", values);
testLogicalOperatorsFolding("or", values);
testLogicalOperatorsFolding("xor", values);
}
}
private void testShiftOperatorsFolding(String op, int[] v) {
int v0 = v[0];
int v1 = v[1];
int v2 = v[2];
int v3 = v[3];
int expected = 0;
switch (op) {
case "shl":
v0 = v0 << v1;
v0 = v0 << v2;
v0 = v0 << v3;
break;
case "shr":
v0 = v0 >> v1;
v0 = v0 >> v2;
v0 = v0 >> v3;
break;
case "ushr":
v0 = v0 >>> v1;
v0 = v0 >>> v2;
v0 = v0 >>> v3;
break;
default:
fail("Unsupported shift " + op);
}
expected = v0;
DexEncodedMethod method = oneMethodApplication(
"int", Collections.singletonList("int"),
4,
" const v0, " + v[0],
" const v1, " + v[1],
" const v2, " + v[2],
" const v3, " + v[3],
// E.g. and-int//2addr v1, v0
" " + op + "-int/2addr v0, v1 ",
" " + op + "-int/2addr v0, v2 ",
" " + op + "-int/2addr v0, v3 ",
" return v0\n "
);
DexCode code = method.getCode().asDexCode();
// Test that this just returns a constant.
assertEquals(2, code.instructions.length);
assertConstValue(expected, code.instructions[0]);
assertTrue(code.instructions[1] instanceof Return);
}
@Test
public void shiftOperatorsFolding() {
int[][] testValues = new int[][]{
new int[]{0x01, 0x01, 0x01, 0x01},
new int[]{0x01, 0x02, 0x03, 0x04},
new int[]{0x7f000000, 0x01, 0x2, 0x03},
new int[]{0x80000000, 0x01, 0x2, 0x03},
new int[]{0xffffffff, 0x01, 0x2, 0x03},
};
for (int[] values : testValues) {
testShiftOperatorsFolding("shl", values);
testShiftOperatorsFolding("shr", values);
testShiftOperatorsFolding("ushr", values);
}
}
private void testShiftOperatorsFoldingWide(String op, long[] v) {
long v0 = v[0];
int v2 = (int) v[1];
int v4 = (int) v[2];
int v6 = (int) v[3];
long expected = 0;
switch (op) {
case "shl":
v0 = v0 << v2;
v0 = v0 << v4;
v0 = v0 << v6;
break;
case "shr":
v0 = v0 >> v2;
v0 = v0 >> v4;
v0 = v0 >> v6;
break;
case "ushr":
v0 = v0 >>> v2;
v0 = v0 >>> v4;
v0 = v0 >>> v6;
break;
default:
fail("Unsupported shift " + op);
}
expected = v0;
DexEncodedMethod method = oneMethodApplication(
"long", Collections.singletonList("long"),
5,
" const-wide v0, 0x" + Long.toHexString(v[0]) + "L",
" const v2, " + v[1],
" const v3, " + v[2],
" const v4, " + v[3],
// E.g. and-long//2addr v1, v0
" " + op + "-long/2addr v0, v2 ",
" " + op + "-long/2addr v0, v3 ",
" " + op + "-long/2addr v0, v4 ",
" return-wide v0\n "
);
DexCode code = method.getCode().asDexCode();
// Test that this just returns a constant.
assertEquals(2, code.instructions.length);
assertConstValue(expected, code.instructions[0]);
assertTrue(code.instructions[1] instanceof ReturnWide);
}
@Test
public void shiftOperatorsFoldingWide() {
long[][] testValues = new long[][]{
new long[]{0x01, 0x01, 0x01, 0x01},
new long[]{0x01, 0x02, 0x03, 0x04},
new long[]{0x7f0000000000L, 0x01, 0x2, 0x03},
new long[]{0x800000000000L, 0x01, 0x2, 0x03},
new long[]{0x7f00000000000000L, 0x01, 0x2, 0x03},
new long[]{0x8000000000000000L, 0x01, 0x2, 0x03},
new long[]{0xffffffffffffffffL, 0x01, 0x2, 0x03},
};
for (long[] values : testValues) {
testShiftOperatorsFoldingWide("shl", values);
testShiftOperatorsFoldingWide("shr", values);
testShiftOperatorsFoldingWide("ushr", values);
}
}
@Test
public void notIntFold() {
int[] testValues = new int[]{0, 1, 0xff, 0xffffffff, 0xff000000, 0x80000000};
for (int value : testValues) {
DexEncodedMethod method = oneMethodApplication(
"int", Collections.emptyList(),
1,
" const v0, " + value,
" not-int v0, v0",
" return v0"
);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
assertConstValue(~value, code.instructions[0]);
assertTrue(code.instructions[1] instanceof Return);
}
}
@Test
public void notLongFold() {
long[] testValues = new long[]{
0L,
1L,
0xffL,
0xffffffffffffffffL,
0x00ffffffffffffffL,
0xff00000000000000L,
0x8000000000000000L
};
for (long value : testValues) {
DexEncodedMethod method = oneMethodApplication(
"long", Collections.emptyList(),
2,
" const-wide v0, 0x" + Long.toHexString(value) + "L",
" not-long v0, v0",
" return-wide v0"
);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
assertConstValue(~value, code.instructions[0]);
assertTrue(code.instructions[1] instanceof ReturnWide);
}
}
@Test
public void negIntFold() {
int[] testValues = new int[]{0, 1, 0xff, 0xffffffff, 0xff000000, 0x80000000};
for (int value : testValues) {
DexEncodedMethod method = oneMethodApplication(
"int", Collections.emptyList(),
1,
" const v0, " + value,
" neg-int v0, v0",
" return v0"
);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
assertConstValue(-value, code.instructions[0]);
assertTrue(code.instructions[1] instanceof Return);
}
}
@Test
public void negLongFold() {
long[] testValues = new long[]{
0L,
1L,
0xffL,
0xffffffffffffffffL,
0x00ffffffffffffffL,
0xff00000000000000L,
0x8000000000000000L
};
for (long value : testValues) {
DexEncodedMethod method = oneMethodApplication(
"long", Collections.emptyList(),
2,
" const-wide v0, 0x" + Long.toHexString(value) + "L",
" neg-long v0, v0",
" return-wide v0"
);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
long expected = -value;
assertConstValue(-value, code.instructions[0]);
assertTrue(code.instructions[1] instanceof ReturnWide);
}
}
@Test
public void cmpFloatFold() {
String[] ifOpcode = new String[6];
ifOpcode[Type.EQ.ordinal()] = "if-eqz";
ifOpcode[Type.NE.ordinal()] = "if-nez";
ifOpcode[Type.LE.ordinal()] = "if-lez";
ifOpcode[Type.GE.ordinal()] = "if-gez";
ifOpcode[Type.LT.ordinal()] = "if-ltz";
ifOpcode[Type.GT.ordinal()] = "if-gtz";
class FloatTestData {
final float a;
final float b;
final boolean results[];
FloatTestData(float a, float b) {
this.a = a;
this.b = b;
results = new boolean[6];
results[Type.EQ.ordinal()] = a == b;
results[Type.NE.ordinal()] = a != b;
results[Type.LE.ordinal()] = a <= b;
results[Type.GE.ordinal()] = a >= b;
results[Type.LT.ordinal()] = a < b;
results[Type.GT.ordinal()] = a > b;
}
}
float[] testValues = new float[]{
Float.NEGATIVE_INFINITY,
-100.0f,
-0.0f,
0.0f,
100.0f,
Float.POSITIVE_INFINITY,
Float.NaN
};
List<FloatTestData> tests = new ArrayList<>();
for (int i = 0; i < testValues.length; i++) {
for (int j = 0; j < testValues.length; j++) {
tests.add(new FloatTestData(testValues[i], testValues[j]));
}
}
for (FloatTestData test : tests) {
for (Type type : Type.values()) {
for (Bias bias : Bias.values()) {
if (bias == Bias.NONE) {
// Bias NONE is only for long comparison.
continue;
}
// If no NaNs are involved either bias produce the same result.
if (Float.isNaN(test.a) || Float.isNaN(test.b)) {
// For NaN comparison only test with the bias that provide Java semantics.
// The Java Language Specification 4.2.3. Floating-Point Types, Formats, and Values
// says:
//
// The numerical comparison operators <, <=, >, and >= return false if either or both
// operands are NaN
if ((type == Type.GE || type == Type.GT) && bias == Bias.GT) {
continue;
}
if ((type == Type.LE || type == Type.LT) && bias == Bias.LT) {
continue;
}
}
String cmpInstruction;
if (bias == Bias.LT) {
cmpInstruction = " cmpl-float v0, v0, v1";
} else {
cmpInstruction = " cmpg-float v0, v0, v1";
}
DexEncodedMethod method = oneMethodApplication(
"int", Collections.emptyList(),
2,
" const v0, 0x" + Integer.toHexString(Float.floatToRawIntBits(test.a)),
" const v1, 0x" + Integer.toHexString(Float.floatToRawIntBits(test.b)),
cmpInstruction,
" " + ifOpcode[type.ordinal()] + " v0, :label_2",
" const v0, 0",
":label_1",
" return v0",
":label_2",
" const v0, 1",
" goto :label_1"
);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
int expected = test.results[type.ordinal()] ? 1 : 0;
assertConstValue(expected, code.instructions[0]);
assertTrue(code.instructions[1] instanceof Return);
}
}
}
}
@Test
public void cmpDoubleFold() {
String[] ifOpcode = new String[6];
ifOpcode[Type.EQ.ordinal()] = "if-eqz";
ifOpcode[Type.NE.ordinal()] = "if-nez";
ifOpcode[Type.LE.ordinal()] = "if-lez";
ifOpcode[Type.GE.ordinal()] = "if-gez";
ifOpcode[Type.LT.ordinal()] = "if-ltz";
ifOpcode[Type.GT.ordinal()] = "if-gtz";
class DoubleTestData {
final double a;
final double b;
final boolean results[];
DoubleTestData(double a, double b) {
this.a = a;
this.b = b;
results = new boolean[6];
results[Type.EQ.ordinal()] = a == b;
results[Type.NE.ordinal()] = a != b;
results[Type.LE.ordinal()] = a <= b;
results[Type.GE.ordinal()] = a >= b;
results[Type.LT.ordinal()] = a < b;
results[Type.GT.ordinal()] = a > b;
}
}
double[] testValues = new double[]{
Double.NEGATIVE_INFINITY,
-100.0f,
-0.0f,
0.0f,
100.0f,
Double.POSITIVE_INFINITY,
Double.NaN
};
List<DoubleTestData> tests = new ArrayList<>();
for (int i = 0; i < testValues.length; i++) {
for (int j = 0; j < testValues.length; j++) {
tests.add(new DoubleTestData(testValues[i], testValues[j]));
}
}
for (DoubleTestData test : tests) {
for (Type type : Type.values()) {
for (Bias bias : Bias.values()) {
if (bias == Bias.NONE) {
// Bias NONE is only for long comparison.
continue;
}
if (Double.isNaN(test.a) || Double.isNaN(test.b)) {
// For NaN comparison only test with the bias that provide Java semantics.
// The Java Language Specification 4.2.3. Doubleing-Point Types, Formats, and Values
// says:
//
// The numerical comparison operators <, <=, >, and >= return false if either or both
// operands are NaN
if ((type == Type.GE || type == Type.GT) && bias == Bias.GT) {
continue;
}
if ((type == Type.LE || type == Type.LT) && bias == Bias.LT) {
continue;
}
}
String cmpInstruction;
if (bias == Bias.LT) {
cmpInstruction = " cmpl-double v0, v0, v2";
} else {
cmpInstruction = " cmpg-double v0, v0, v2";
}
DexEncodedMethod method = oneMethodApplication(
"int", Collections.emptyList(),
4,
" const-wide v0, 0x" + Long.toHexString(Double.doubleToRawLongBits(test.a)) + "L",
" const-wide v2, 0x" + Long.toHexString(Double.doubleToRawLongBits(test.b)) + "L",
cmpInstruction,
" " + ifOpcode[type.ordinal()] + " v0, :label_2",
" const v0, 0",
":label_1",
" return v0",
":label_2",
" const v0, 1",
" goto :label_1"
);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
int expected = test.results[type.ordinal()] ? 1 : 0;
assertConstValue(expected, code.instructions[0]);
assertTrue(code.instructions[1] instanceof Return);
}
}
}
}
@Test
public void cmpLongFold() {
long[][] longValues = new long[][]{
{Long.MIN_VALUE, 1L},
{Long.MAX_VALUE, 1L},
{Long.MIN_VALUE, 0L},
{Long.MAX_VALUE, 0L},
{Long.MIN_VALUE, -1L},
{Long.MAX_VALUE, -1L},
};
for (long[] values : longValues) {
DexEncodedMethod method = oneMethodApplication(
"int", Collections.emptyList(),
4,
" const-wide v0, 0x" + Long.toHexString(values[0]) + "L",
" const-wide v2, 0x" + Long.toHexString(values[1]) + "L",
" cmp-long v0, v0, v2",
" return v0"
);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
assertConstValue(Long.compare(values[0], values[1]), code.instructions[0]);
assertTrue(code.instructions[1] instanceof Return);
}
}
}