| // 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(); | 
 |   } | 
 |  | 
 | } |