Implement IR primitives for number comparisons in JVM BE
This commit is contained in:
+25
-48
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -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);
|
||||
|
||||
|
||||
+29
-16
@@ -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"
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user