Implement IR primitives for number comparisons in JVM BE

This commit is contained in:
Dmitry Petrov
2018-02-02 11:49:44 +03:00
parent bf18186045
commit 4776535205
9 changed files with 235 additions and 70 deletions
@@ -16,20 +16,13 @@
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo
import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.AsmUtil.comparisonOperandType
import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.IrPrimitiveCallBase
import org.jetbrains.kotlin.ir.expressions.impl.IrUnaryPrimitiveImpl
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.lang.UnsupportedOperationException
@@ -45,10 +38,14 @@ class CompareTo : IntrinsicMethod() {
}
}
override fun toCallable(expression: IrMemberAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
override fun toCallable(
expression: IrMemberAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
val parameterType = comparisonOperandType(
expressionType(expression.dispatchReceiver ?: expression.extensionReceiver!!, context),
signature.valueParameters.single().asmType
expressionType(expression.dispatchReceiver ?: expression.extensionReceiver!!, context),
signature.valueParameters.single().asmType
)
return IrIntrinsicFunction.create(expression, signature, context, listOf(parameterType, parameterType)) {
genInvoke(parameterType, it)
@@ -57,44 +54,24 @@ class CompareTo : IntrinsicMethod() {
}
class IrCompareTo : IntrinsicMethod() {
override fun toCallable(expression: IrMemberAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
assert(expression is IrPrimitiveCallBase)
val compareCall = expression.getValueArgument(0) as IrCall
val args = compareCall.receiverAndArgs()
val argTypes = args.asmTypes(context)
val leftType = argTypes[0]
val rightType = argTypes[1]
val parameterType = comparisonOperandType(leftType, rightType)
class PrimitiveComparison(
private val primitiveNumberType: KotlinType,
private val operatorToken: KtSingleValueToken
) : IntrinsicMethod() {
val newSignature = context.state.typeMapper.mapSignatureSkipGeneric(compareCall.descriptor, OwnerKind.IMPLEMENTATION)
return object : IrIntrinsicFunction(compareCall, newSignature, context, listOf(parameterType, parameterType)) {
override fun invoke(v: InstructionAdapter, codegen: ExpressionCodegen, data: BlockInfo): StackValue {
val isPrimitiveIntrinsic = codegen.intrinsics.intrinsics.getIntrinsic(compareCall.descriptor) != null
val operationType: Type
val leftValue: StackValue
val rightValue: StackValue
if (isPrimitive(leftType) && isPrimitive(rightType) && isPrimitiveIntrinsic) {
operationType = comparisonOperandType(leftType, rightType)
leftValue = codegen.gen(args[0], operationType, data)
rightValue = codegen.gen(args[1], operationType,data)
}
else {
operationType = Type.INT_TYPE
leftValue = codegen.gen(compareCall, data)
rightValue = StackValue.constant(0, operationType)
}
val origin = expression.origin
val token = when (origin) {
IrStatementOrigin.GT -> KtTokens.GT
IrStatementOrigin.GTEQ -> KtTokens.GTEQ
IrStatementOrigin.LT -> KtTokens.LT
IrStatementOrigin.LTEQ -> KtTokens.LTEQ
else -> TODO()
}
StackValue.cmp(token, operationType, leftValue, rightValue).put(Type.BOOLEAN_TYPE, v)
return StackValue.onStack(Type.BOOLEAN_TYPE)
override fun toCallable(
expression: IrMemberAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
val parameterType = context.state.typeMapper.mapType(primitiveNumberType)
return object : IrIntrinsicFunction(expression, signature, context, listOf(parameterType, parameterType)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
StackValue.cmp(operatorToken, parameterType, StackValue.onStack(parameterType), StackValue.onStack(parameterType))
.put(Type.BOOLEAN_TYPE, v)
}
}
}
}
@@ -18,17 +18,27 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.AsmUtil.*
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class Equals(val operator: IElementType) : IntrinsicMethod() {
override fun toCallable(expression: IrMemberAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction {
override fun toCallable(
expression: IrMemberAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
val receiverAndArgs = expression.receiverAndArgs().apply {
assert(size == 2) { "Equals expects 2 arguments, but ${joinToString()}" }
}
@@ -40,8 +50,7 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
rightType = boxType(rightType)
}
return object: IrIntrinsicFunction(expression, signature, context, listOf(leftType, rightType)) {
return object : IrIntrinsicFunction(expression, signature, context, listOf(leftType, rightType)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
val opToken = expression.origin
@@ -49,8 +58,7 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
// TODO: always casting to the type of the left operand in case of primitives looks wrong
val operandType = if (isPrimitive(leftType)) leftType else OBJECT_TYPE
StackValue.cmp(operator, operandType, StackValue.onStack(leftType), StackValue.onStack(rightType))
}
else {
} else {
genEqualsForExpressionsOnStack(operator, StackValue.onStack(leftType), StackValue.onStack(rightType))
}
value.put(Type.BOOLEAN_TYPE, v)
@@ -58,3 +66,72 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
}
}
}
class Ieee754Equals(val operandType: Type) : IntrinsicMethod() {
private val boxedOperandType = AsmUtil.boxType(operandType)
override fun toCallable(
expression: IrMemberAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction {
class Ieee754AreEqual(
val left: Type,
val right: Type
) : IrIntrinsicFunction(expression, signature, context, listOf(left, right)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
v.invokestatic(
IntrinsicMethods.INTRINSICS_CLASS_NAME, "areEqual",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, left, right),
false
)
}
}
val arg0Type = expression.getValueArgument(0)!!.type
if (!arg0Type.isPrimitiveNumberOrNullableType()) throw AssertionError("Should be primitive or nullable primitive type: $arg0Type")
val arg1Type = expression.getValueArgument(1)!!.type
if (!arg1Type.isPrimitiveNumberOrNullableType()) throw AssertionError("Should be primitive or nullable primitive type: $arg1Type")
val arg0isNullable = arg0Type.isMarkedNullable
val arg1isNullable = arg1Type.isMarkedNullable
return when {
!arg0isNullable && !arg1isNullable ->
object : IrIntrinsicFunction(expression, signature, context, listOf(operandType, operandType)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
StackValue.cmp(KtTokens.EQEQ, operandType, StackValue.onStack(operandType), StackValue.onStack(operandType))
.put(Type.BOOLEAN_TYPE, v)
}
}
arg0isNullable && !arg1isNullable ->
Ieee754AreEqual(boxedOperandType, operandType)
!arg0isNullable && arg1isNullable ->
Ieee754AreEqual(operandType, boxedOperandType)
else ->
Ieee754AreEqual(boxedOperandType, boxedOperandType)
}
}
}
class TotalOrderEquals(operandType: Type) : IntrinsicMethod() {
private val boxedType = AsmUtil.boxType(operandType)
override fun toCallable(
expression: IrMemberAccessExpression,
signature: JvmMethodSignature,
context: JvmBackendContext
): IrIntrinsicFunction =
object : IrIntrinsicFunction(expression, signature, context, listOf(boxedType, boxedType)) {
override fun genInvokeInstruction(v: InstructionAdapter) {
v.invokevirtual(boxedType.internalName, "equals", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, AsmTypes.OBJECT_TYPE), false)
}
}
}
@@ -27,8 +27,10 @@ import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import org.jetbrains.org.objectweb.asm.Type;
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.*;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
@@ -92,7 +94,13 @@ public class IntrinsicMethods {
for (PrimitiveType type : PrimitiveType.values()) {
FqName typeFqName = type.getTypeFqName();
declareIntrinsicFunction(typeFqName, "equals", 1, EQUALS);
Type asmPrimitiveType = AsmTypes.valueTypeForPrimitive(type);
if (asmPrimitiveType == Type.FLOAT_TYPE || asmPrimitiveType == Type.DOUBLE_TYPE) {
declareIntrinsicFunction(typeFqName, "equals", 1, new TotalOrderEquals(asmPrimitiveType));
}
else {
declareIntrinsicFunction(typeFqName, "equals", 1, EQUALS);
}
declareIntrinsicFunction(typeFqName, "hashCode", 0, HASH_CODE);
declareIntrinsicFunction(typeFqName, "toString", 0, TO_STRING);
@@ -18,9 +18,13 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.org.objectweb.asm.Type
class IrIntrinsicMethods(irBuiltIns: IrBuiltIns) {
@@ -28,25 +32,34 @@ class IrIntrinsicMethods(irBuiltIns: IrBuiltIns) {
private val irMapping = hashMapOf<CallableMemberDescriptor, IntrinsicMethod>()
private fun createPrimitiveComparisonIntrinsics(typeToIrFun: Map<SimpleType, IrSimpleFunction>, operator: KtSingleValueToken) {
for ((type, irFun) in typeToIrFun) {
irMapping[irFun.descriptor] = PrimitiveComparison(type, operator)
}
}
init {
irMapping.put(irBuiltIns.eqeq, Equals(KtTokens.EQEQ))
irMapping.put(irBuiltIns.eqeqeq, Equals(KtTokens.EQEQEQ))
irMapping.put(irBuiltIns.booleanNot, Not())
val compare = IrCompareTo()
irMapping.put(irBuiltIns.lt0, compare)
irMapping.put(irBuiltIns.lteq0, compare)
irMapping.put(irBuiltIns.gt0, compare)
irMapping.put(irBuiltIns.gteq0, compare)
irMapping.put(irBuiltIns.enumValueOf, IrEnumValueOf())
irMapping.put(irBuiltIns.noWhenBranchMatchedException, IrNoWhenBranchMatchedException())
irMapping.put(irBuiltIns.throwNpe, ThrowNPE())
irMapping[irBuiltIns.eqeq] = Equals(KtTokens.EQEQ)
irMapping[irBuiltIns.eqeqeq] = Equals(KtTokens.EQEQEQ)
irMapping[irBuiltIns.ieee754equalsFunByOperandType[irBuiltIns.float]!!.descriptor] = Ieee754Equals(Type.FLOAT_TYPE)
irMapping[irBuiltIns.ieee754equalsFunByOperandType[irBuiltIns.double]!!.descriptor] = Ieee754Equals(Type.DOUBLE_TYPE)
irMapping[irBuiltIns.booleanNot] = Not()
createPrimitiveComparisonIntrinsics(irBuiltIns.lessFunByOperandType, KtTokens.LT)
createPrimitiveComparisonIntrinsics(irBuiltIns.lessOrEqualFunByOperandType, KtTokens.LTEQ)
createPrimitiveComparisonIntrinsics(irBuiltIns.greaterFunByOperandType, KtTokens.GT)
createPrimitiveComparisonIntrinsics(irBuiltIns.greaterOrEqualFunByOperandType, KtTokens.GTEQ)
irMapping[irBuiltIns.enumValueOf] = IrEnumValueOf()
irMapping[irBuiltIns.noWhenBranchMatchedException] = IrNoWhenBranchMatchedException()
irMapping[irBuiltIns.throwNpe] = ThrowNPE()
}
fun getIntrinsic(descriptor: CallableMemberDescriptor): IntrinsicMethod? {
return intrinsics.getIntrinsic(descriptor) ?:
(if (descriptor is PropertyAccessorDescriptor)
intrinsics.getIntrinsic(DescriptorUtils.unwrapFakeOverride(descriptor.correspondingProperty))
else null) ?: irMapping[descriptor.original]
intrinsics.getIntrinsic(descriptor)?.let { return it }
if (descriptor is PropertyAccessorDescriptor) {
return intrinsics.getIntrinsic(DescriptorUtils.unwrapFakeOverride(descriptor.correspondingProperty))
}
return irMapping[descriptor.original]
}
}
@@ -0,0 +1,15 @@
fun ltDD(x: Comparable<Double>, y: Double) =
x is Double && x < y
fun ltCD(x: Comparable<Double>, y: Double) =
x < y
fun box(): String {
val Z = 0.0
val NZ = -0.0
if (ltDD(NZ, Z)) return "Fail 1"
if (!ltCD(NZ, Z)) return "Fail 2"
return "OK"
}
@@ -0,0 +1,17 @@
fun eqeq(x: Any, y: Any) =
x is Double && y is Double && x == y
fun anyEqeq(x: Any, y: Any) =
x == y
fun box(): String {
val Z = 0.0
val NZ = -0.0
if (!(Z == NZ)) return "Fail 1"
if (!eqeq(Z, NZ)) return "Fail 2"
if (anyEqeq(Z, NZ)) return "Fail A"
return "OK"
}
@@ -0,0 +1,17 @@
fun eqeq(x: Any, y: Any) =
x is Float && y is Float && x == y
fun anyEqeq(x: Any, y: Any) =
x == y
fun box(): String {
val Z = 0.0F
val NZ = -0.0F
if (!(Z == NZ)) return "Fail 1"
if (!eqeq(Z, NZ)) return "Fail 2"
if (anyEqeq(Z, NZ)) return "Fail A"
return "OK"
}
@@ -0,0 +1,8 @@
fun ltDI(x: Any, y: Any) =
x is Double && y is Int && x < y
fun box(): String {
if (ltDI(-0.0, 0)) return "Fail 1"
return "OK"
}
@@ -123,4 +123,37 @@ public class IrOnlyBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTest
doTest(fileName);
}
}
@TestMetadata("compiler/testData/ir/box/primitiveNumberComparisons")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PrimitiveNumberComparisons extends AbstractIrBlackBoxCodegenTest {
public void testAllFilesPresentInPrimitiveNumberComparisons() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/ir/box/primitiveNumberComparisons"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("comparableToDouble.kt")
public void testComparableToDouble() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/box/primitiveNumberComparisons/comparableToDouble.kt");
doTest(fileName);
}
@TestMetadata("doubleEqeq.kt")
public void testDoubleEqeq() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/box/primitiveNumberComparisons/doubleEqeq.kt");
doTest(fileName);
}
@TestMetadata("floatEqeq.kt")
public void testFloatEqeq() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/box/primitiveNumberComparisons/floatEqeq.kt");
doTest(fileName);
}
@TestMetadata("mixedNumberTypes.kt")
public void testMixedNumberTypes() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/box/primitiveNumberComparisons/mixedNumberTypes.kt");
doTest(fileName);
}
}
}