From a2f89cc71172f431afc467c2a452918e1b6fa1ed Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Wed, 4 Jul 2018 18:10:10 +0300 Subject: [PATCH] [JS IR BE] Eqeq operator lowering. IntrinsicifyCallsLowering.kt refactoring --- .../kotlin/ir/backend/js/JsIntrinsics.kt | 1 - .../js/lower/IntrinsicifyCallsLowering.kt | 267 +++++++++++------- .../stdlib/js/irRuntime/numberConversion.kt | 2 - 3 files changed, 161 insertions(+), 109 deletions(-) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 91b6e4c599b..46f6db1aef8 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -93,7 +93,6 @@ class JsIntrinsics( // Number conversions: - val jsAsIs = getInternalFunction("asIs") // as-is conversion. Call can be replaced with first paramenter val jsNumberToByte = getInternalFunction("numberToByte") val jsNumberToDouble = getInternalFunction("numberToDouble") val jsNumberToInt = getInternalFunction("numberToInt") diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt index 9a3bb854d70..fe7d9aad4cb 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.utils.isNullable +import org.jetbrains.kotlin.backend.common.utils.isSubtypeOf import org.jetbrains.kotlin.backend.common.utils.isSubtypeOfClass import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder @@ -14,6 +15,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.ConversionNames import org.jetbrains.kotlin.ir.backend.js.utils.Namer import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames import org.jetbrains.kotlin.ir.backend.js.utils.isFakeOverriddenFromAny +import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction @@ -23,30 +25,32 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.originalKotlinType +import org.jetbrains.kotlin.ir.util.isNullConst import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.types.SimpleType +private typealias MemberToTransformer = MutableMap IrExpression> +private typealias SymbolToTransformer = MutableMap IrExpression> + class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileLoweringPass { private val intrinsics = context.intrinsics private val irBuiltIns = context.irBuiltIns // TODO: should/can we unify these maps? - private val memberToTransformer: Map IrExpression> - private val memberToIrFunction: Map - private val symbolToIrFunction: Map - private val nameToIrTransformer: Map IrExpression> + private val memberToTransformer: MemberToTransformer + private val symbolToTransformer: SymbolToTransformer + private val nameToTransformer: Map IrExpression> init { - memberToIrFunction = mutableMapOf() - symbolToIrFunction = mutableMapOf() + symbolToTransformer = mutableMapOf() memberToTransformer = mutableMapOf() - nameToIrTransformer = mutableMapOf() + nameToTransformer = mutableMapOf() val primitiveNumbers = context.irBuiltIns.run { listOf(intType, shortType, byteType, floatType, doubleType) } - memberToIrFunction.run { + memberToTransformer.run { for (type in primitiveNumbers) { op(type, OperatorNames.UNARY_PLUS, intrinsics.jsUnaryPlus) op(type, OperatorNames.UNARY_MINUS, intrinsics.jsUnaryMinus) @@ -77,18 +81,17 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL // TODO: Add Char and Number conversions irBuiltIns.byteType.let { - op(it, ConversionNames.TO_BYTE, intrinsics.jsAsIs) - op(it, ConversionNames.TO_DOUBLE, intrinsics.jsAsIs) - op(it, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) - op(it, ConversionNames.TO_INT, intrinsics.jsAsIs) - op(it, ConversionNames.TO_SHORT, intrinsics.jsAsIs) - op(it, ConversionNames.TO_LONG, intrinsics.jsToLong) + op(it, ConversionNames.TO_BYTE, ::useDispatchReceiver) + op(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver) + op(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver) + op(it, ConversionNames.TO_INT, ::useDispatchReceiver) + op(it, ConversionNames.TO_SHORT, ::useDispatchReceiver) } for (type in listOf(irBuiltIns.floatType, irBuiltIns.doubleType)) { op(type, ConversionNames.TO_BYTE, intrinsics.jsNumberToByte) - op(type, ConversionNames.TO_DOUBLE, intrinsics.jsAsIs) - op(type, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) + op(type, ConversionNames.TO_DOUBLE, ::useDispatchReceiver) + op(type, ConversionNames.TO_FLOAT, ::useDispatchReceiver) op(type, ConversionNames.TO_INT, intrinsics.jsNumberToInt) op(type, ConversionNames.TO_SHORT, intrinsics.jsNumberToShort) op(type, ConversionNames.TO_LONG, intrinsics.jsNumberToLong) @@ -96,27 +99,25 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL irBuiltIns.intType.let { op(it, ConversionNames.TO_BYTE, intrinsics.jsToByte) - op(it, ConversionNames.TO_DOUBLE, intrinsics.jsAsIs) - op(it, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) - op(it, ConversionNames.TO_INT, intrinsics.jsAsIs) + op(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver) + op(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver) + op(it, ConversionNames.TO_INT, ::useDispatchReceiver) op(it, ConversionNames.TO_SHORT, intrinsics.jsToShort) op(it, ConversionNames.TO_LONG, intrinsics.jsToLong) } irBuiltIns.shortType.let { op(it, ConversionNames.TO_BYTE, intrinsics.jsToByte) - op(it, ConversionNames.TO_DOUBLE, intrinsics.jsAsIs) - op(it, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) - op(it, ConversionNames.TO_INT, intrinsics.jsAsIs) - op(it, ConversionNames.TO_SHORT, intrinsics.jsAsIs) - op(it, ConversionNames.TO_LONG, intrinsics.jsToLong) + op(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver) + op(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver) + op(it, ConversionNames.TO_INT, ::useDispatchReceiver) + op(it, ConversionNames.TO_SHORT, ::useDispatchReceiver) } } - symbolToIrFunction.run { + symbolToTransformer.run { add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq) - // TODO: implement it a right way - add(irBuiltIns.eqeqSymbol, intrinsics.jsEquals.owner) + add(irBuiltIns.eqeqSymbol, ::transformEqeqOperator) // TODO: implement it a right way add(irBuiltIns.ieee754equalsFunByOperandType, intrinsics.jsEqeqeq) @@ -153,7 +154,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } } - nameToIrTransformer.run { + nameToTransformer.run { addWithPredicate( Name.special(Namer.KCALLABLE_GET_NAME), { call -> @@ -193,7 +194,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL { call -> irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true) } ) - put(Name.identifier("equals"), ::transformEquals) + put(Name.identifier("equals"), ::transformEqualsMethodCall) } } @@ -225,20 +226,8 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL if (call is IrCall) { val symbol = call.symbol - // TODO create another map for this? Lower == to equals beforehand? - if (symbol == irBuiltIns.eqeqSymbol) { - val lhs = call.getValueArgument(0)!! - val rhs = call.getValueArgument(1)!! - - return when (translateEquals(lhs.type, rhs.type)) { - is IdentityOperator -> irCall(call, intrinsics.jsEqeqeq.symbol) - is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol) - else -> irCall(call, intrinsics.jsEquals) - } - } - - symbolToIrFunction[symbol]?.let { - return irCall(call, it.symbol) + symbolToTransformer[symbol]?.let { + return it(call) } // TODO: get rid of unbound symbols @@ -246,21 +235,12 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL (symbol.owner as? IrFunction)?.dispatchReceiverParameter?.let { val key = SimpleMemberKey(it.type, symbol.owner.name) - - memberToIrFunction[key]?.let { - if (it == intrinsics.jsAsIs) { - return call.dispatchReceiver!! - } - // TODO: don't apply intrinsics when type of receiver or argument is Long - return irCall(call, it, dispatchReceiverAsFirstArgument = true) - } - memberToTransformer[key]?.let { return it(call) } } - nameToIrTransformer[symbol.owner.name]?.let { + nameToTransformer[symbol.owner.name]?.let { return it(call) } } @@ -271,6 +251,56 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL }, null) } + private fun useDispatchReceiver(call: IrCall): IrExpression { + return call.dispatchReceiver!! + } + + private fun transformEqeqOperator(call: IrCall): IrExpression { + val lhs = call.getValueArgument(0)!! + val rhs = call.getValueArgument(1)!! + + // Special optimization for " == null" + if (lhs.isNullConst() || rhs.isNullConst()) + return irCall(call, intrinsics.jsEqeq.symbol) + + return when (translateEquals(lhs.type, rhs.type)) { + is IdentityOperator -> irCall(call, intrinsics.jsEqeqeq.symbol) + is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol) + is RuntimeFunctionCall -> irCall(call, intrinsics.jsEquals) + is RuntimeOrMethodCall -> { + assert(!lhs.type.isNullable()) + val equalsMethod = lhs.type.findEqualsMethod(rhs.type) + if (equalsMethod != null) { + irCall(call, equalsMethod.symbol, firstArgumentAsDispatchReceiver = true) + } else { + irCall(call, intrinsics.jsEquals) + } + } + } + } + + private fun IrType.findEqualsMethod(rhs: IrType): IrSimpleFunction? { + val classifier = classifierOrNull ?: return null + if (!classifier.isBound) return null + return ((classifier.owner as? IrClass) ?: return null).declarations + .filterIsInstance() + .filter { + it.name == Name.identifier("equals") + && it.valueParameters.size == 1 + && rhs.isSubtypeOf(it.valueParameters[0].type) + && !it.descriptor.isFakeOverriddenFromAny() + } + .maxWith( // Find the most specific function + Comparator { f1, f2 -> + val t1 = f1.valueParameters[0].type + val t2 = f2.valueParameters[0].type + if (t1.isSubtypeOf(t2)) { + if (t2.isSubtypeOf(t1)) 0 else 1 + } else -1 + } + ) + } + private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call -> assert(call.valueArgumentsCount == 1) val arg = call.getValueArgument(0)!! @@ -323,7 +353,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } } - private fun transformEquals(call: IrCall): IrExpression { + private fun transformEqualsMethodCall(call: IrCall): IrExpression { if (call.superQualifier != null) return call val symbol = call.symbol if (!symbol.isBound) return call @@ -356,8 +386,7 @@ fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean { }?.type ?: return false return receiverParameterType.run { - this.isArray() || - this.isAny() || this.isNullable() || this is IrDynamicType || this.isString() + isArray() || isAny() || isNullable() || this is IrDynamicType || isString() } } @@ -397,65 +426,67 @@ JsNumber -- type lowered to JS Number */ sealed class EqualityLoweringType -class IdentityOperator : EqualityLoweringType() -class EqualityOperator : EqualityLoweringType() -class RuntimeFunctionCall : EqualityLoweringType() -class RuntimeOrMethodCall : EqualityLoweringType() +object IdentityOperator : EqualityLoweringType() +object EqualityOperator : EqualityLoweringType() +object RuntimeFunctionCall : EqualityLoweringType() +object RuntimeOrMethodCall : EqualityLoweringType() fun translateEquals(lhs: IrType, rhs: IrType): EqualityLoweringType = when { + lhs is IrDynamicType -> EqualityOperator lhs.isJsNumber() -> translateEqualsForJsNumber(rhs) lhs.isNullableJsNumber() -> translateEqualsForNullableJsNumber(rhs) lhs.isLong() -> translateEqualsForLong(rhs) lhs.isNullableLong() -> translateEqualsForNullableLong(rhs) lhs.isBoolean() -> translateEqualsForBoolean(rhs) lhs.isNullableBoolean() -> translateEqualsForNullableBoolean(rhs) - else -> RuntimeOrMethodCall() + lhs.isNullable() -> RuntimeFunctionCall + else -> RuntimeOrMethodCall } fun translateEqualsForJsNumber(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator() - rhs.isLong() || rhs.isNullableLong() -> EqualityOperator() - rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator() - else -> RuntimeFunctionCall() + rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator + rhs.isLong() || rhs.isNullableLong() -> EqualityOperator + rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator + else -> RuntimeFunctionCall } fun translateEqualsForNullableJsNumber(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() -> IdentityOperator() - rhs.isNullableJsNumber() -> EqualityOperator() - rhs.isLong() || rhs.isNullableLong() -> EqualityOperator() - rhs.isBoolean() -> IdentityOperator() - else -> RuntimeFunctionCall() + rhs.isJsNumber() -> IdentityOperator + rhs.isNullableJsNumber() -> EqualityOperator + rhs.isLong() || rhs.isNullableLong() -> EqualityOperator + rhs.isBoolean() -> IdentityOperator + else -> RuntimeFunctionCall } fun translateEqualsForLong(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator() - rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall() - rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator() - else -> RuntimeFunctionCall() + rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator + rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall + rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator + else -> RuntimeFunctionCall } fun translateEqualsForNullableLong(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator() - rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall() - rhs.isBoolean() -> IdentityOperator() - else -> RuntimeFunctionCall() + rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator + rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall + rhs.isBoolean() -> IdentityOperator + else -> RuntimeFunctionCall } fun translateEqualsForBoolean(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator() - rhs.isLong() || rhs.isNullableLong() -> IdentityOperator() - rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator() - else -> RuntimeFunctionCall() + rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator + rhs.isLong() || rhs.isNullableLong() -> IdentityOperator + rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator + else -> RuntimeFunctionCall } fun translateEqualsForNullableBoolean(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() -> IdentityOperator() - rhs.isNullableJsNumber() -> RuntimeFunctionCall() - rhs.isLong() -> IdentityOperator() - rhs.isNullableLong() -> RuntimeFunctionCall() - rhs.isBoolean() -> IdentityOperator() - rhs.isNullableBoolean() -> EqualityOperator() - else -> RuntimeFunctionCall() + rhs.isJsNumber() -> IdentityOperator + rhs.isNullableJsNumber() -> RuntimeFunctionCall + rhs.isLong() -> IdentityOperator + rhs.isNullableLong() -> RuntimeFunctionCall + rhs.isBoolean() -> IdentityOperator + rhs.isNullableBoolean() -> EqualityOperator + else -> RuntimeFunctionCall } @@ -465,7 +496,12 @@ private fun IrType.isJsNumber(): Boolean = isPrimitiveType() && !isLong() // TODO extract to common place? -fun irCall(call: IrCall, newSymbol: IrFunctionSymbol, dispatchReceiverAsFirstArgument: Boolean = false): IrCall = +fun irCall( + call: IrCall, + newSymbol: IrFunctionSymbol, + dispatchReceiverAsFirstArgument: Boolean = false, + firstArgumentAsDispatchReceiver: Boolean = false +): IrCall = call.run { IrCallImpl( startOffset, @@ -477,38 +513,53 @@ fun irCall(call: IrCall, newSymbol: IrFunctionSymbol, dispatchReceiverAsFirstArg origin, superQualifierSymbol ).apply { - copyTypeAndValueArgumentsFrom(call, dispatchReceiverAsFirstArgument) + copyTypeAndValueArgumentsFrom( + call, + dispatchReceiverAsFirstArgument, + firstArgumentAsDispatchReceiver + ) } } // TODO extract to common place? -private fun IrCall.copyTypeAndValueArgumentsFrom(call: IrCall, dispatchReceiverAsFirstArgument: Boolean = false) { +private fun IrCall.copyTypeAndValueArgumentsFrom( + call: IrCall, + dispatchReceiverAsFirstArgument: Boolean = false, + firstArgumentAsDispatchReceiver: Boolean = false +) { copyTypeArgumentsFrom(call) - var j = 0 + var toValueArgumentIndex = 0 + var fromValueArgumentIndex = 0 - if (!dispatchReceiverAsFirstArgument) { - dispatchReceiver = call.dispatchReceiver - } else { - putValueArgument(j++, call.dispatchReceiver) + when { + dispatchReceiverAsFirstArgument -> { + putValueArgument(toValueArgumentIndex++, call.dispatchReceiver) + } + firstArgumentAsDispatchReceiver -> { + dispatchReceiver = call.getValueArgument(fromValueArgumentIndex++) + } + else -> { + dispatchReceiver = call.dispatchReceiver + } } extensionReceiver = call.extensionReceiver - for (i in 0 until call.valueArgumentsCount) { - putValueArgument(j++, call.getValueArgument(i)) + while (fromValueArgumentIndex < call.valueArgumentsCount) { + putValueArgument(toValueArgumentIndex++, call.getValueArgument(fromValueArgumentIndex++)) } } -private fun MutableMap.op(type: IrType, name: Name, v: IrSimpleFunctionSymbol) { - put(SimpleMemberKey(type, name), v) +private fun MemberToTransformer.op(type: IrType, name: Name, v: IrSimpleFunctionSymbol) { + op(type, name, v = { irCall(it, v, dispatchReceiverAsFirstArgument = true) }) } -private fun MutableMap.op(type: IrType, name: Name, v: IrSimpleFunction) { - put(SimpleMemberKey(type, name), v.symbol) +private fun MemberToTransformer.op(type: IrType, name: Name, v: IrSimpleFunction) { + op(type, name, v.symbol) } -private fun MutableMap IrExpression>.op(type: IrType, name: Name, v: (IrCall) -> IrExpression) { +private fun MemberToTransformer.op(type: IrType, name: Name, v: (IrCall) -> IrExpression) { put(SimpleMemberKey(type, name), v) } @@ -518,16 +569,20 @@ private fun MutableMap.op(type: IrType, name: String, v: put(SimpleMemberKey(type, Name.identifier(name)), v) } -private fun MutableMap.add(from: Map, to: V) { +private fun SymbolToTransformer.add(from: Map, to: IrSimpleFunction) { from.forEach { _, func -> add(func.symbol, to) } } -private fun MutableMap.add(from: IrFunctionSymbol, to: V) { +private fun SymbolToTransformer.add(from: IrFunctionSymbol, to: (IrCall) -> IrExpression) { put(from, to) } +private fun SymbolToTransformer.add(from: IrFunctionSymbol, to: IrSimpleFunction) { + put(from, { call -> irCall(call, to.symbol) }) +} + private fun MutableMap IrExpression>.addWithPredicate( from: K, predicate: (IrCall) -> Boolean, diff --git a/libraries/stdlib/js/irRuntime/numberConversion.kt b/libraries/stdlib/js/irRuntime/numberConversion.kt index 4c2fe415ea0..024b3bbb611 100644 --- a/libraries/stdlib/js/irRuntime/numberConversion.kt +++ b/libraries/stdlib/js/irRuntime/numberConversion.kt @@ -5,8 +5,6 @@ package kotlin.js -fun asIs(a: dynamic) = a - fun numberToByte(a: dynamic): Byte = toByte(numberToInt(a)) fun numberToDouble(a: dynamic): Double = js("+a").unsafeCast()