| // 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 com.android.tools.r8.TestParameters; | |
| import com.android.tools.r8.TestParametersCollection; | |
| import com.android.tools.r8.dex.code.DexConst4; | |
| import com.android.tools.r8.dex.code.DexDivIntLit8; | |
| import com.android.tools.r8.dex.code.DexInstruction; | |
| import com.android.tools.r8.dex.code.DexRemIntLit8; | |
| import com.android.tools.r8.dex.code.DexReturn; | |
| import com.android.tools.r8.dex.code.DexReturnWide; | |
| 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.IfType; | |
| import com.android.tools.r8.ir.code.SingleConstant; | |
| import com.android.tools.r8.ir.code.WideConstant; | |
| 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.internal.IntUtils; | |
| import com.android.tools.r8.utils.internal.LongUtils; | |
| import com.android.tools.r8.utils.internal.exceptions.Unreachable; | |
| import com.google.common.collect.ImmutableList; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.function.BiConsumer; | |
| 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 ConstantFoldingTest extends SmaliTestBase { | |
| public interface TriConsumer<T, U, V> { | |
| void accept(T t, U u, V v); | |
| } | |
| private class SmaliBuilderWithCheckers { | |
| List<Object> values = new ArrayList<>(); | |
| List<BiConsumer<DexEncodedMethod, Object>> checkers = new ArrayList<>(); | |
| private final SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); | |
| public void addTest( | |
| TriConsumer<SmaliBuilder, String, Object> methodBilder, | |
| BiConsumer<DexEncodedMethod, Object> methodChecker, | |
| Object value) { | |
| methodBilder.accept(builder, "m" + values.size(), value); | |
| values.add(value); | |
| checkers.add(methodChecker); | |
| } | |
| public void run() throws Exception { | |
| AndroidApp processdApplication = processApplication(buildApplication(builder)); | |
| assertEquals(1, getNumberOfProgramClasses(processdApplication)); | |
| CodeInspector inspector = new CodeInspector(processdApplication); | |
| ClassSubject clazz = inspector.clazz(DEFAULT_CLASS_NAME); | |
| clazz.forAllMethods( | |
| method -> { | |
| int index = | |
| Integer.parseInt(method.getMethod().getReference().name.toString().substring(1)); | |
| checkers.get(index).accept(method.getMethod(), values.get(index)); | |
| }); | |
| } | |
| } | |
| public class BinopTestData { | |
| public final String type; | |
| public final String op; | |
| public final List<Long> values; | |
| public final Long result; | |
| BinopTestData(String type, String op, List<Long> values, Long result) { | |
| this.type = type; | |
| this.op = op; | |
| this.values = values; | |
| this.result = result; | |
| } | |
| } | |
| private ImmutableList<Long> arguments = ImmutableList.of(1L, 2L, 3L, 4L); | |
| private ImmutableList<Long> floatArguments = | |
| ImmutableList.of( | |
| LongUtils.encodeFloat(1.0f), | |
| LongUtils.encodeFloat(2.0f), | |
| LongUtils.encodeFloat(3.0f), | |
| LongUtils.encodeFloat(4.0f)); | |
| private ImmutableList<Long> doubleArguments = | |
| ImmutableList.of( | |
| LongUtils.encodeDouble(1.0), | |
| LongUtils.encodeDouble(2.0), | |
| LongUtils.encodeDouble(3.0), | |
| LongUtils.encodeDouble(4.0)); | |
| private void binopMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| BinopTestData test = (BinopTestData) parameters; | |
| boolean wide = test.type.equals("long") || test.type.equals("double"); | |
| StringBuilder source = new StringBuilder(); | |
| int factor = wide ? 2 : 1; | |
| for (int i = 0; i < test.values.size(); i++) { | |
| source.append(" "); | |
| source.append(wide ? "const-wide " : "const "); | |
| source.append("v" + (i * factor)); | |
| source.append(", "); | |
| source.append("0x" + Long.toHexString(test.values.get(i))); | |
| source.append(wide ? "L" : ""); | |
| source.append("\n"); | |
| } | |
| for (int i = 0; i < test.values.size() - 1; i++) { | |
| source.append(" "); | |
| source.append(test.op + "-" + test.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" + ((test.values.size() - 1) * factor)); | |
| builder.addStaticMethod( | |
| test.type, | |
| name, | |
| Collections.singletonList(test.type), | |
| test.values.size() * factor, | |
| source.toString()); | |
| } | |
| private void binopMethodChecker(DexEncodedMethod method, Object parameters) { | |
| BinopTestData test = (BinopTestData) parameters; | |
| boolean wide = test.type.equals("long") || test.type.equals("double"); | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| if (wide) { | |
| assertTrue(code.instructions[0] instanceof WideConstant); | |
| assertEquals(test.result.longValue(), ((WideConstant) code.instructions[0]).decodedValue()); | |
| assertTrue(code.instructions[1] instanceof DexReturnWide); | |
| } else { | |
| assertTrue(code.instructions[0] instanceof SingleConstant); | |
| assertEquals( | |
| test.result.longValue(), (long) ((SingleConstant) code.instructions[0]).decodedValue()); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| } | |
| private void addBinopTest(SmaliBuilderWithCheckers testBuilder, BinopTestData test) { | |
| testBuilder.addTest(this::binopMethodBuilder, this::binopMethodChecker, test); | |
| } | |
| private void addBinopFoldingTests(SmaliBuilderWithCheckers testBuilder) { | |
| // Add tests. | |
| addBinopTest(testBuilder, new BinopTestData("int", "add", arguments, 10L)); | |
| addBinopTest(testBuilder, new BinopTestData("long", "add", arguments, 10L)); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("float", "add", floatArguments, LongUtils.encodeFloat(10.0f))); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("double", "add", doubleArguments, LongUtils.encodeDouble(10.0))); | |
| // Mul tests. | |
| addBinopTest(testBuilder, new BinopTestData("int", "mul", arguments, 24L)); | |
| addBinopTest(testBuilder, new BinopTestData("long", "mul", arguments, 24L)); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("float", "mul", floatArguments, LongUtils.encodeFloat(24.0f))); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("double", "mul", doubleArguments, LongUtils.encodeDouble(24.0))); | |
| // Sub tests. | |
| addBinopTest(testBuilder, new BinopTestData("int", "sub", arguments.reverse(), -2L)); | |
| addBinopTest(testBuilder, new BinopTestData("long", "sub", arguments.reverse(), -2L)); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("float", "sub", floatArguments.reverse(), LongUtils.encodeFloat(-2.0f))); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData( | |
| "double", "sub", doubleArguments.reverse(), LongUtils.encodeDouble(-2.0))); | |
| // Div tests. | |
| { | |
| ImmutableList<Long> arguments = ImmutableList.of(2L, 24L, 48L, 4L); | |
| ImmutableList<Long> floatArguments = | |
| ImmutableList.of( | |
| LongUtils.encodeFloat(2.0f), | |
| LongUtils.encodeFloat(24.0f), | |
| LongUtils.encodeFloat(48.0f), | |
| LongUtils.encodeFloat(4.0f)); | |
| ImmutableList<Long> doubleArguments = | |
| ImmutableList.of( | |
| LongUtils.encodeDouble(2.0), | |
| LongUtils.encodeDouble(24.0), | |
| LongUtils.encodeDouble(48.0), | |
| LongUtils.encodeDouble(4.0)); | |
| addBinopTest(testBuilder, new BinopTestData("int", "div", arguments, 1L)); | |
| addBinopTest(testBuilder, new BinopTestData("long", "div", arguments, 1L)); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("float", "div", floatArguments, LongUtils.encodeFloat(1.0f))); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("double", "div", doubleArguments, LongUtils.encodeDouble(1.0))); | |
| } | |
| // Rem tests. | |
| { | |
| ImmutableList<Long> arguments = ImmutableList.of(10L, 6L, 3L, 2L); | |
| ImmutableList<Long> floatArguments = | |
| ImmutableList.of( | |
| LongUtils.encodeFloat(10.0f), | |
| LongUtils.encodeFloat(6.0f), | |
| LongUtils.encodeFloat(3.0f), | |
| LongUtils.encodeFloat(2.0f)); | |
| ImmutableList<Long> doubleArguments = | |
| ImmutableList.of( | |
| LongUtils.encodeDouble(10.0), | |
| LongUtils.encodeDouble(6.0), | |
| LongUtils.encodeDouble(3.0), | |
| LongUtils.encodeDouble(2.0)); | |
| addBinopTest(testBuilder, new BinopTestData("int", "rem", arguments, 2L)); | |
| addBinopTest(testBuilder, new BinopTestData("long", "rem", arguments, 2L)); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("float", "rem", floatArguments, LongUtils.encodeFloat(2.0f))); | |
| addBinopTest( | |
| testBuilder, | |
| new BinopTestData("double", "rem", doubleArguments, LongUtils.encodeDouble(2.0))); | |
| } | |
| } | |
| private void addDivIntFoldDivByZero(SmaliBuilderWithCheckers testBuilder) { | |
| testBuilder.addTest( | |
| (builder, name, parameters) -> { | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.singletonList("int"), | |
| 2, | |
| " const/4 v0, 1 ", | |
| " const/4 v1, 0 ", | |
| " div-int/2addr v0, v1 ", | |
| " return v0\n "); | |
| }, | |
| (method, parameters) -> { | |
| 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 DexConst4); | |
| assertTrue(code.instructions[1] instanceof DexDivIntLit8); | |
| assertEquals(0, ((DexDivIntLit8) code.instructions[1]).CC); | |
| assertTrue(code.instructions[2] instanceof DexReturn); | |
| }, | |
| null); | |
| } | |
| private void addDivIntFoldRemByZero(SmaliBuilderWithCheckers testBuilder) { | |
| testBuilder.addTest( | |
| (builder, name, parameters) -> { | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.singletonList("int"), | |
| 2, | |
| " const/4 v0, 1 ", | |
| " const/4 v1, 0 ", | |
| " rem-int/2addr v0, v1 ", | |
| " return v0\n "); | |
| }, | |
| (method, parameters) -> { | |
| 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 DexConst4); | |
| assertTrue(code.instructions[1] instanceof DexRemIntLit8); | |
| assertEquals(0, ((DexRemIntLit8) code.instructions[1]).CC); | |
| assertTrue(code.instructions[2] instanceof DexReturn); | |
| }, | |
| null); | |
| } | |
| public class UnopTestData { | |
| public final String type; | |
| public final String op; | |
| public final Long value; | |
| public final Long result; | |
| UnopTestData(String type, String op, Long value, Long result) { | |
| this.type = type; | |
| this.op = op; | |
| this.value = value; | |
| this.result = result; | |
| } | |
| } | |
| private void unopMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| UnopTestData test = (UnopTestData) parameters; | |
| boolean wide = test.type.equals("long") || test.type.equals("double"); | |
| StringBuilder source = new StringBuilder(); | |
| source.append(" "); | |
| source.append(wide ? "const-wide " : "const "); | |
| source.append("v0 , "); | |
| source.append("0x" + Long.toHexString(test.value)); | |
| source.append(wide ? "L" : ""); | |
| source.append("\n"); | |
| source.append(" "); | |
| source.append(test.op + "-" + test.type + " v0, v0\n"); | |
| source.append(" "); | |
| source.append(wide ? "return-wide v0" : "return v0"); | |
| builder.addStaticMethod( | |
| test.type, name, Collections.singletonList(test.type), wide ? 2 : 1, source.toString()); | |
| } | |
| private void unopMethodChecker(DexEncodedMethod method, Object parameters) { | |
| UnopTestData test = (UnopTestData) parameters; | |
| boolean wide = test.type.equals("long") || test.type.equals("double"); | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| if (wide) { | |
| assertTrue(code.instructions[0] instanceof WideConstant); | |
| assertEquals(test.result.longValue(), ((WideConstant) code.instructions[0]).decodedValue()); | |
| assertTrue(code.instructions[1] instanceof DexReturnWide); | |
| } else { | |
| assertTrue(code.instructions[0] instanceof SingleConstant); | |
| assertEquals( | |
| test.result.longValue(), (long) ((SingleConstant) code.instructions[0]).decodedValue()); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| } | |
| private void addUnopTest(SmaliBuilderWithCheckers testBuilder, UnopTestData test) { | |
| testBuilder.addTest(this::unopMethodBuilder, this::unopMethodChecker, test); | |
| } | |
| private void addNegFoldingTest(SmaliBuilderWithCheckers testBuilder) { | |
| addUnopTest(testBuilder, new UnopTestData("int", "neg", 2L, -2L)); | |
| addUnopTest(testBuilder, new UnopTestData("int", "neg", -2L, 2L)); | |
| addUnopTest(testBuilder, new UnopTestData("long", "neg", 2L, -2L)); | |
| addUnopTest(testBuilder, new UnopTestData("long", "neg", -2L, 2L)); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "float", "neg", LongUtils.encodeFloat(2.0f), LongUtils.encodeFloat(-2.0f))); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "float", "neg", LongUtils.encodeFloat(-2.0f), LongUtils.encodeFloat(2.0f))); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "float", "neg", LongUtils.encodeFloat(0.0f), LongUtils.encodeFloat(-0.0f))); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "float", "neg", LongUtils.encodeFloat(-0.0f), LongUtils.encodeFloat(0.0f))); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "double", "neg", LongUtils.encodeDouble(2.0), LongUtils.encodeDouble(-2.0))); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "double", "neg", LongUtils.encodeDouble(-2.0), LongUtils.encodeDouble(2.0))); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "double", "neg", LongUtils.encodeDouble(0.0), LongUtils.encodeDouble(-0.0))); | |
| addUnopTest( | |
| testBuilder, | |
| new UnopTestData( | |
| "double", "neg", LongUtils.encodeDouble(-0.0), LongUtils.encodeDouble(0.0))); | |
| } | |
| private void assertConstValue(int expected, DexInstruction insn) { | |
| assertTrue(insn instanceof SingleConstant); | |
| assertEquals(expected, ((SingleConstant) insn).decodedValue()); | |
| } | |
| private void assertConstValue(long expected, DexInstruction insn) { | |
| assertTrue(insn instanceof WideConstant); | |
| assertEquals(expected, ((WideConstant) insn).decodedValue()); | |
| } | |
| class LogicalOperatorTestData { | |
| final String op; | |
| final int[] values; | |
| final int expected; | |
| LogicalOperatorTestData(String op, int[] values) { | |
| this.op = op; | |
| this.values = values; | |
| int v0 = values[0]; | |
| int v1 = values[1]; | |
| int v2 = values[2]; | |
| int v3 = values[3]; | |
| switch (op) { | |
| case "and": | |
| this.expected = v0 & v1 & v2 & v3; | |
| break; | |
| case "or": | |
| this.expected = v0 | v1 | v2 | v3; | |
| break; | |
| case "xor": | |
| this.expected = v0 ^ v1 ^ v2 ^ v3; | |
| break; | |
| default: | |
| this.expected = 0; | |
| throw new Unreachable("Unsupported logical binop " + op); | |
| } | |
| } | |
| } | |
| private void logicalOperatorMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| LogicalOperatorTestData test = (LogicalOperatorTestData) parameters; | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.singletonList("int"), | |
| 4, | |
| " const v0, " + test.values[0], | |
| " const v1, " + test.values[1], | |
| " const v2, " + test.values[2], | |
| " const v3, " + test.values[3], | |
| // E.g. and-int//2addr v1, v0 | |
| " " + test.op + "-int/2addr v1, v0 ", | |
| " " + test.op + "-int/2addr v2, v1 ", | |
| " " + test.op + "-int/2addr v3, v2 ", | |
| " return v3\n "); | |
| } | |
| private void logicalOperatorMethodChecker(DexEncodedMethod method, Object parameters) { | |
| LogicalOperatorTestData test = (LogicalOperatorTestData) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| // Test that this just returns a constant. | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(test.expected, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| private void addLogicalOperatorsFoldTests(SmaliBuilderWithCheckers testBuilder) { | |
| 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) { | |
| for (String op : new String[] {"and", "or", "xor"}) { | |
| testBuilder.addTest( | |
| this::logicalOperatorMethodBuilder, | |
| this::logicalOperatorMethodChecker, | |
| new LogicalOperatorTestData(op, values)); | |
| } | |
| } | |
| } | |
| class ShiftTestData { | |
| final int[] values; | |
| final String op; | |
| final int expected; | |
| ShiftTestData(String op, int[] values) { | |
| this.values = values; | |
| this.op = op; | |
| int v0 = values[0]; | |
| int v1 = values[1]; | |
| int v2 = values[2]; | |
| int v3 = values[3]; | |
| 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: | |
| throw new Unreachable("Unsupported shift " + op); | |
| } | |
| this.expected = v0; | |
| } | |
| } | |
| private void shiftOperatorMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| ShiftTestData data = (ShiftTestData) parameters; | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.singletonList("int"), | |
| 4, | |
| " const v0, " + data.values[0], | |
| " const v1, " + data.values[1], | |
| " const v2, " + data.values[2], | |
| " const v3, " + data.values[3], | |
| // E.g. and-int//2addr v1, v0 | |
| " " + data.op + "-int/2addr v0, v1 ", | |
| " " + data.op + "-int/2addr v0, v2 ", | |
| " " + data.op + "-int/2addr v0, v3 ", | |
| " return v0\n "); | |
| } | |
| private void shiftOperatorMethodChecker(DexEncodedMethod method, Object parameters) { | |
| ShiftTestData data = (ShiftTestData) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| // Test that this just returns a constant. | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(data.expected, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| public void addShiftOperatorsFolding(SmaliBuilderWithCheckers testBuilder) { | |
| 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) { | |
| for (String op : new String[] {"shl", "shr", "ushr"}) { | |
| testBuilder.addTest( | |
| this::shiftOperatorMethodBuilder, | |
| this::shiftOperatorMethodChecker, | |
| new ShiftTestData(op, values)); | |
| } | |
| } | |
| } | |
| class ShiftWideTestData { | |
| final long[] values; | |
| final String op; | |
| final long expected; | |
| ShiftWideTestData(String op, long[] values) { | |
| this.values = values; | |
| this.op = op; | |
| long v0 = values[0]; | |
| int v2 = (int) values[1]; | |
| int v4 = (int) values[2]; | |
| int v6 = (int) values[3]; | |
| 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: | |
| throw new Unreachable("Unsupported shift " + op); | |
| } | |
| this.expected = v0; | |
| } | |
| } | |
| private void shiftOperatorWideMethodBuilder( | |
| SmaliBuilder builder, String name, Object parameters) { | |
| ShiftWideTestData data = (ShiftWideTestData) parameters; | |
| builder.addStaticMethod( | |
| "long", | |
| name, | |
| Collections.singletonList("long"), | |
| 5, | |
| " const-wide v0, 0x" + Long.toHexString(data.values[0]) + "L", | |
| " const v2, " + data.values[1], | |
| " const v3, " + data.values[2], | |
| " const v4, " + data.values[3], | |
| // E.g. and-long//2addr v1, v0 | |
| " " + data.op + "-long/2addr v0, v2 ", | |
| " " + data.op + "-long/2addr v0, v3 ", | |
| " " + data.op + "-long/2addr v0, v4 ", | |
| " return-wide v0\n "); | |
| } | |
| private void shiftOperatorWideMethodChecker(DexEncodedMethod method, Object parameters) { | |
| ShiftWideTestData data = (ShiftWideTestData) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| // Test that this just returns a constant. | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(data.expected, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturnWide); | |
| } | |
| public void addShiftOperatorsFoldingWide(SmaliBuilderWithCheckers testBuilder) { | |
| 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) { | |
| for (String op : new String[] {"shl", "shr", "ushr"}) { | |
| testBuilder.addTest( | |
| this::shiftOperatorWideMethodBuilder, | |
| this::shiftOperatorWideMethodChecker, | |
| new ShiftWideTestData(op, values)); | |
| } | |
| } | |
| } | |
| private void notIntMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| Integer value = (Integer) parameters; | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.emptyList(), | |
| 1, | |
| " const v0, " + value, | |
| " not-int v0, v0", | |
| " return v0"); | |
| } | |
| private void notIntMethodChecker(DexEncodedMethod method, Object parameters) { | |
| Integer value = (Integer) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(~value, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| private void addNotIntFoldTests(SmaliBuilderWithCheckers testBuilder) { | |
| ImmutableList.of(0, 1, 0xff, 0xffffffff, 0xff000000, 0x80000000) | |
| .forEach(v -> testBuilder.addTest(this::notIntMethodBuilder, this::notIntMethodChecker, v)); | |
| } | |
| private void notLongMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| Long value = (Long) parameters; | |
| builder.addStaticMethod( | |
| "long", | |
| name, | |
| Collections.emptyList(), | |
| 2, | |
| " const-wide v0, 0x" + Long.toHexString(value) + "L", | |
| " not-long v0, v0", | |
| " return-wide v0"); | |
| } | |
| private void notLongMethodChecker(DexEncodedMethod method, Object parameters) { | |
| Long value = (Long) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(~value, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturnWide); | |
| } | |
| private void addNotLongFoldTests(SmaliBuilderWithCheckers testBuilder) { | |
| ImmutableList.of( | |
| 0L, | |
| 1L, | |
| 0xffL, | |
| 0xffffffffffffffffL, | |
| 0x00ffffffffffffffL, | |
| 0xff00000000000000L, | |
| 0x8000000000000000L) | |
| .forEach( | |
| v -> testBuilder.addTest(this::notLongMethodBuilder, this::notLongMethodChecker, v)); | |
| } | |
| private void negIntMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| Integer value = (Integer) parameters; | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.emptyList(), | |
| 1, | |
| " const v0, " + value, | |
| " neg-int v0, v0", | |
| " return v0"); | |
| } | |
| private void negIntMethodChecker(DexEncodedMethod method, Object parameters) { | |
| Integer value = (Integer) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(-value, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| private void addNegIntFoldTests(SmaliBuilderWithCheckers testBuilder) { | |
| ImmutableList.of(0, 1, 0xff, 0xffffffff, 0xff000000, 0x80000000) | |
| .forEach(v -> testBuilder.addTest(this::negIntMethodBuilder, this::negIntMethodChecker, v)); | |
| } | |
| private void negLongMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| Long value = (Long) parameters; | |
| builder.addStaticMethod( | |
| "long", | |
| name, | |
| Collections.emptyList(), | |
| 2, | |
| " const-wide v0, 0x" + Long.toHexString(value) + "L", | |
| " neg-long v0, v0", | |
| " return-wide v0"); | |
| } | |
| private void negLongMethodChecker(DexEncodedMethod method, Object parameters) { | |
| Long value = (Long) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(-value, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturnWide); | |
| } | |
| private void addNegLongFoldTests(SmaliBuilderWithCheckers testBuilder) { | |
| ImmutableList.of( | |
| 0L, | |
| 1L, | |
| 0xffL, | |
| 0xffffffffffffffffL, | |
| 0x00ffffffffffffffL, | |
| 0xff00000000000000L, | |
| 0x8000000000000000L) | |
| .forEach( | |
| v -> testBuilder.addTest(this::negLongMethodBuilder, this::negLongMethodChecker, v)); | |
| } | |
| class FloatTestData { | |
| final float a; | |
| final float b; | |
| final IfType type; | |
| final Bias bias; | |
| final boolean expected; | |
| FloatTestData(float a, float b, IfType type, Bias bias) { | |
| this.a = a; | |
| this.b = b; | |
| this.type = type; | |
| this.bias = bias; | |
| switch (type) { | |
| case EQ: | |
| expected = a == b; | |
| break; | |
| case NE: | |
| expected = a != b; | |
| break; | |
| case LE: | |
| expected = a <= b; | |
| break; | |
| case GE: | |
| expected = a >= b; | |
| break; | |
| case LT: | |
| expected = a < b; | |
| break; | |
| case GT: | |
| expected = a > b; | |
| break; | |
| default: | |
| expected = false; | |
| assert false; | |
| } | |
| } | |
| } | |
| private void cmpFloatMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| String[] ifOpcode = new String[6]; | |
| ifOpcode[IfType.EQ.ordinal()] = "if-eqz"; | |
| ifOpcode[IfType.NE.ordinal()] = "if-nez"; | |
| ifOpcode[IfType.LE.ordinal()] = "if-lez"; | |
| ifOpcode[IfType.GE.ordinal()] = "if-gez"; | |
| ifOpcode[IfType.LT.ordinal()] = "if-ltz"; | |
| ifOpcode[IfType.GT.ordinal()] = "if-gtz"; | |
| FloatTestData test = (FloatTestData) parameters; | |
| String cmpInstruction; | |
| if (test.bias == Bias.LT) { | |
| cmpInstruction = " cmpl-float v0, v0, v1"; | |
| } else { | |
| cmpInstruction = " cmpg-float v0, v0, v1"; | |
| } | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.emptyList(), | |
| 2, | |
| " const v0, 0x" + Integer.toHexString(IntUtils.encodeFloat(test.a)), | |
| " const v1, 0x" + Integer.toHexString(IntUtils.encodeFloat(test.b)), | |
| cmpInstruction, | |
| " " + ifOpcode[test.type.ordinal()] + " v0, :label_2", | |
| " const v0, 0", | |
| ":label_1", | |
| " return v0", | |
| ":label_2", | |
| " const v0, 1", | |
| " goto :label_1"); | |
| } | |
| private void cmpFloatMethodChecker(DexEncodedMethod method, Object parameters) { | |
| FloatTestData test = (FloatTestData) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(test.expected ? 1 : 0, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| private void addCmpFloatFoldTests(SmaliBuilderWithCheckers testBuilder) { | |
| float[] testValues = | |
| new float[] { | |
| Float.NEGATIVE_INFINITY, -100.0f, -0.0f, 0.0f, 100.0f, Float.POSITIVE_INFINITY, Float.NaN | |
| }; | |
| // Build the test configuration. | |
| for (int i = 0; i < testValues.length; i++) { | |
| for (int j = 0; j < testValues.length; j++) { | |
| for (IfType type : IfType.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(testValues[i]) || Float.isNaN(testValues[j])) { | |
| // 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 == IfType.GE || type == IfType.GT) && bias == Bias.GT) { | |
| continue; | |
| } | |
| if ((type == IfType.LE || type == IfType.LT) && bias == Bias.LT) { | |
| continue; | |
| } | |
| } | |
| testBuilder.addTest( | |
| this::cmpFloatMethodBuilder, | |
| this::cmpFloatMethodChecker, | |
| new FloatTestData(testValues[i], testValues[j], type, bias)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| class DoubleTestData { | |
| final double a; | |
| final double b; | |
| final IfType type; | |
| final Bias bias; | |
| final boolean expected; | |
| DoubleTestData(double a, double b, IfType type, Bias bias) { | |
| this.a = a; | |
| this.b = b; | |
| this.type = type; | |
| this.bias = bias; | |
| switch (type) { | |
| case EQ: | |
| expected = a == b; | |
| break; | |
| case NE: | |
| expected = a != b; | |
| break; | |
| case LE: | |
| expected = a <= b; | |
| break; | |
| case GE: | |
| expected = a >= b; | |
| break; | |
| case LT: | |
| expected = a < b; | |
| break; | |
| case GT: | |
| expected = a > b; | |
| break; | |
| default: | |
| expected = false; | |
| assert false; | |
| } | |
| } | |
| } | |
| private void cmpDoubleMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| String[] ifOpcode = new String[6]; | |
| ifOpcode[IfType.EQ.ordinal()] = "if-eqz"; | |
| ifOpcode[IfType.NE.ordinal()] = "if-nez"; | |
| ifOpcode[IfType.LE.ordinal()] = "if-lez"; | |
| ifOpcode[IfType.GE.ordinal()] = "if-gez"; | |
| ifOpcode[IfType.LT.ordinal()] = "if-ltz"; | |
| ifOpcode[IfType.GT.ordinal()] = "if-gtz"; | |
| DoubleTestData test = (DoubleTestData) parameters; | |
| String cmpInstruction; | |
| if (test.bias == Bias.LT) { | |
| cmpInstruction = " cmpl-double v0, v0, v2"; | |
| } else { | |
| cmpInstruction = " cmpg-double v0, v0, v2"; | |
| } | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| Collections.emptyList(), | |
| 4, | |
| " const-wide v0, 0x" + Long.toHexString(LongUtils.encodeDouble(test.a)) + "L", | |
| " const-wide v2, 0x" + Long.toHexString(LongUtils.encodeDouble(test.b)) + "L", | |
| cmpInstruction, | |
| " " + ifOpcode[test.type.ordinal()] + " v0, :label_2", | |
| " const v0, 0", | |
| ":label_1", | |
| " return v0", | |
| ":label_2", | |
| " const v0, 1", | |
| " goto :label_1"); | |
| } | |
| private void cmpDoubleMethodChecker(DexEncodedMethod method, Object parameters) { | |
| DoubleTestData test = (DoubleTestData) parameters; | |
| DexCode code = method.getCode().asDexCode(); | |
| assertEquals(2, code.instructions.length); | |
| assertConstValue(test.expected ? 1 : 0, code.instructions[0]); | |
| assertTrue(code.instructions[1] instanceof DexReturn); | |
| } | |
| private void addCmpDoubleFoldTests(SmaliBuilderWithCheckers testBuilder) { | |
| double[] testValues = | |
| new double[] { | |
| Double.NEGATIVE_INFINITY, | |
| -100.0f, | |
| -0.0f, | |
| 0.0f, | |
| 100.0f, | |
| Double.POSITIVE_INFINITY, | |
| Double.NaN | |
| }; | |
| // Build the test configuration. | |
| for (int i = 0; i < testValues.length; i++) { | |
| for (int j = 0; j < testValues.length; j++) { | |
| for (IfType type : IfType.values()) { | |
| for (Bias bias : Bias.values()) { | |
| if (bias == Bias.NONE) { | |
| // Bias NONE is only for long comparison. | |
| continue; | |
| } | |
| if (Double.isNaN(testValues[i]) || Double.isNaN(testValues[j])) { | |
| // 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 == IfType.GE || type == IfType.GT) && bias == Bias.GT) { | |
| continue; | |
| } | |
| if ((type == IfType.LE || type == IfType.LT) && bias == Bias.LT) { | |
| continue; | |
| } | |
| } | |
| testBuilder.addTest( | |
| this::cmpDoubleMethodBuilder, | |
| this::cmpDoubleMethodChecker, | |
| new DoubleTestData(testValues[i], testValues[j], type, bias)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| private void cmpLongMethodBuilder(SmaliBuilder builder, String name, Object parameters) { | |
| long[] values = (long[]) (parameters); | |
| builder.addStaticMethod( | |
| "int", | |
| name, | |
| 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"); | |
| } | |
| private void cmpLongMethodChecker(DexEncodedMethod method, Object parameters) { | |
| long[] values = (long[]) (parameters); | |
| 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 DexReturn); | |
| } | |
| private void addCmpLongFold(SmaliBuilderWithCheckers testBuilder) { | |
| ImmutableList.of( | |
| new long[] {Long.MIN_VALUE, 1L}, | |
| new long[] {Long.MAX_VALUE, 1L}, | |
| new long[] {Long.MIN_VALUE, 0L}, | |
| new long[] {Long.MAX_VALUE, 0L}, | |
| new long[] {Long.MIN_VALUE, -1L}, | |
| new long[] {Long.MAX_VALUE, -1L}) | |
| .forEach( | |
| v -> testBuilder.addTest(this::cmpLongMethodBuilder, this::cmpLongMethodChecker, v)); | |
| } | |
| @Parameter(0) | |
| public TestParameters parameters; | |
| @Parameters(name = "{0}") | |
| public static TestParametersCollection data() { | |
| return getTestParameters().withDexRuntimes().build(); | |
| } | |
| @Test | |
| public void foldingTest() throws Exception { | |
| SmaliBuilderWithCheckers testBuilder = new SmaliBuilderWithCheckers(); | |
| addBinopFoldingTests(testBuilder); | |
| addDivIntFoldDivByZero(testBuilder); | |
| addDivIntFoldRemByZero(testBuilder); | |
| addNegFoldingTest(testBuilder); | |
| addShiftOperatorsFolding(testBuilder); | |
| addShiftOperatorsFoldingWide(testBuilder); | |
| addLogicalOperatorsFoldTests(testBuilder); | |
| addNotIntFoldTests(testBuilder); | |
| addNotLongFoldTests(testBuilder); | |
| addNegIntFoldTests(testBuilder); | |
| addNegLongFoldTests(testBuilder); | |
| addCmpFloatFoldTests(testBuilder); | |
| addCmpDoubleFoldTests(testBuilder); | |
| addCmpLongFold(testBuilder); | |
| runDex2Oat(testBuilder.builder.build(), parameters.getDexVm()); | |
| testBuilder.run(); | |
| } | |
| } |