[JS IR BE] Refactor: split IntrinsicifyCallsLowering into multiple classes

This commit is contained in:
Svyatoslav Kuzmich
2018-10-03 18:43:32 +03:00
parent 749556f565
commit e73ff16b35
11 changed files with 926 additions and 712 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.lower.calls.CallsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.CoroutineIntrinsicLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.SuspendFunctionsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.inline.FunctionInlining
@@ -131,7 +132,8 @@ private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependenc
moduleFragment.files.forEach(clble.getReferenceReplacer())
moduleFragment.files.forEach(ClassReferenceLowering(this)::lower)
moduleFragment.files.forEach(PrimitiveCompanionLowering(this)::lower)
moduleFragment.files.forEach(IntrinsicifyCallsLowering(this)::lower)
moduleFragment.files.forEach(ConstLowering(this)::lower)
moduleFragment.files.forEach(CallsLowering(this)::lower)
}
private fun FileLoweringPass.lower(files: List<IrFile>) = files.forEach { lower(it) }
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class ConstTransformer(private val context: JsIrBackendContext) : IrElementTransformerVoid() {
private fun <C> lowerConst(
irClass: IrClassSymbol,
carrierFactory: (Int, Int, IrType, C) -> IrExpression,
vararg args: C
): IrExpression {
val constructor = irClass.constructors.single()
val argType = constructor.owner.valueParameters.first().type
return JsIrBuilder.buildCall(constructor).apply {
for (i in args.indices) {
putValueArgument(i, carrierFactory(UNDEFINED_OFFSET, UNDEFINED_OFFSET, argType, args[i]))
}
}
}
private fun createLong(v: Long): IrExpression =
lowerConst(context.intrinsics.longClassSymbol, IrConstImpl<*>::int, v.toInt(), (v shr 32).toInt())
override fun <T> visitConst(expression: IrConst<T>): IrExpression {
with(context.intrinsics) {
return when (expression.type.classifierOrNull) {
uByteClassSymbol -> lowerConst(uByteClassSymbol, IrConstImpl<*>::byte, IrConstKind.Byte.valueOf(expression))
uShortClassSymbol -> lowerConst(uShortClassSymbol, IrConstImpl<*>::short, IrConstKind.Short.valueOf(expression))
uIntClassSymbol -> lowerConst(uIntClassSymbol, IrConstImpl<*>::int, IrConstKind.Int.valueOf(expression))
uLongClassSymbol -> lowerConst(uLongClassSymbol, { _, _, _, v -> createLong(v) }, IrConstKind.Long.valueOf(expression))
else -> when {
expression.kind is IrConstKind.Char ->
lowerConst(charClassSymbol, IrConstImpl<*>::int, IrConstKind.Char.valueOf(expression).toInt())
expression.kind is IrConstKind.Long ->
createLong(IrConstKind.Long.valueOf(expression))
else -> super.visitConst(expression)
}
}
}
}
}
class ConstLowering(private val context: JsIrBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(ConstTransformer(context))
}
}
@@ -1,711 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.utils.isSubtypeOfClass
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
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.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
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.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.types.isNullable
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: MemberToTransformer
private val symbolToTransformer: SymbolToTransformer
private val nameToTransformer: Map<Name, (IrCall) -> IrExpression>
private val dynamicCallOriginToIrFunction: Map<IrStatementOrigin, IrSimpleFunction>
init {
symbolToTransformer = mutableMapOf()
memberToTransformer = mutableMapOf()
nameToTransformer = mutableMapOf()
dynamicCallOriginToIrFunction = mutableMapOf()
val primitiveNumbers = context.irBuiltIns.run { listOf(intType, shortType, byteType, floatType, doubleType) }
memberToTransformer.run {
for (type in primitiveNumbers) {
op(type, OperatorNames.UNARY_PLUS, intrinsics.jsUnaryPlus)
op(type, OperatorNames.UNARY_MINUS, intrinsics.jsUnaryMinus)
}
irBuiltIns.stringType.let {
op(it, OperatorNames.ADD, intrinsics.jsPlus)
}
irBuiltIns.intType.let {
op(it, OperatorNames.SHL, intrinsics.jsBitShiftL)
op(it, OperatorNames.SHR, intrinsics.jsBitShiftR)
op(it, OperatorNames.SHRU, intrinsics.jsBitShiftRU)
op(it, OperatorNames.AND, intrinsics.jsBitAnd)
op(it, OperatorNames.OR, intrinsics.jsBitOr)
op(it, OperatorNames.XOR, intrinsics.jsBitXor)
op(it, OperatorNames.INV, intrinsics.jsBitNot)
}
irBuiltIns.booleanType.let {
op(it, OperatorNames.AND, intrinsics.jsBitAnd)
op(it, OperatorNames.OR, intrinsics.jsBitOr)
op(it, OperatorNames.NOT, intrinsics.jsNot)
op(it, OperatorNames.XOR, intrinsics.jsBitXor)
}
// Conversion rules are ported from NumberAndCharConversionFIF
// TODO: Add Char and Number conversions
irBuiltIns.byteType.let {
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)
op(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in listOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
op(type, ConversionNames.TO_BYTE, intrinsics.jsNumberToByte)
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)
}
irBuiltIns.intType.let {
op(it, ConversionNames.TO_BYTE, intrinsics.jsToByte)
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, ::useDispatchReceiver)
op(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
op(it, ConversionNames.TO_INT, ::useDispatchReceiver)
op(it, ConversionNames.TO_SHORT, ::useDispatchReceiver)
op(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in primitiveNumbers) {
op(type, Name.identifier("rangeTo"), ::transformRangeTo)
}
}
symbolToTransformer.run {
add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq)
add(irBuiltIns.eqeqSymbol, ::transformEqeqOperator)
// ieee754equals can only be applied in between statically known Floats, Doubles, null or undefined
add(irBuiltIns.ieee754equalsFunByOperandType, ::chooseEqualityOperatorForPrimitiveTypes)
add(irBuiltIns.booleanNotSymbol, intrinsics.jsNot)
add(irBuiltIns.lessFunByOperandType, intrinsics.jsLt)
add(irBuiltIns.lessOrEqualFunByOperandType, intrinsics.jsLtEq)
add(irBuiltIns.greaterFunByOperandType, intrinsics.jsGt)
add(irBuiltIns.greaterOrEqualFunByOperandType, intrinsics.jsGtEq)
// Arrays
add(context.intrinsics.array.sizeProperty, context.intrinsics.jsArrayLength, true)
add(context.intrinsics.array.getFunction, context.intrinsics.jsArrayGet, true)
add(context.intrinsics.array.setFunction, context.intrinsics.jsArraySet, true)
add(context.intrinsics.array.iterator, context.intrinsics.jsArrayIteratorFunction.owner, true)
for ((key, elementType) in context.intrinsics.primitiveArrays) {
add(key.sizeProperty, context.intrinsics.jsArrayLength, true)
add(key.getFunction, context.intrinsics.jsArrayGet, true)
add(key.setFunction, context.intrinsics.jsArraySet, true)
add(key.iterator, context.intrinsics.jsPrimitiveArrayIteratorFunctions[elementType]!!.owner, true)
// TODO irCall?
add(key.sizeConstructor) { call ->
IrCallImpl(call.startOffset, call.endOffset, call.type, context.intrinsics.primitiveToSizeConstructor[elementType]!!).apply {
putValueArgument(0, call.getValueArgument(0))
}
}
}
add(context.irBuiltIns.stringClass.lengthProperty, context.intrinsics.jsArrayLength, true)
add(context.irBuiltIns.stringClass.getFunction, intrinsics.jsCharSequenceGet.owner, true)
add(context.irBuiltIns.stringClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "subSequence"}.symbol,
intrinsics.jsCharSequenceSubSequence.owner, true)
add(intrinsics.charSequenceLengthPropertyGetterSymbol, intrinsics.jsCharSequenceLength.owner, true)
add(intrinsics.charSequenceGetFunctionSymbol, intrinsics.jsCharSequenceGet.owner, true)
add(intrinsics.charSequenceSubSequenceFunctionSymbol, intrinsics.jsCharSequenceSubSequence.owner, true)
}
memberToTransformer.run {
for (type in primitiveNumbers) {
// TODO: use increment and decrement when it's possible
op(type, OperatorNames.INC) {
irCall(it, intrinsics.jsPlus.symbol, dispatchReceiverAsFirstArgument = true).apply {
putValueArgument(1, JsIrBuilder.buildInt(irBuiltIns.intType, 1))
}
}
op(type, OperatorNames.DEC) {
irCall(it, intrinsics.jsMinus.symbol, dispatchReceiverAsFirstArgument = true).apply {
putValueArgument(1, JsIrBuilder.buildInt(irBuiltIns.intType, 1))
}
}
}
for (type in primitiveNumbers) {
op(type, OperatorNames.ADD, withLongCoercion(intrinsics.jsPlus))
op(type, OperatorNames.SUB, withLongCoercion(intrinsics.jsMinus))
op(type, OperatorNames.MUL, withLongCoercion(intrinsics.jsMult))
op(type, OperatorNames.DIV, withLongCoercion(intrinsics.jsDiv))
op(type, OperatorNames.MOD, withLongCoercion(intrinsics.jsMod))
op(type, OperatorNames.REM, withLongCoercion(intrinsics.jsMod))
}
for (type in arrayOf(irBuiltIns.byteType, irBuiltIns.intType)) {
op(type, ConversionNames.TO_CHAR) {
irCall(it, intrinsics.charClassSymbol.constructors.single(), dispatchReceiverAsFirstArgument = true)
}
}
for (type in arrayOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
op(type, ConversionNames.TO_CHAR) {
JsIrBuilder.buildCall(intrinsics.charClassSymbol.constructors.single()).apply {
putValueArgument(0, irCall(it, intrinsics.jsNumberToInt, dispatchReceiverAsFirstArgument = true))
}
}
}
op(irBuiltIns.charType, ConversionNames.TO_CHAR) { it.dispatchReceiver!! }
}
nameToTransformer.run {
addWithPredicate(
Name.special(Namer.KCALLABLE_GET_NAME),
{ call ->
call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kCallableClass) } ?: false
},
{ call -> irCall(call, context.intrinsics.jsName.symbol, dispatchReceiverAsFirstArgument = true) })
addWithPredicate(
Name.identifier(Namer.KPROPERTY_GET),
{ call ->
call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false
},
{ call -> irCall(call, context.intrinsics.jsPropertyGet.symbol, dispatchReceiverAsFirstArgument = true) }
)
addWithPredicate(
Name.identifier(Namer.KPROPERTY_SET),
{ call ->
call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false
},
{ call -> irCall(call, context.intrinsics.jsPropertySet.symbol, dispatchReceiverAsFirstArgument = true) }
)
put(Name.identifier("toString")) { call ->
if (shouldReplaceToStringWithRuntimeCall(call)) {
if (call.isSuperToAny()) {
irCall(call, intrinsics.jsAnyToString, dispatchReceiverAsFirstArgument = true)
} else {
irCall(call, intrinsics.jsToString, dispatchReceiverAsFirstArgument = true)
}
} else {
call
}
}
put(Name.identifier("hashCode")) { call ->
if (call.symbol.owner.isFakeOverriddenFromAny()) {
if (call.isSuperToAny()) {
irCall(call, intrinsics.jsGetObjectHashCode, dispatchReceiverAsFirstArgument = true)
} else {
irCall(call, intrinsics.jsHashCode, dispatchReceiverAsFirstArgument = true)
}
} else {
call
}
}
put(Name.identifier("compareTo"), ::transformCompareToMethodCall)
put(Name.identifier("equals"), ::transformEqualsMethodCall)
}
dynamicCallOriginToIrFunction.apply {
put(IrStatementOrigin.EXCL, context.intrinsics.jsNot)
put(IrStatementOrigin.LT, context.intrinsics.jsLt)
put(IrStatementOrigin.GT, context.intrinsics.jsGt)
put(IrStatementOrigin.LTEQ, context.intrinsics.jsLtEq)
put(IrStatementOrigin.GTEQ, context.intrinsics.jsGtEq)
put(IrStatementOrigin.EQEQ, context.intrinsics.jsEqeq)
put(IrStatementOrigin.EQEQEQ, context.intrinsics.jsEqeqeq)
put(IrStatementOrigin.EXCLEQ, context.intrinsics.jsNotEq)
put(IrStatementOrigin.EXCLEQEQ, context.intrinsics.jsNotEqeq)
put(IrStatementOrigin.ANDAND, context.intrinsics.jsAnd)
put(IrStatementOrigin.OROR, context.intrinsics.jsOr)
put(IrStatementOrigin.UMINUS, context.intrinsics.jsUnaryMinus)
put(IrStatementOrigin.UPLUS, context.intrinsics.jsUnaryPlus)
put(IrStatementOrigin.PLUS, context.intrinsics.jsPlus)
put(IrStatementOrigin.MINUS, context.intrinsics.jsMinus)
put(IrStatementOrigin.MUL, context.intrinsics.jsMult)
put(IrStatementOrigin.DIV, context.intrinsics.jsDiv)
put(IrStatementOrigin.PERC, context.intrinsics.jsMod)
put(IrStatementOrigin.PLUSEQ, context.intrinsics.jsPlusAssign)
put(IrStatementOrigin.MINUSEQ, context.intrinsics.jsMinusAssign)
put(IrStatementOrigin.MULTEQ, context.intrinsics.jsMultAssign)
put(IrStatementOrigin.DIVEQ, context.intrinsics.jsDivAssign)
put(IrStatementOrigin.PERCEQ, context.intrinsics.jsModAssign)
put(IrStatementOrigin.PREFIX_INCR, context.intrinsics.jsPrefixInc)
put(IrStatementOrigin.PREFIX_DECR, context.intrinsics.jsPrefixDec)
put(IrStatementOrigin.POSTFIX_INCR, context.intrinsics.jsPostfixInc)
put(IrStatementOrigin.POSTFIX_DECR, context.intrinsics.jsPostfixDec)
put(IrStatementOrigin.GET_ARRAY_ELEMENT, context.intrinsics.jsArrayGet)
// TODO add a special statement origin, e.g. SET_ARRAY_ELEMENT
put(IrStatementOrigin.EQ, context.intrinsics.jsArraySet)
}
}
override fun lower(irFile: IrFile) {
irFile.transform(object : IrElementTransformerVoid() {
private fun <C> lowerConst(
irClass: IrClassSymbol,
carrierFactory: (Int, Int, IrType, C) -> IrExpression,
vararg args: C
): IrExpression {
val constructor = irClass.constructors.single()
val argType = constructor.owner.valueParameters.first().type
return JsIrBuilder.buildCall(constructor).apply {
for (i in args.indices) {
putValueArgument(i, carrierFactory(UNDEFINED_OFFSET, UNDEFINED_OFFSET, argType, args[i]))
}
}
}
private fun createLong(v: Long): IrExpression = lowerConst(context.intrinsics.longClassSymbol, IrConstImpl<*>::int, v.toInt(), (v shr 32).toInt())
// TODO should this be a separate lowering?
override fun <T> visitConst(expression: IrConst<T>): IrExpression {
with(context.intrinsics) {
return when (expression.type.classifierOrNull) {
uByteClassSymbol -> lowerConst(uByteClassSymbol, IrConstImpl<*>::byte, IrConstKind.Byte.valueOf(expression))
uShortClassSymbol -> lowerConst(uShortClassSymbol, IrConstImpl<*>::short, IrConstKind.Short.valueOf(expression))
uIntClassSymbol -> lowerConst(uIntClassSymbol, IrConstImpl<*>::int, IrConstKind.Int.valueOf(expression))
uLongClassSymbol -> lowerConst(uLongClassSymbol, { _, _, _, v -> createLong(v) }, IrConstKind.Long.valueOf(expression))
else -> when {
expression.kind is IrConstKind.Char ->
lowerConst(charClassSymbol, IrConstImpl<*>::int, IrConstKind.Char.valueOf(expression).toInt())
expression.kind is IrConstKind.Long ->
createLong(IrConstKind.Long.valueOf(expression))
else -> super.visitConst(expression)
}
}
}
}
override fun visitFunction(declaration: IrFunction): IrStatement {
if (declaration.hasAnnotation(intrinsics.doNotIntrinsifyAnnotationSymbol))
return declaration
return super.visitFunction(declaration)
}
override fun visitCall(expression: IrCall): IrExpression {
val call = super.visitCall(expression)
if (call is IrCall) {
val symbol = call.symbol
val declaration = symbol.owner
if (declaration.isDynamic() || declaration.isEffectivelyExternal()) {
when (call.origin) {
IrStatementOrigin.GET_PROPERTY -> {
val fieldSymbol = context.symbolTable.lazyWrapper.referenceField(
(symbol.descriptor as PropertyAccessorDescriptor).correspondingProperty
)
return JsIrBuilder.buildGetField(fieldSymbol, call.dispatchReceiver, type = call.type)
}
// assignment to a property
IrStatementOrigin.EQ -> {
if (symbol.descriptor is PropertyAccessorDescriptor) {
val fieldSymbol = context.symbolTable.lazyWrapper.referenceField(
(symbol.descriptor as PropertyAccessorDescriptor).correspondingProperty
)
return call.run {
JsIrBuilder.buildSetField(fieldSymbol, dispatchReceiver, getValueArgument(0)!!, type)
}
}
}
}
}
if (declaration.isDynamic()) {
dynamicCallOriginToIrFunction[call.origin]?.let {
return irCall(call, it.symbol, dispatchReceiverAsFirstArgument = true)
}
}
symbolToTransformer[symbol]?.let {
return it(call)
}
(symbol.owner as? IrFunction)?.dispatchReceiverParameter?.let {
val key = SimpleMemberKey(it.type, symbol.owner.name)
memberToTransformer[key]?.let {
return it(call)
}
}
nameToTransformer[symbol.owner.name]?.let {
return it(call)
}
}
return call
}
}, null)
}
private fun useDispatchReceiver(call: IrCall): IrExpression {
return call.dispatchReceiver!!
}
private fun transformRangeTo(call: IrCall): IrExpression {
if (call.valueArgumentsCount != 1) return call
return with(call.symbol.owner.valueParameters[0].type) {
when {
isByte() || isShort() || isInt() ->
irCall(call, intrinsics.jsNumberRangeToNumber, dispatchReceiverAsFirstArgument = true)
isLong() ->
irCall(call, intrinsics.jsNumberRangeToLong, dispatchReceiverAsFirstArgument = true)
else -> call
}
}
}
private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call ->
assert(call.valueArgumentsCount == 1)
val arg = call.getValueArgument(0)!!
if (arg.type.isLong()) {
val receiverType = call.dispatchReceiver!!.type
when {
// Double OP Long => Double OP Long.toDouble()
receiverType.isDouble() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
context.intrinsics.longToDouble.owner.returnType,
context.intrinsics.longToDouble
).apply {
dispatchReceiver = arg
})
}
// Float OP Long => Float OP Long.toFloat()
receiverType.isFloat() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
context.intrinsics.longToFloat.owner.returnType,
context.intrinsics.longToFloat
).apply {
dispatchReceiver = arg
})
}
// {Byte, Short, Int} OP Long => {Byte, Sort, Int}.toLong() OP Long
!receiverType.isLong() -> {
call.dispatchReceiver = IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.jsNumberToLong.owner.returnType,
intrinsics.jsNumberToLong
).apply {
putValueArgument(0, call.dispatchReceiver)
}
}
}
}
if (call.dispatchReceiver!!.type.isLong()) {
// LHS is Long => use as is
call
} else {
irCall(call, intrinsic.symbol, dispatchReceiverAsFirstArgument = true)
}
}
private fun transformEqeqOperator(call: IrCall): IrExpression {
val lhs = call.getValueArgument(0)!!
val rhs = call.getValueArgument(1)!!
val lhsJsType = lhs.type.getPrimitiveType()
val rhsJsType = rhs.type.getPrimitiveType()
val equalsMethod = lhs.type.findEqualsMethod()
val isLhsPrimitive = lhsJsType != PrimitiveType.OTHER
return when {
lhs.type is IrDynamicType ->
irCall(call, intrinsics.jsEqeq.symbol)
// Special optimization for "<expression> == null"
lhs.isNullConst() || rhs.isNullConst() ->
irCall(call, intrinsics.jsEqeq.symbol)
// For non-float primitives of the same type use JS `==`
isLhsPrimitive && lhsJsType == rhsJsType && lhsJsType != PrimitiveType.FLOATING_POINT_NUMBER ->
chooseEqualityOperatorForPrimitiveTypes(call)
!isLhsPrimitive && !lhs.type.toKotlinType().isNullable() && equalsMethod != null ->
irCall(call, equalsMethod.symbol, firstArgumentAsDispatchReceiver = true)
else ->
irCall(call, intrinsics.jsEquals)
}
}
private fun chooseEqualityOperatorForPrimitiveTypes(call: IrCall): IrExpression = when {
call.allValueArgumentsAreNullable() ->
irCall(call, intrinsics.jsEqeq.symbol)
else ->
irCall(call, intrinsics.jsEqeqeq.symbol)
}
private fun IrCall.allValueArgumentsAreNullable() =
(0 until valueArgumentsCount).all { getValueArgument(it)!!.type.isNullable() }
private fun transformCompareToMethodCall(call: IrCall): IrExpression {
val function = call.symbol.owner as IrSimpleFunction
if (function.parent !is IrClass) return call
fun IrSimpleFunction.isFakeOverriddenFromComparable(): Boolean = when {
origin != IrDeclarationOrigin.FAKE_OVERRIDE ->
parentAsClass.thisReceiver!!.type.isComparable()
else -> overriddenSymbols.all { it.owner.isFakeOverriddenFromComparable() }
}
return when {
// Use runtime function call in case when receiverType is a primitive JS type that doesn't have `compareTo` method,
// or has a potential to be primitive type (being fake overridden from `Comparable`)
function.isMethodOfPrimitiveJSType() || function.isFakeOverriddenFromComparable() ->
irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true)
// Valid `compareTo` method must be present at this point
else ->
call
}
}
private fun transformEqualsMethodCall(call: IrCall): IrExpression {
val function = call.symbol.owner
return when {
// Nothing special
!function.isEqualsInheritedFromAny() -> call
// `Any.equals` works as identity operator
call.isSuperToAny() ->
irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true)
// Use runtime function call in case when receiverType is a primitive JS type that doesn't have `equals` method,
// or has a potential to be primitive type (being fake overridden from `Any`)
function.isMethodOfPotentiallyPrimitiveJSType() ->
irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true)
// Valid `equals` method must be present at this point
else -> call
}
}
private fun IrType.findEqualsMethod(): IrSimpleFunction? {
val klass = getClass() ?: return null
return klass.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.isEqualsInheritedFromAny() && !it.isFakeOverriddenFromAny() }
.also { assert(it.size <= 1) }
.singleOrNull()
}
private fun IrFunction.isMethodOfPrimitiveJSType() =
dispatchReceiverParameter?.type?.getPrimitiveType() != PrimitiveType.OTHER
private fun IrFunction.isMethodOfPotentiallyPrimitiveJSType() =
isMethodOfPrimitiveJSType() || isFakeOverriddenFromAny()
private fun IrFunction.isEqualsInheritedFromAny() =
name == Name.identifier("equals") &&
dispatchReceiverParameter != null &&
valueParameters.size == 1 &&
valueParameters[0].type.isNullableAny()
private fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean {
// TODO: (KOTLIN-CR-2079)
// - User defined extension functions Any?.toString() call can be lost during lowering.
// - Use direct method call for dynamic types???
// - Define Any?.toString() in runtime library and stop intrincifying extensions
if (call.valueArgumentsCount > 0)
return false
val receiverParameterType = with(call.symbol.owner) {
dispatchReceiverParameter ?: extensionReceiverParameter
}?.type ?: return false
return receiverParameterType.run {
isArray() || isAny() || isNullable() || this is IrDynamicType || isString()
}
}
}
enum class PrimitiveType {
FLOATING_POINT_NUMBER,
INTEGER_NUMBER,
STRING,
BOOLEAN,
OTHER
}
fun IrType.getPrimitiveType() = makeNotNull().run {
when {
isBoolean() -> PrimitiveType.BOOLEAN
isByte() || isShort() || isInt() -> PrimitiveType.INTEGER_NUMBER
isFloat() || isDouble() -> PrimitiveType.FLOATING_POINT_NUMBER
isString() -> PrimitiveType.STRING
else -> PrimitiveType.OTHER
}
}
private fun MemberToTransformer.op(type: IrType, name: Name, v: IrSimpleFunctionSymbol) {
op(type, name, v = { irCall(it, v, dispatchReceiverAsFirstArgument = true) })
}
private fun MemberToTransformer.op(type: IrType, name: Name, v: IrSimpleFunction) {
op(type, name, v.symbol)
}
private fun MemberToTransformer.op(type: IrType, name: Name, v: (IrCall) -> IrExpression) {
put(SimpleMemberKey(type, name), v)
}
// TODO issue: marked as unused, but used; rename works wrongly.
private fun <V> MutableMap<SimpleMemberKey, V>.op(type: IrType, name: String, v: V) {
put(SimpleMemberKey(type, Name.identifier(name)), v)
}
private fun SymbolToTransformer.add(from: Map<SimpleType, IrSimpleFunction>, to: IrSimpleFunction) {
from.forEach { _, func ->
add(func.symbol, to)
}
}
private fun SymbolToTransformer.add(from: Map<SimpleType, IrSimpleFunction>, to: (IrCall) -> IrExpression) {
from.forEach { _, func ->
add(func.symbol, to)
}
}
private fun SymbolToTransformer.add(from: IrFunctionSymbol, to: (IrCall) -> IrExpression) {
put(from, to)
}
private fun SymbolToTransformer.add(from: IrFunctionSymbol, to: IrSimpleFunction, dispatchReceiverAsFirstArgument: Boolean = false) {
put(from, { call -> irCall(call, to.symbol, dispatchReceiverAsFirstArgument) })
}
private fun <K> MutableMap<K, (IrCall) -> IrExpression>.addWithPredicate(
from: K,
predicate: (IrCall) -> Boolean,
action: (IrCall) -> IrExpression
) {
put(from) { call: IrCall -> select({ predicate(call) }, { action(call) }, { call }) }
}
private inline fun <T> select(crossinline predicate: () -> Boolean, crossinline ifTrue: () -> T, crossinline ifFalse: () -> T): T =
if (predicate()) ifTrue() else ifFalse()
private class SimpleMemberKey(val klass: IrType, val name: Name) {
// TODO drop custom equals and hashCode when IrTypes will have right equals
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SimpleMemberKey
if (name != other.name) return false
if (klass.originalKotlinType != other.klass.originalKotlinType) return false
return true
}
override fun hashCode(): Int {
var result = klass.originalKotlinType?.hashCode() ?: 0
result = 31 * result + name.hashCode()
return result
}
}
private val IrClassSymbol.sizeProperty
get() = owner.declarations.filterIsInstance<IrProperty>().first { it.name.asString() == "size" }.getter!!.symbol
private val IrClassSymbol.getFunction
get() = owner.declarations.filterIsInstance<IrFunction>().first { it.name.asString() == "get" }.symbol
private val IrClassSymbol.setFunction
get() = owner.declarations.filterIsInstance<IrFunction>().first { it.name.asString() == "set" }.symbol
private val IrClassSymbol.iterator
get() = owner.declarations.filterIsInstance<IrFunction>().first { it.name.asString() == "iterator" }.symbol
private val IrClassSymbol.sizeConstructor
get() = owner.declarations.filterIsInstance<IrConstructor>().first { it.valueParameters.size == 1 }.symbol
private val IrClassSymbol.lengthProperty
get() = owner.declarations.filterIsInstance<IrProperty>().first { it.name.asString() == "length" }.getter!!.symbol
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class CallsLowering(val context: JsIrBackendContext) : FileLoweringPass {
private val transformers = listOf(
NumberOperatorCallsTransformer(context),
DynamicCallsTransformer(context),
EqualityAndComparisonCallsTransformer(context),
PrimitiveContainerMemberCallTransformer(context),
MethodsOfAnyCallsTransformer(context),
ReflectionCallsTransformer(context)
)
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitFunction(declaration: IrFunction): IrStatement {
if (declaration.hasAnnotation(context.intrinsics.doNotIntrinsifyAnnotationSymbol))
return declaration
return super.visitFunction(declaration)
}
override fun visitCall(expression: IrCall): IrExpression {
val call = super.visitCall(expression)
if (call is IrCall) {
for (transformer in transformers) {
val newCall = transformer.transformCall(call)
if (newCall !== call) {
return newCall
}
}
}
return call
}
})
}
}
interface CallsTransformer {
fun transformCall(call: IrCall): IrExpression
}
@@ -0,0 +1,100 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
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.name.Name
import org.jetbrains.kotlin.types.SimpleType
typealias SymbolToTransformer = MutableMap<IrFunctionSymbol, (IrCall) -> IrExpression>
internal fun SymbolToTransformer.add(from: Map<SimpleType, IrSimpleFunction>, to: IrSimpleFunction) {
from.forEach { _, func ->
add(func.symbol, to)
}
}
internal fun SymbolToTransformer.add(from: Map<SimpleType, IrSimpleFunction>, to: (IrCall) -> IrExpression) {
from.forEach { _, func ->
add(func.symbol, to)
}
}
internal fun SymbolToTransformer.add(from: IrFunctionSymbol, to: (IrCall) -> IrExpression) {
put(from, to)
}
internal fun SymbolToTransformer.add(from: IrFunctionSymbol, to: IrSimpleFunction, dispatchReceiverAsFirstArgument: Boolean = false) {
put(from) { call -> irCall(call, to.symbol, dispatchReceiverAsFirstArgument) }
}
internal fun <K> MutableMap<K, (IrCall) -> IrExpression>.addWithPredicate(
from: K,
predicate: (IrCall) -> Boolean,
action: (IrCall) -> IrExpression
) {
put(from) { call: IrCall -> if (predicate(call)) action(call) else call }
}
internal typealias MemberToTransformer = HashMap<SimpleMemberKey, (IrCall) -> IrExpression>
internal fun MemberToTransformer.add(type: IrType, name: Name, v: IrSimpleFunctionSymbol) {
add(type, name) { irCall(it, v, dispatchReceiverAsFirstArgument = true) }
}
internal fun MemberToTransformer.add(type: IrType, name: Name, v: IrSimpleFunction) {
add(type, name, v.symbol)
}
internal fun MemberToTransformer.add(type: IrType, name: Name, v: (IrCall) -> IrExpression) {
put(SimpleMemberKey(type, name), v)
}
internal class SimpleMemberKey(val klass: IrType, val name: Name) {
// TODO drop custom equals and hashCode when IrTypes will have right equals
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SimpleMemberKey
if (name != other.name) return false
if (klass.originalKotlinType != other.klass.originalKotlinType) return false
return true
}
override fun hashCode(): Int {
var result = klass.originalKotlinType?.hashCode() ?: 0
result = 31 * result + name.hashCode()
return result
}
}
enum class PrimitiveType {
FLOATING_POINT_NUMBER,
INTEGER_NUMBER,
STRING,
BOOLEAN,
OTHER
}
fun IrType.getPrimitiveType() = makeNotNull().run {
when {
isBoolean() -> PrimitiveType.BOOLEAN
isByte() || isShort() || isInt() -> PrimitiveType.INTEGER_NUMBER
isFloat() || isDouble() -> PrimitiveType.FLOATING_POINT_NUMBER
isString() -> PrimitiveType.STRING
else -> PrimitiveType.OTHER
}
}
@@ -0,0 +1,90 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin.*
import org.jetbrains.kotlin.ir.util.isDynamic
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
class DynamicCallsTransformer(private val context: JsIrBackendContext) : CallsTransformer {
private val originToIrFunction =
context.intrinsics.run {
mapOf(
EXCL to jsNot,
LT to jsLt,
GT to jsGt,
LTEQ to jsLtEq,
GTEQ to jsGtEq,
EQEQ to jsEqeq,
EQEQEQ to jsEqeqeq,
EXCLEQ to jsNotEq,
EXCLEQEQ to jsNotEqeq,
ANDAND to jsAnd,
OROR to jsOr,
UMINUS to jsUnaryMinus,
UPLUS to jsUnaryPlus,
PLUS to jsPlus,
MINUS to jsMinus,
MUL to jsMult,
DIV to jsDiv,
PERC to jsMod,
PLUSEQ to jsPlusAssign,
MINUSEQ to jsMinusAssign,
MULTEQ to jsMultAssign,
DIVEQ to jsDivAssign,
PERCEQ to jsModAssign,
PREFIX_INCR to jsPrefixInc,
PREFIX_DECR to jsPrefixDec,
POSTFIX_INCR to jsPostfixInc,
POSTFIX_DECR to jsPostfixDec,
GET_ARRAY_ELEMENT to jsArrayGet,
// TODO add a special statement origin, e.g. SET_ARRAY_ELEMENT
EQ to jsArraySet
)
}
override fun transformCall(call: IrCall): IrExpression {
val symbol = call.symbol
val function = call.symbol.owner
if (function.isDynamic() || function.isEffectivelyExternal()) {
when (call.origin) {
GET_PROPERTY -> {
val fieldSymbol = context.symbolTable.lazyWrapper.referenceField(
(symbol.descriptor as PropertyAccessorDescriptor).correspondingProperty
)
return JsIrBuilder.buildGetField(fieldSymbol, call.dispatchReceiver, type = call.type)
}
// assignment to a property
EQ -> {
if (symbol.descriptor is PropertyAccessorDescriptor) {
val fieldSymbol = context.symbolTable.lazyWrapper.referenceField(
(symbol.descriptor as PropertyAccessorDescriptor).correspondingProperty
)
return call.run {
JsIrBuilder.buildSetField(fieldSymbol, dispatchReceiver, getValueArgument(0)!!, type)
}
}
}
}
}
if (function.isDynamic()) {
originToIrFunction[call.origin]?.let {
return irCall(call, it.symbol, dispatchReceiverAsFirstArgument = true)
}
}
return call
}
}
@@ -0,0 +1,164 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.isNullable
class EqualityAndComparisonCallsTransformer(context: JsIrBackendContext) : CallsTransformer {
private val intrinsics = context.intrinsics
private val irBuiltIns = context.irBuiltIns
private val symbolToTransformer: SymbolToTransformer = mutableMapOf()
init {
symbolToTransformer.run {
add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq)
add(irBuiltIns.eqeqSymbol, ::transformEqeqOperator)
// ieee754equals can only be applied in between statically known Floats, Doubles, null or undefined
add(irBuiltIns.ieee754equalsFunByOperandType, ::chooseEqualityOperatorForPrimitiveTypes)
add(irBuiltIns.booleanNotSymbol, intrinsics.jsNot)
add(irBuiltIns.lessFunByOperandType, intrinsics.jsLt)
add(irBuiltIns.lessOrEqualFunByOperandType, intrinsics.jsLtEq)
add(irBuiltIns.greaterFunByOperandType, intrinsics.jsGt)
add(irBuiltIns.greaterOrEqualFunByOperandType, intrinsics.jsGtEq)
}
}
override fun transformCall(call: IrCall): IrExpression {
val symbol = call.symbol
symbolToTransformer[symbol]?.let {
return it(call)
}
return when (symbol.owner.name) {
Name.identifier("compareTo") -> transformCompareToMethodCall(call)
Name.identifier("equals") -> transformEqualsMethodCall(call)
else -> call
}
}
private fun transformEqeqOperator(call: IrCall): IrExpression {
val lhs = call.getValueArgument(0)!!
val rhs = call.getValueArgument(1)!!
val lhsJsType = lhs.type.getPrimitiveType()
val rhsJsType = rhs.type.getPrimitiveType()
val equalsMethod = lhs.type.findEqualsMethod()
val isLhsPrimitive = lhsJsType != PrimitiveType.OTHER
return when {
lhs.type is IrDynamicType ->
irCall(call, intrinsics.jsEqeq.symbol)
// Special optimization for "<expression> == null"
lhs.isNullConst() || rhs.isNullConst() ->
irCall(call, intrinsics.jsEqeq.symbol)
// For non-float primitives of the same type use JS `==`
isLhsPrimitive && lhsJsType == rhsJsType && lhsJsType != PrimitiveType.FLOATING_POINT_NUMBER ->
chooseEqualityOperatorForPrimitiveTypes(call)
!isLhsPrimitive && !lhs.type.toKotlinType().isNullable() && equalsMethod != null ->
irCall(call, equalsMethod.symbol, firstArgumentAsDispatchReceiver = true)
else ->
irCall(call, intrinsics.jsEquals)
}
}
private fun chooseEqualityOperatorForPrimitiveTypes(call: IrCall): IrExpression = when {
call.allValueArgumentsAreNullable() ->
irCall(call, intrinsics.jsEqeq.symbol)
else ->
irCall(call, intrinsics.jsEqeqeq.symbol)
}
private fun IrCall.allValueArgumentsAreNullable() =
(0 until valueArgumentsCount).all { getValueArgument(it)!!.type.isNullable() }
private fun transformCompareToMethodCall(call: IrCall): IrExpression {
val function = call.symbol.owner as IrSimpleFunction
if (function.parent !is IrClass) return call
fun IrSimpleFunction.isFakeOverriddenFromComparable(): Boolean = when {
origin != IrDeclarationOrigin.FAKE_OVERRIDE ->
parentAsClass.thisReceiver!!.type.isComparable()
else -> overriddenSymbols.all { it.owner.isFakeOverriddenFromComparable() }
}
return when {
// Use runtime function call in case when receiverType is a primitive JS type that doesn't have `compareTo` method,
// or has a potential to be primitive type (being fake overridden from `Comparable`)
function.isMethodOfPrimitiveJSType() || function.isFakeOverriddenFromComparable() ->
irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true)
// Valid `compareTo` method must be present at this point
else ->
call
}
}
private fun transformEqualsMethodCall(call: IrCall): IrExpression {
val function = call.symbol.owner
return when {
// Nothing special
!function.isEqualsInheritedFromAny() -> call
// `Any.equals` works as identity operator
call.isSuperToAny() ->
irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true)
// Use runtime function call in case when receiverType is a primitive JS type that doesn't have `equals` method,
// or has a potential to be primitive type (being fake overridden from `Any`)
function.isMethodOfPotentiallyPrimitiveJSType() ->
irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true)
// Valid `equals` method must be present at this point
else -> call
}
}
private fun IrType.findEqualsMethod(): IrSimpleFunction? {
val klass = getClass() ?: return null
return klass.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.isEqualsInheritedFromAny() && !it.isFakeOverriddenFromAny() }
.also { assert(it.size <= 1) }
.singleOrNull()
}
private fun IrFunction.isMethodOfPrimitiveJSType() =
dispatchReceiverParameter?.type?.getPrimitiveType() != PrimitiveType.OTHER
private fun IrFunction.isMethodOfPotentiallyPrimitiveJSType() =
isMethodOfPrimitiveJSType() || isFakeOverriddenFromAny()
private fun IrFunction.isEqualsInheritedFromAny() =
name == Name.identifier("equals") &&
dispatchReceiverParameter != null &&
valueParameters.size == 1 &&
valueParameters[0].type.isNullableAny()
}
@@ -0,0 +1,82 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.IrDynamicType
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.types.isArray
import org.jetbrains.kotlin.ir.types.isString
import org.jetbrains.kotlin.ir.util.isFakeOverriddenFromAny
import org.jetbrains.kotlin.ir.util.isNullable
import org.jetbrains.kotlin.ir.util.isSuperToAny
import org.jetbrains.kotlin.name.Name
class MethodsOfAnyCallsTransformer(context: JsIrBackendContext) : CallsTransformer {
private val intrinsics = context.intrinsics
private val nameToTransformer: Map<Name, (IrCall) -> IrExpression>
init {
nameToTransformer = mutableMapOf()
nameToTransformer.run {
put(Name.identifier("toString")) { call ->
if (shouldReplaceToStringWithRuntimeCall(call)) {
if (call.isSuperToAny()) {
irCall(call, intrinsics.jsAnyToString, dispatchReceiverAsFirstArgument = true)
} else {
irCall(call, intrinsics.jsToString, dispatchReceiverAsFirstArgument = true)
}
} else {
call
}
}
put(Name.identifier("hashCode")) { call ->
if (call.symbol.owner.isFakeOverriddenFromAny()) {
if (call.isSuperToAny()) {
irCall(call, intrinsics.jsGetObjectHashCode, dispatchReceiverAsFirstArgument = true)
} else {
irCall(call, intrinsics.jsHashCode, dispatchReceiverAsFirstArgument = true)
}
} else {
call
}
}
}
}
override fun transformCall(call: IrCall): IrExpression {
val symbol = call.symbol
nameToTransformer[symbol.owner.name]?.let {
return it(call)
}
return call
}
private fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean {
// TODO: (KOTLIN-CR-2079)
// - User defined extension functions Any?.toString() call can be lost during lowering.
// - Use direct method call for dynamic types???
// - Define Any?.toString() in runtime library and stop intrinsicifying extensions
if (call.valueArgumentsCount > 0)
return false
val receiverParameterType = with(call.symbol.owner) {
dispatchReceiverParameter ?: extensionReceiverParameter
}?.type ?: return false
return receiverParameterType.run {
isArray() || isAny() || isNullable() || this is IrDynamicType || isString()
}
}
}
@@ -0,0 +1,218 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.backend.js.utils.ConversionNames
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
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.constructors
import org.jetbrains.kotlin.name.Name
class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransformer {
private val intrinsics = context.intrinsics
private val irBuiltIns = context.irBuiltIns
private val primitiveNumbers =
irBuiltIns.run { listOf(intType, shortType, byteType, floatType, doubleType) }
private val memberToTransformer = MemberToTransformer().apply {
for (type in primitiveNumbers) {
add(type, OperatorNames.UNARY_PLUS, intrinsics.jsUnaryPlus)
add(type, OperatorNames.UNARY_MINUS, intrinsics.jsUnaryMinus)
}
add(irBuiltIns.stringType, OperatorNames.ADD, intrinsics.jsPlus)
irBuiltIns.intType.let {
add(it, OperatorNames.SHL, intrinsics.jsBitShiftL)
add(it, OperatorNames.SHR, intrinsics.jsBitShiftR)
add(it, OperatorNames.SHRU, intrinsics.jsBitShiftRU)
add(it, OperatorNames.AND, intrinsics.jsBitAnd)
add(it, OperatorNames.OR, intrinsics.jsBitOr)
add(it, OperatorNames.XOR, intrinsics.jsBitXor)
add(it, OperatorNames.INV, intrinsics.jsBitNot)
}
irBuiltIns.booleanType.let {
add(it, OperatorNames.AND, intrinsics.jsBitAnd)
add(it, OperatorNames.OR, intrinsics.jsBitOr)
add(it, OperatorNames.NOT, intrinsics.jsNot)
add(it, OperatorNames.XOR, intrinsics.jsBitXor)
}
// Conversion rules are ported from NumberAndCharConversionFIF
// TODO: Add Char and Number conversions
irBuiltIns.byteType.let {
add(it, ConversionNames.TO_BYTE, ::useDispatchReceiver)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, ::useDispatchReceiver)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in listOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
add(type, ConversionNames.TO_BYTE, intrinsics.jsNumberToByte)
add(type, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(type, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(type, ConversionNames.TO_INT, intrinsics.jsNumberToInt)
add(type, ConversionNames.TO_SHORT, intrinsics.jsNumberToShort)
add(type, ConversionNames.TO_LONG, intrinsics.jsNumberToLong)
}
irBuiltIns.intType.let {
add(it, ConversionNames.TO_BYTE, intrinsics.jsToByte)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, intrinsics.jsToShort)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
irBuiltIns.shortType.let {
add(it, ConversionNames.TO_BYTE, intrinsics.jsToByte)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, ::useDispatchReceiver)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in primitiveNumbers) {
add(type, Name.identifier("rangeTo"), ::transformRangeTo)
}
for (type in primitiveNumbers) {
// TODO: use increment and decrement when it's possible
add(type, OperatorNames.INC) {
irCall(it, intrinsics.jsPlus.symbol, dispatchReceiverAsFirstArgument = true).apply {
putValueArgument(1, JsIrBuilder.buildInt(irBuiltIns.intType, 1))
}
}
add(type, OperatorNames.DEC) {
irCall(it, intrinsics.jsMinus.symbol, dispatchReceiverAsFirstArgument = true).apply {
putValueArgument(1, JsIrBuilder.buildInt(irBuiltIns.intType, 1))
}
}
}
for (type in primitiveNumbers) {
add(type, OperatorNames.ADD, withLongCoercion(intrinsics.jsPlus))
add(type, OperatorNames.SUB, withLongCoercion(intrinsics.jsMinus))
add(type, OperatorNames.MUL, withLongCoercion(intrinsics.jsMult))
add(type, OperatorNames.DIV, withLongCoercion(intrinsics.jsDiv))
add(type, OperatorNames.MOD, withLongCoercion(intrinsics.jsMod))
add(type, OperatorNames.REM, withLongCoercion(intrinsics.jsMod))
}
for (type in arrayOf(irBuiltIns.byteType, irBuiltIns.intType)) {
add(type, ConversionNames.TO_CHAR) {
irCall(it, intrinsics.charClassSymbol.constructors.single(), dispatchReceiverAsFirstArgument = true)
}
}
for (type in arrayOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
add(type, ConversionNames.TO_CHAR) {
JsIrBuilder.buildCall(intrinsics.charClassSymbol.constructors.single()).apply {
putValueArgument(0, irCall(it, intrinsics.jsNumberToInt, dispatchReceiverAsFirstArgument = true))
}
}
}
add(irBuiltIns.charType, ConversionNames.TO_CHAR) { it.dispatchReceiver!! }
}
override fun transformCall(call: IrCall): IrExpression {
val function = call.symbol.owner
function.dispatchReceiverParameter?.also {
val key = SimpleMemberKey(it.type, function.name)
memberToTransformer[key]?.also {
return it(call)
}
}
return call
}
private fun useDispatchReceiver(call: IrCall): IrExpression {
return call.dispatchReceiver!!
}
private fun transformRangeTo(call: IrCall): IrExpression {
if (call.valueArgumentsCount != 1) return call
return with(call.symbol.owner.valueParameters[0].type) {
when {
isByte() || isShort() || isInt() ->
irCall(call, intrinsics.jsNumberRangeToNumber, dispatchReceiverAsFirstArgument = true)
isLong() ->
irCall(call, intrinsics.jsNumberRangeToLong, dispatchReceiverAsFirstArgument = true)
else -> call
}
}
}
private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call ->
assert(call.valueArgumentsCount == 1)
val arg = call.getValueArgument(0)!!
if (arg.type.isLong()) {
val receiverType = call.dispatchReceiver!!.type
when {
// Double OP Long => Double OP Long.toDouble()
receiverType.isDouble() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.longToDouble.owner.returnType,
intrinsics.longToDouble
).apply {
dispatchReceiver = arg
})
}
// Float OP Long => Float OP Long.toFloat()
receiverType.isFloat() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.longToFloat.owner.returnType,
intrinsics.longToFloat
).apply {
dispatchReceiver = arg
})
}
// {Byte, Short, Int} OP Long => {Byte, Sort, Int}.toLong() OP Long
!receiverType.isLong() -> {
call.dispatchReceiver = IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.jsNumberToLong.owner.returnType,
intrinsics.jsNumberToLong
).apply {
putValueArgument(0, call.dispatchReceiver)
}
}
}
}
if (call.dispatchReceiver!!.type.isLong()) {
// LHS is Long => use as is
call
} else {
irCall(call, intrinsic.symbol, dispatchReceiverAsFirstArgument = true)
}
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
class PrimitiveContainerMemberCallTransformer(private val context: JsIrBackendContext) : CallsTransformer {
private val intrinsics = context.intrinsics
private val symbolToTransformer: SymbolToTransformer = mutableMapOf()
init {
symbolToTransformer.run {
// Arrays
add(context.intrinsics.array.sizeProperty, context.intrinsics.jsArrayLength, true)
add(context.intrinsics.array.getFunction, context.intrinsics.jsArrayGet, true)
add(context.intrinsics.array.setFunction, context.intrinsics.jsArraySet, true)
add(context.intrinsics.array.iterator, context.intrinsics.jsArrayIteratorFunction.owner, true)
for ((key, elementType) in context.intrinsics.primitiveArrays) {
add(key.sizeProperty, context.intrinsics.jsArrayLength, true)
add(key.getFunction, context.intrinsics.jsArrayGet, true)
add(key.setFunction, context.intrinsics.jsArraySet, true)
add(key.iterator, context.intrinsics.jsPrimitiveArrayIteratorFunctions[elementType]!!.owner, true)
// TODO irCall?
add(key.sizeConstructor) { call ->
IrCallImpl(
call.startOffset,
call.endOffset,
call.type,
context.intrinsics.primitiveToSizeConstructor[elementType]!!
).apply {
putValueArgument(0, call.getValueArgument(0))
}
}
}
add(context.irBuiltIns.stringClass.lengthProperty, context.intrinsics.jsArrayLength, true)
add(context.irBuiltIns.stringClass.getFunction, intrinsics.jsCharSequenceGet.owner, true)
add(context.irBuiltIns.stringClass.subSequence, intrinsics.jsCharSequenceSubSequence.owner, true)
add(intrinsics.charSequenceLengthPropertyGetterSymbol, intrinsics.jsCharSequenceLength.owner, true)
add(intrinsics.charSequenceGetFunctionSymbol, intrinsics.jsCharSequenceGet.owner, true)
add(intrinsics.charSequenceSubSequenceFunctionSymbol, intrinsics.jsCharSequenceSubSequence.owner, true)
}
}
override fun transformCall(call: IrCall): IrExpression {
val symbol = call.symbol
symbolToTransformer[symbol]?.let {
return it(call)
}
return call
}
}
private val IrClassSymbol.sizeProperty
get() = owner.declarations.filterIsInstance<IrProperty>().first { it.name.asString() == "size" }.getter!!.symbol
private val IrClassSymbol.getFunction
get() = owner.declarations.filterIsInstance<IrFunction>().first { it.name.asString() == "get" }.symbol
private val IrClassSymbol.setFunction
get() = owner.declarations.filterIsInstance<IrFunction>().first { it.name.asString() == "set" }.symbol
private val IrClassSymbol.iterator
get() = owner.declarations.filterIsInstance<IrFunction>().first { it.name.asString() == "iterator" }.symbol
private val IrClassSymbol.sizeConstructor
get() = owner.declarations.filterIsInstance<IrConstructor>().first { it.valueParameters.size == 1 }.symbol
private val IrClassSymbol.lengthProperty
get() = owner.declarations.filterIsInstance<IrProperty>().first { it.name.asString() == "length" }.getter!!.symbol
private val IrClassSymbol.subSequence
get() = owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "subSequence" }.symbol
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.backend.common.utils.isSubtypeOfClass
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.name.Name
class ReflectionCallsTransformer(private val context: JsIrBackendContext) : CallsTransformer {
private val nameToTransformer: Map<Name, (IrCall) -> IrExpression>
init {
nameToTransformer = mutableMapOf()
nameToTransformer.run {
addWithPredicate(
Name.special(Namer.KCALLABLE_GET_NAME),
{ call ->
call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kCallableClass) } ?: false
},
{ call -> irCall(call, context.intrinsics.jsName.symbol, dispatchReceiverAsFirstArgument = true) })
addWithPredicate(
Name.identifier(Namer.KPROPERTY_GET),
{ call ->
call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false
},
{ call -> irCall(call, context.intrinsics.jsPropertyGet.symbol, dispatchReceiverAsFirstArgument = true) }
)
addWithPredicate(
Name.identifier(Namer.KPROPERTY_SET),
{ call ->
call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false
},
{ call -> irCall(call, context.intrinsics.jsPropertySet.symbol, dispatchReceiverAsFirstArgument = true) }
)
}
}
override fun transformCall(call: IrCall): IrExpression {
val symbol = call.symbol
nameToTransformer[symbol.owner.name]?.let {
return it(call)
}
return call
}
}