[JS IR BE] Eqeq operator lowering. IntrinsicifyCallsLowering.kt refactoring

This commit is contained in:
Svyatoslav Kuzmich
2018-07-04 18:10:10 +03:00
parent d412b6f4d0
commit a2f89cc711
3 changed files with 161 additions and 109 deletions
@@ -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")
@@ -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<SimpleMemberKey, (IrCall) -> IrExpression>
private typealias SymbolToTransformer = MutableMap<IrFunctionSymbol, (IrCall) -> 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<SimpleMemberKey, (IrCall) -> IrExpression>
private val memberToIrFunction: Map<SimpleMemberKey, IrSimpleFunctionSymbol>
private val symbolToIrFunction: Map<IrFunctionSymbol, IrSimpleFunction>
private val nameToIrTransformer: Map<Name, (IrCall) -> IrExpression>
private val memberToTransformer: MemberToTransformer
private val symbolToTransformer: SymbolToTransformer
private val nameToTransformer: Map<Name, (IrCall) -> 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 "<expression> == 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<IrSimpleFunction>()
.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<SimpleMemberKey, IrSimpleFunctionSymbol>.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<SimpleMemberKey, IrSimpleFunctionSymbol>.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<SimpleMemberKey, (IrCall) -> 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 <V> MutableMap<SimpleMemberKey, V>.op(type: IrType, name: String, v:
put(SimpleMemberKey(type, Name.identifier(name)), v)
}
private fun <V> MutableMap<IrFunctionSymbol, V>.add(from: Map<SimpleType, IrSimpleFunction>, to: V) {
private fun SymbolToTransformer.add(from: Map<SimpleType, IrSimpleFunction>, to: IrSimpleFunction) {
from.forEach { _, func ->
add(func.symbol, to)
}
}
private fun <V> MutableMap<IrFunctionSymbol, V>.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 <K> MutableMap<K, (IrCall) -> IrExpression>.addWithPredicate(
from: K,
predicate: (IrCall) -> Boolean,
@@ -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<Double>()