| // 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.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.errors.Unreachable; |
| 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.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| 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; |
| |
| public class ConstantFoldingTest extends SmaliTestBase { |
| |
| @FunctionalInterface |
| public interface TriConsumer<T, U, V> { |
| public 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().method.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 long floatBits(float f) { |
| return Float.floatToIntBits(f); |
| } |
| |
| private long doubleBits(double d) { |
| return Double.doubleToLongBits(d); |
| } |
| |
| private ImmutableList<Long> arguments = ImmutableList.of(1L, 2L, 3L, 4L); |
| private ImmutableList<Long> floatArguments = ImmutableList.of( |
| floatBits(1.0f), floatBits(2.0f), floatBits(3.0f), floatBits(4.0f)); |
| private ImmutableList<Long> doubleArguments = ImmutableList.of( |
| doubleBits(1.0), doubleBits(2.0), doubleBits(3.0), doubleBits(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 ReturnWide); |
| } else { |
| assertTrue(code.instructions[0] instanceof SingleConstant); |
| assertEquals( |
| test.result.longValue(), |
| (long) ((SingleConstant) code.instructions[0]).decodedValue()); |
| assertTrue(code.instructions[1] instanceof Return); |
| } |
| } |
| |
| 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, floatBits(10.0f))); |
| addBinopTest(testBuilder, |
| new BinopTestData("double", "add", doubleArguments, doubleBits(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, floatBits(24.0f))); |
| addBinopTest(testBuilder, |
| new BinopTestData("double", "mul", doubleArguments, doubleBits(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(), floatBits(-2.0f))); |
| addBinopTest(testBuilder, |
| new BinopTestData("double", "sub", doubleArguments.reverse(), doubleBits(-2.0))); |
| |
| // Div tests. |
| { |
| 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)); |
| |
| addBinopTest(testBuilder, new BinopTestData("int", "div", arguments, 1L)); |
| addBinopTest(testBuilder, new BinopTestData("long", "div", arguments, 1L)); |
| addBinopTest(testBuilder, |
| new BinopTestData("float", "div", floatArguments, floatBits(1.0f))); |
| addBinopTest(testBuilder, |
| new BinopTestData("double", "div", doubleArguments, doubleBits(1.0))); |
| } |
| |
| // Rem tests. |
| { |
| 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)); |
| |
| addBinopTest(testBuilder, new BinopTestData("int", "rem", arguments, 2L)); |
| addBinopTest(testBuilder, new BinopTestData("long", "rem", arguments, 2L)); |
| addBinopTest(testBuilder, |
| new BinopTestData("float", "rem", floatArguments, floatBits(2.0f))); |
| addBinopTest(testBuilder, |
| new BinopTestData("double", "rem", doubleArguments, doubleBits(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 Const4); |
| assertTrue(code.instructions[1] instanceof DivIntLit8); |
| assertEquals(0, ((DivIntLit8) code.instructions[1]).CC); |
| assertTrue(code.instructions[2] instanceof Return); |
| }, |
| 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 Const4); |
| assertTrue(code.instructions[1] instanceof RemIntLit8); |
| assertEquals(0, ((RemIntLit8) code.instructions[1]).CC); |
| assertTrue(code.instructions[2] instanceof Return); |
| }, |
| 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 ReturnWide); |
| } else { |
| assertTrue(code.instructions[0] instanceof SingleConstant); |
| assertEquals( |
| test.result.longValue(), (long) ((SingleConstant) code.instructions[0]).decodedValue()); |
| assertTrue(code.instructions[1] instanceof Return); |
| } |
| } |
| |
| private void addUnopTest(SmaliBuilderWithCheckers testBuilder, UnopTestData test) { |
| testBuilder.addTest(this::unopMethodBuilder, this::unopMethodChecker, test); |
| } |
| |
| private void addNegFoldingTest(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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", floatBits(2.0f), floatBits(-2.0f))); |
| addUnopTest(testBuilder, new UnopTestData("float", "neg", floatBits(-2.0f), floatBits(2.0f))); |
| addUnopTest(testBuilder, new UnopTestData("float", "neg", floatBits(0.0f), floatBits(-0.0f))); |
| addUnopTest(testBuilder, new UnopTestData("float", "neg", floatBits(-0.0f), floatBits(0.0f))); |
| addUnopTest(testBuilder, new UnopTestData("double", "neg", doubleBits(2.0), doubleBits(-2.0))); |
| addUnopTest(testBuilder, new UnopTestData("double", "neg", doubleBits(-2.0), doubleBits(2.0))); |
| addUnopTest(testBuilder, new UnopTestData("double", "neg", doubleBits(0.0), doubleBits(-0.0))); |
| addUnopTest(testBuilder, new UnopTestData("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()); |
| } |
| |
| 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 Return); |
| } |
| |
| 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 Return); |
| } |
| |
| 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 ReturnWide); |
| } |
| |
| 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 Return); |
| } |
| |
| private void addNotIntFoldTests(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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 ReturnWide); |
| } |
| |
| |
| private void addNotLongFoldTests(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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 Return); |
| } |
| |
| private void addNegIntFoldTests(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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 ReturnWide); |
| } |
| |
| private void addNegLongFoldTests(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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 Type type; |
| final Bias bias; |
| final boolean expected; |
| |
| FloatTestData(float a, float b, Type 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[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"; |
| |
| 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(Float.floatToRawIntBits(test.a)), |
| " const v1, 0x" + Integer.toHexString(Float.floatToRawIntBits(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 Return); |
| } |
| |
| private void addCmpFloatFoldTests(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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 (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(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 == Type.GE || type == Type.GT) && bias == Bias.GT) { |
| continue; |
| } |
| if ((type == Type.LE || type == Type.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 Type type; |
| final Bias bias; |
| final boolean expected; |
| |
| DoubleTestData(double a, double b, Type 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[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"; |
| |
| 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(Double.doubleToRawLongBits(test.a)) + "L", |
| " const-wide v2, 0x" + Long.toHexString(Double.doubleToRawLongBits(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 Return); |
| } |
| |
| |
| private void addCmpDoubleFoldTests(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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 (Type type : Type.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 == Type.GE || type == Type.GT) && bias == Bias.GT) { |
| continue; |
| } |
| if ((type == Type.LE || type == Type.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 Return); |
| } |
| |
| private void addCmpLongFold(SmaliBuilderWithCheckers testBuilder) throws Exception { |
| 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)); |
| } |
| |
| @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()); |
| testBuilder.run(); |
| } |
| |
| } |