[JS IR BE] Refactor: split IntrinsicifyCallsLowering into multiple classes
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
-711
@@ -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
|
||||
+55
@@ -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
|
||||
}
|
||||
+100
@@ -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
|
||||
}
|
||||
}
|
||||
+90
@@ -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
|
||||
}
|
||||
}
|
||||
+164
@@ -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()
|
||||
|
||||
}
|
||||
+82
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+218
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+86
@@ -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
|
||||
+56
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user