[IR] Add REINTERPRET_CAST operator in IrTypeOperatorCall

- fix BE
 - fix Serializer
 - implement builder
 - make Ir a bit more type-correct
 - support developer mode (lowering [dynamic]implicit cast meterialized as general cast)
 - fix autoboxing lowering
This commit is contained in:
Roman Artemev
2019-11-13 17:16:49 +03:00
committed by romanart
parent 3795897fb7
commit 52b24ead91
10 changed files with 57 additions and 26 deletions
@@ -203,7 +203,8 @@ class CheckIrElementVisitor(
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT,
IrTypeOperator.IMPLICIT_INTEGER_COERCION,
IrTypeOperator.SAM_CONVERSION,
IrTypeOperator.IMPLICIT_DYNAMIC_CAST ->
IrTypeOperator.IMPLICIT_DYNAMIC_CAST,
IrTypeOperator.REINTERPRET_CAST ->
typeOperand
IrTypeOperator.SAFE_CAST ->
@@ -251,6 +251,8 @@ object JsIrBuilder {
fun buildImplicitCast(value: IrExpression, toType: IrType) =
buildTypeOperator(toType, IrTypeOperator.IMPLICIT_CAST, value, toType)
fun buildReinterpretCast(value: IrExpression, toType: IrType) =
buildTypeOperator(toType, IrTypeOperator.REINTERPRET_CAST, value, toType)
fun buildNull(type: IrType) = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type)
fun buildBoolean(type: IrType, v: Boolean) = IrConstImpl.boolean(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v)
@@ -54,14 +54,9 @@ class AutoboxingTransformer(val context: JsIrBackendContext) : AbstractValueUsag
}
is IrGetField -> this.symbol.owner.type
is IrTypeOperatorCall -> when (this.operator) {
IrTypeOperator.IMPLICIT_INTEGER_COERCION ->
// TODO: is it a workaround for inconsistent IR?
this.typeOperand
IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> context.irBuiltIns.anyNType
else -> this.type
is IrTypeOperatorCall -> {
assert(operator == IrTypeOperator.REINTERPRET_CAST) { "Only REINTERPRET_CAST expected at this point" }
this.typeOperand
}
is IrGetValue -> {
@@ -456,6 +456,16 @@ class BlockDecomposerTransformer(
}
}
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
expression.transformChildrenVoid(expressionTransformer)
val composite = expression.argument as? IrComposite ?: return expression
return materializeLastExpression(composite) {
expression.apply { argument = it }
}
}
override fun visitGetClass(expression: IrGetClass): IrExpression {
expression.transformChildrenVoid(expressionTransformer)
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpressionWithCopy
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
@@ -37,6 +38,8 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : FileLoweringPass {
private val calculator = JsIrArithBuilder(context)
private val devMode = context.devMode
//NOTE: Should we define JS-own functions similar to current implementation?
private val throwCCE = context.ir.symbols.ThrowTypeCastException
private val throwNPE = context.ir.symbols.ThrowNullPointerException
@@ -71,8 +74,8 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : FileLoweringPass {
super.visitTypeOperator(expression, data)
return when (expression.operator) {
IrTypeOperator.IMPLICIT_CAST -> lowerImplicitCast(expression)
IrTypeOperator.IMPLICIT_DYNAMIC_CAST -> lowerImplicitDynamicCast(expression)
IrTypeOperator.IMPLICIT_CAST -> lowerImplicitCast(expression, data)
IrTypeOperator.IMPLICIT_DYNAMIC_CAST -> lowerImplicitDynamicCast(expression, data)
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> lowerCoercionToUnit(expression)
IrTypeOperator.IMPLICIT_INTEGER_COERCION -> lowerIntegerCoercion(expression, data)
IrTypeOperator.IMPLICIT_NOTNULL -> lowerImplicitNotNull(expression, data)
@@ -80,6 +83,7 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : FileLoweringPass {
IrTypeOperator.NOT_INSTANCEOF -> lowerInstanceOf(expression, data, true)
IrTypeOperator.CAST -> lowerCast(expression, data, false)
IrTypeOperator.SAFE_CAST -> lowerCast(expression, data, true)
IrTypeOperator.REINTERPRET_CAST -> expression
IrTypeOperator.SAM_CONVERSION -> TODO("SAM conversion: ${expression.render()}")
}
}
@@ -98,13 +102,26 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : FileLoweringPass {
return expression.run { IrCompositeImpl(startOffset, endOffset, typeOperand, null, newStatements) }
}
private fun needBoxingOrUnboxing(fromType: IrType, toType: IrType): Boolean {
return ((fromType.getInlinedClass() != null) xor (toType.getInlinedClass() != null)) || (fromType.isUnit() && !toType.isUnit())
}
private fun IrTypeOperatorCall.wrapWithUnsafeCast(arg: IrExpression): IrExpression {
// TODO: there is possible some situation which could be visible for AutoboxingLowering
// They are: 1. Inline classes, 2. Unit materialization. Using unsafe cast makes lowering work wrong.
return if (!needBoxingOrUnboxing(arg.type, typeOperand)) {
IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.REINTERPRET_CAST, typeOperand, arg)
} else arg
}
private fun lowerCast(
expression: IrTypeOperatorCall,
declaration: IrDeclarationParent,
isSafe: Boolean
): IrExpression {
assert(expression.operator == IrTypeOperator.CAST || expression.operator == IrTypeOperator.SAFE_CAST)
assert((expression.operator == IrTypeOperator.SAFE_CAST) == isSafe)
val operator = expression.operator
assert(operator == IrTypeOperator.CAST || operator == IrTypeOperator.SAFE_CAST || operator == IrTypeOperator.IMPLICIT_CAST || operator == IrTypeOperator.IMPLICIT_DYNAMIC_CAST)
assert((operator == IrTypeOperator.SAFE_CAST) == isSafe)
val toType = expression.typeOperand
val failResult = if (isSafe) litNull else JsIrBuilder.buildCall(throwCCE)
@@ -113,23 +130,23 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : FileLoweringPass {
val argument = cacheValue(expression.argument, newStatements, declaration)
val check = generateTypeCheck(argument, toType)
val castedValue = expression.wrapWithUnsafeCast(argument())
newStatements += JsIrBuilder.buildIfElse(expression.type, check, argument(), failResult)
newStatements += JsIrBuilder.buildIfElse(expression.type, check, castedValue, failResult)
return expression.run {
IrCompositeImpl(startOffset, endOffset, expression.type, null, newStatements)
}
}
private fun lowerImplicitCast(expression: IrTypeOperatorCall) = expression.run {
private fun lowerImplicitCast(expression: IrTypeOperatorCall, data: IrDeclarationParent) = expression.run {
assert(operator == IrTypeOperator.IMPLICIT_CAST)
argument
if (devMode) lowerCast(expression, data, false) else wrapWithUnsafeCast(argument)
}
private fun lowerImplicitDynamicCast(expression: IrTypeOperatorCall) = expression.run {
// TODO check argument
private fun lowerImplicitDynamicCast(expression: IrTypeOperatorCall, data: IrDeclarationParent) = expression.run {
assert(operator == IrTypeOperator.IMPLICIT_DYNAMIC_CAST)
argument
if (devMode) lowerCast(expression, data, false) else wrapWithUnsafeCast(argument)
}
// Note: native `instanceOf` is not used which is important because of null-behaviour
@@ -8,7 +8,6 @@ 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.utils.ConversionNames
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
@@ -87,6 +86,6 @@ class NumberConversionCallsTransformer(context: JsIrBackendContext) : CallsTrans
}
private fun useDispatchReceiver(call: IrFunctionAccessExpression): IrExpression {
return JsIrBuilder.buildImplicitCast(call.dispatchReceiver!!, call.type)
return JsIrBuilder.buildReinterpretCast(call.dispatchReceiver!!, call.type)
}
}
@@ -10,12 +10,10 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.OperatorNameConventions
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsExpression, JsGenerationContext> {
@@ -192,8 +190,8 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: JsGenerationContext): JsExpression {
return when (expression.operator) {
IrTypeOperator.IMPLICIT_CAST -> expression.argument.accept(this, data)
else -> error("All type operator calls except IMPLICIT_CAST should be lowered at this point")
IrTypeOperator.REINTERPRET_CAST -> expression.argument.accept(this, data)
else -> error("All type operator calls except REINTERPRET_CAST should be lowered at this point: ${expression.operator}")
}
}
@@ -314,6 +314,9 @@ fun IrBuilderWithScope.irAs(argument: IrExpression, type: IrType) =
fun IrBuilderWithScope.irImplicitCast(argument: IrExpression, type: IrType) =
IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.IMPLICIT_CAST, type, argument)
fun IrBuilderWithScope.irReinterpretCast(argument: IrExpression, type: IrType) =
IrTypeOperatorCallImpl(startOffset, endOffset, type, IrTypeOperator.REINTERPRET_CAST, type, argument)
fun IrBuilderWithScope.irInt(value: Int) =
IrConstImpl.int(startOffset, endOffset, context.irBuiltIns.intType, value)
@@ -49,7 +49,11 @@ enum class IrTypeOperator {
* Implicit dynamic cast: implicit cast from `dynamic` to `T`.
* This currently can happen in Kotlin/JS only.
*/
IMPLICIT_DYNAMIC_CAST;
IMPLICIT_DYNAMIC_CAST,
/**
* C-like reinterpret_cast<T> using as primitive type operation in JS
*/
REINTERPRET_CAST;
}
interface IrTypeOperatorCall : IrExpression {
@@ -738,6 +738,8 @@ open class IrFileSerializer(
ProtoTypeOperator.SAM_CONVERSION
IrTypeOperator.IMPLICIT_DYNAMIC_CAST ->
ProtoTypeOperator.IMPLICIT_DYNAMIC_CAST
IrTypeOperator.REINTERPRET_CAST ->
error("Unreachable execution")
}
private fun serializeTypeOp(expression: IrTypeOperatorCall): ProtoTypeOp {