blob: 9990254c074c60ddd2490af71321b46f524894e9 [file]
// Copyright (c) 2025, 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.optimize.numberconversion;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.timing.Timing;
import com.google.common.collect.ImmutableList;
import java.util.function.Consumer;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class NumberConversionTestBase extends TestBase implements Opcodes {
/**
* Tests that {@code computation} is reduced to a single constant of type {@code computationType}
* represented by long value {@code expectedRawValue}.
*
* @param computation should leave a single value on the stack and not use more than 10 max stack
* size.
* @param computationType the type of the value of {@code computation}.
* @param expectedRawValue the expected constant after optimization.
*/
protected void testConversion(
Consumer<MethodVisitor> computation, NumericType computationType, long expectedRawValue)
throws Exception {
ValueType computationValueType = ValueType.fromNumericType(computationType);
byte[] testClass = dump(computation, computationValueType);
AndroidApp androidApp = AndroidApp.builder().addClassProgramData(testClass).build();
AppView<?> appView = computeAppView(androidApp);
DexItemFactory itemFactory = appView.dexItemFactory();
DexMethod mainMethod =
itemFactory.createMethod(
itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(CLASS_NAME)),
itemFactory.createProto(itemFactory.voidType, itemFactory.stringArrayType),
"main");
IRCode ir =
appView
.definitionFor(mainMethod)
.asProgramMethod()
.buildIR(appView, MethodConversionOptions.nonConverting());
assertTrue(ir.streamInstructions().anyMatch(Instruction::isNumberConversion));
// Run optimization and dead code elimination.
CodeRewriterResult result =
new SparseConditionalConstantPropagation(appView).run(ir, null, null, Timing.empty());
assertEquals(OptionalBool.TRUE, result.hasChanged());
new DeadCodeRemover(appView).run(ir, Timing.empty());
// Assertions.
ImmutableList<ConstNumber> constNumbers =
ImmutableList.copyOf(
ir.streamInstructions()
.filter(Instruction::isConstNumber)
.map(Instruction::asConstNumber)
.iterator());
assertEquals(1, constNumbers.size());
ConstNumber cst = constNumbers.get(0);
assertEquals(computationValueType, cst.outType());
assertEquals(expectedRawValue, cst.getRawValue());
}
private static final String CLASS_NAME = "TestClass";
/**
* Constructs the below method where {@code [..]} describes meta values.
*
* <pre>{@code
* public class [CLASS_NAME] {
* public static void main(String[] args) {
* System.out.println([computation]);
* }
* }
* }</pre>
*
* @param computation should leave a single value on the stack and not use more than 10 max stack
* size.
* @param computationType the type of the value returned by {@code computation}.
*/
private static byte[] dump(Consumer<MethodVisitor> computation, ValueType computationType) {
char computationTypeDescriptor;
switch (computationType) {
case INT:
computationTypeDescriptor = 'I';
break;
case FLOAT:
computationTypeDescriptor = 'F';
break;
case LONG:
computationTypeDescriptor = 'J';
break;
case DOUBLE:
computationTypeDescriptor = 'D';
break;
default:
throw new IllegalArgumentException("No code gen for computationType " + computationType);
}
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(V1_8, ACC_PUBLIC, CLASS_NAME, null, "java/lang/Object", null);
MethodVisitor methodVisitor =
classWriter.visitMethod(
ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
computation.accept(methodVisitor);
methodVisitor.visitMethodInsn(
INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(" + computationTypeDescriptor + ")V",
false);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(1 + 10, 1);
methodVisitor.visitEnd();
classWriter.visitEnd();
return classWriter.toByteArray();
}
}