Add a TypeOperatorLowering for JVM_IR

This commit is contained in:
Steven Schäfer
2019-05-13 14:30:34 +02:00
committed by max-kammerer
parent 39ba462540
commit b6ea135e70
5 changed files with 256 additions and 72 deletions
@@ -191,6 +191,7 @@ private val jvmFilePhases =
toArrayPhase then
jvmBuiltinOptimizationLoweringPhase then
additionalClassAnnotationPhase then
typeOperatorLowering then
recordNamesForKotlinTypeMapperPhase then
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
@@ -41,32 +42,47 @@ class JvmSymbols(
firMode: Boolean
) : Symbols<JvmBackendContext>(context, symbolTable) {
private val storageManager = LockBasedStorageManager(this::class.java.simpleName)
private val irBuiltIns = context.irBuiltIns
override val ThrowNullPointerException: IrSimpleFunctionSymbol
get() = error("Unused in JVM IR")
override val ThrowNoWhenBranchMatchedException: IrSimpleFunctionSymbol
get() = error("Unused in JVM IR")
override val ThrowTypeCastException: IrSimpleFunctionSymbol
get() = error("Unused in JVM IR")
private fun createPackage(fqName: FqName): IrPackageFragment =
IrExternalPackageFragmentImpl(IrExternalPackageFragmentSymbolImpl(EmptyPackageFragmentDescriptor(context.state.module, fqName)))
private val kotlinPackage: IrPackageFragment = createPackage(FqName("kotlin"))
private val kotlinJvmPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm"))
private val kotlinJvmInternalPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm.internal"))
private val kotlinJvmFunctionsPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm.functions"))
private val javaLangPackage: IrPackageFragment = createPackage(FqName("java.lang"))
private val irBuiltIns = context.irBuiltIns
private val nullPointerExceptionClass: IrClassSymbol =
createClass(FqName("java.lang.NullPointerException")) { klass ->
klass.addConstructor().apply {
addValueParameter("message", irBuiltIns.stringType)
}
}.symbol
override val ThrowNullPointerException: IrFunctionSymbol =
nullPointerExceptionClass.constructors.single()
override val ThrowNoWhenBranchMatchedException: IrSimpleFunctionSymbol
get() = error("Unused in JVM IR")
private val typeCastExceptionClass: IrClassSymbol =
createClass(FqName("kotlin.TypeCastException")) { klass ->
klass.addConstructor().apply {
addValueParameter("message", irBuiltIns.stringType)
}
}.symbol
override val ThrowTypeCastException: IrFunctionSymbol =
typeCastExceptionClass.constructors.single()
private fun createPackage(fqName: FqName): IrPackageFragment =
IrExternalPackageFragmentImpl(IrExternalPackageFragmentSymbolImpl(EmptyPackageFragmentDescriptor(context.state.module, fqName)))
private fun createClass(fqName: FqName, classKind: ClassKind = ClassKind.CLASS, block: (IrClass) -> Unit): IrClass =
buildClass {
name = fqName.shortName()
kind = classKind
}.apply {
parent = when (fqName.parent().asString()) {
"kotlin" -> kotlinPackage
"kotlin.jvm.internal" -> kotlinJvmInternalPackage
"kotlin.jvm.functions" -> kotlinJvmFunctionsPackage
"java.lang" -> javaLangPackage
@@ -82,8 +98,22 @@ class JvmSymbols(
klass.addFunction("throwUninitializedPropertyAccessException", irBuiltIns.unitType, isStatic = true).apply {
addValueParameter("propertyName", irBuiltIns.stringType)
}
klass.addFunction("checkExpressionValueIsNotNull", irBuiltIns.unitType, isStatic = true).apply {
addValueParameter("value", irBuiltIns.anyNType)
addValueParameter("expression", irBuiltIns.stringType)
}
klass.addFunction("checkNotNullExpressionValue", irBuiltIns.unitType, isStatic = true).apply {
addValueParameter("value", irBuiltIns.anyNType)
addValueParameter("expression", irBuiltIns.stringType)
}
}.symbol
val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "checkExpressionValueIsNotNull" }
val checkNotNullExpressionValue: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNullExpressionValue" }
override val ThrowUninitializedPropertyAccessException: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "throwUninitializedPropertyAccessException" }
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.backend.common.lower.BOUND_RECEIVER_PARAMETER
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty
import org.jetbrains.kotlin.backend.jvm.intrinsics.Not
import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.lower.constantValue
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
@@ -20,6 +19,7 @@ import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.putNeedClassReificationMarker
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.AS
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.SAFE_AS
import org.jetbrains.kotlin.codegen.intrinsics.TypeIntrinsics
import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysFalseIfeq
import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
@@ -637,15 +637,8 @@ class ExpressionCodegen(
val typeOperand = expression.typeOperand
val kotlinType = typeOperand.toKotlinType()
return when (expression.operator) {
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> {
expression.argument.accept(this, data).discard()
expression.argument.markEndOfStatementIfNeeded()
defaultValue(expression.type)
}
IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.IMPLICIT_INTEGER_COERCION -> {
IrTypeOperator.IMPLICIT_CAST ->
expression.argument.accept(this, data).coerce(expression.type)
}
IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> {
val result = expression.argument.accept(this, data)
@@ -654,44 +647,26 @@ class ExpressionCodegen(
val boxedRightType = typeMapper.boxType(typeOperand)
if (typeOperand.isReifiedTypeParameter) {
putReifiedOperationMarkerIfTypeIsReifiedParameter(
typeOperand, if (IrTypeOperator.SAFE_CAST == expression.operator) SAFE_AS else AS, mv, this
)
val operationKind = if (expression.operator == IrTypeOperator.CAST) AS else SAFE_AS
putReifiedOperationMarkerIfTypeIsReifiedParameter(typeOperand, operationKind, mv, this)
v.checkcast(boxedRightType)
} else {
generateAsCast(
mv, kotlinType, boxedRightType, expression.operator == IrTypeOperator.SAFE_CAST, state.languageVersionSettings
)
assert(expression.operator == IrTypeOperator.CAST) { "IrTypeOperator.SAFE_CAST should have been lowered." }
TypeIntrinsics.checkcast(mv, kotlinType, boxedRightType, false)
}
MaterialValue(this, boxedRightType, expression.type).coerce(expression.type)
}
IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> {
expression.argument.accept(this, data).coerce(OBJECT_TYPE, context.irBuiltIns.anyNType).materialize()
IrTypeOperator.INSTANCEOF -> {
expression.argument.accept(this, data).coerce(context.irBuiltIns.anyNType).materialize()
val type = typeMapper.boxType(typeOperand)
if (typeOperand.isReifiedTypeParameter) {
putReifiedOperationMarkerIfTypeIsReifiedParameter(typeOperand, ReifiedTypeInliner.OperationKind.IS, mv, this)
v.instanceOf(type)
} else {
generateIsCheck(mv, kotlinType, type, state.languageVersionSettings.isReleaseCoroutines())
TypeIntrinsics.instanceOf(mv, kotlinType, type, state.languageVersionSettings.isReleaseCoroutines())
}
// TODO remove this type operator, generate an intrinsic call around INSTANCEOF instead
if (IrTypeOperator.NOT_INSTANCEOF == expression.operator)
Not.BooleanNegation(expression.onStack.coerceToBoolean())
else
expression.onStack
}
IrTypeOperator.IMPLICIT_NOTNULL -> {
val value = expression.argument.accept(this, data).materialized
mv.dup()
mv.visitLdcInsn("TODO provide message for IMPLICIT_NOTNULL") /*TODO*/
val methodName =
if (state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4) "checkNotNullExpressionValue"
else "checkExpressionValueIsNotNull"
mv.invokestatic(IrIntrinsicMethods.INTRINSICS_CLASS_NAME, methodName, "(Ljava/lang/Object;Ljava/lang/String;)V", false)
// Unbox primitives.
value.coerce(expression.type)
expression.onStack
}
else -> throw AssertionError("type operator ${expression.operator} should have been lowered")
@@ -942,9 +917,7 @@ class ExpressionCodegen(
override fun visitThrow(expression: IrThrow, data: BlockInfo): PromisedValue {
expression.markLineNumber(startOffset = true)
expression.value.accept(this, data)
.coerce(AsmTypes.JAVA_THROWABLE_TYPE, context.irBuiltIns.throwableType)
.materialize()
expression.value.accept(this, data).materialize()
mv.athrow()
return immaterialUnitValue
}
@@ -0,0 +1,194 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.backend.common.lower.irThrow
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.fileParent
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
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.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
// After this pass runs there are only four kinds of IrTypeOperatorCalls left:
//
// - IMPLICIT_CAST
// - SAFE_CAST with reified type parameters
// - INSTANCEOF with non-nullable type operand or reified type parameters
// - CAST with non-nullable argument, nullable type operand, or reified type parameters
//
// The latter two correspond to the instanceof/checkcast instructions on the JVM, except for
// the presence of reified type parameters.
internal val typeOperatorLowering = makeIrFilePhase(
::TypeOperatorLowering,
name = "TypeOperatorLowering",
description = "Lower IrTypeOperatorCalls to (implicit) casts and instanceof checks"
)
private class TypeOperatorLowering(private val context: JvmBackendContext) : FileLoweringPass, IrBuildingTransformer(context) {
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
private fun IrExpression.transformVoid() = transform(this@TypeOperatorLowering, null)
private val IrType.isReifiedTypeParameter: Boolean
get() = classifierOrNull?.safeAs<IrTypeParameterSymbol>()?.owner?.isReified == true
private fun lowerInstanceOf(argument: IrExpression, type: IrType) = with(builder) {
when {
type.isReifiedTypeParameter ->
irIs(argument, type)
argument.type.isNullable() && type.isNullable() -> {
irLetS(argument) { valueSymbol ->
context.oror(
irEqualsNull(irGet(valueSymbol.owner)),
irIs(irGet(valueSymbol.owner), type.makeNotNull())
)
}
}
else -> irIs(argument, type.makeNotNull())
}
}
private fun lowerCast(argument: IrExpression, type: IrType): IrExpression = when {
type.isReifiedTypeParameter ->
builder.irAs(argument, type)
argument.type.isNullable() && !type.isNullable() ->
with(builder) {
irLetS(argument) { valueSymbol ->
irIfNull(
type,
irGet(valueSymbol.owner),
irThrow(irCall(typeCastException).apply {
putValueArgument(0, irString("null cannot be cast to non-null type ${type.render()}"))
}),
lowerCast(irGet(valueSymbol.owner), type.makeNullable())
)
}
}
argument.type.isSubtypeOfClass(type.erasedUpperBound.symbol) ->
argument
else ->
builder.irAs(argument, type)
}
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression = with(builder) {
at(expression)
return when (expression.operator) {
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT ->
irComposite(resultType = expression.type) {
+expression.argument.transformVoid()
// TODO: Don't generate these casts in the first place
if (!expression.argument.type.isSubtypeOf(expression.type.makeNullable(), context.irBuiltIns)) {
+IrCompositeImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, expression.type)
}
}
// There is no difference between IMPLICIT_CAST and IMPLICIT_INTEGER_COERCION on JVM_IR.
// Instead, this is handled in StackValue.coerce.
IrTypeOperator.IMPLICIT_INTEGER_COERCION ->
irImplicitCast(expression.argument.transformVoid(), expression.typeOperand)
IrTypeOperator.CAST ->
lowerCast(expression.argument.transformVoid(), expression.typeOperand)
IrTypeOperator.SAFE_CAST ->
if (expression.typeOperand.isReifiedTypeParameter) {
expression.transformChildrenVoid()
expression
} else {
irLetS(expression.argument.transformVoid(), IrStatementOrigin.SAFE_CALL) { valueSymbol ->
irIfThenElse(
expression.type,
lowerInstanceOf(irGet(valueSymbol.owner), expression.typeOperand.makeNotNull()),
irGet(valueSymbol.owner),
irNull(expression.type)
)
}
}
IrTypeOperator.INSTANCEOF ->
lowerInstanceOf(expression.argument.transformVoid(), expression.typeOperand)
IrTypeOperator.NOT_INSTANCEOF ->
irNot(lowerInstanceOf(expression.argument.transformVoid(), expression.typeOperand))
IrTypeOperator.IMPLICIT_NOTNULL -> {
val (startOffset, endOffset) = expression.extents()
val source = sourceViewFor(parent as IrDeclaration).subSequence(startOffset, endOffset).toString()
irLetS(expression.argument.transformVoid()) { valueSymbol ->
irComposite(resultType = expression.type) {
+irCall(checkExpressionValueIsNotNull).apply {
putValueArgument(0, irGet(valueSymbol.owner))
putValueArgument(1, irString(source))
}
+irGet(valueSymbol.owner)
}
}
}
else -> {
expression.transformChildrenVoid()
expression
}
}
}
private fun IrElement.extents(): Pair<Int, Int> {
var startOffset = UNDEFINED_OFFSET
var endOffset = UNDEFINED_OFFSET
acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
if (startOffset == UNDEFINED_OFFSET || element.startOffset != UNDEFINED_OFFSET && element.startOffset < startOffset)
startOffset = element.startOffset
if (endOffset == UNDEFINED_OFFSET || element.endOffset != UNDEFINED_OFFSET && endOffset < element.endOffset)
endOffset = element.endOffset
}
})
return startOffset to endOffset
}
private fun sourceViewFor(declaration: IrDeclaration) =
context.psiSourceManager.getKtFile(declaration.fileParent)!!.viewProvider.contents
// Since Kotlin 1.4 we throw NullPointerExceptions instead of more specialized exception classes.
private val useNullPointerExceptions: Boolean
get() = context.state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4
private val typeCastException: IrFunctionSymbol =
if (useNullPointerExceptions)
context.ir.symbols.ThrowNullPointerException
else
context.ir.symbols.ThrowTypeCastException
private val checkExpressionValueIsNotNull: IrFunctionSymbol =
if (useNullPointerExceptions)
context.ir.symbols.checkNotNullExpressionValue
else
context.ir.symbols.checkExpressionValueIsNotNull
}
@@ -19,20 +19,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.assertedCast
val IrBuilderWithScope.parent get() = scope.getLocalDeclarationParent()
inline fun IrBuilderWithScope.irLet(
value: IrExpression,
origin: IrStatementOrigin? = null,
nameHint: String? = null,
body: (VariableDescriptor) -> IrExpression
): IrExpression {
val irTemporary = scope.createTemporaryVariable(value, nameHint)
val irResult = body(irTemporary.descriptor)
val irBlock = IrBlockImpl(startOffset, endOffset, irResult.type, origin)
irBlock.statements.add(irTemporary)
irBlock.statements.add(irResult)
return irBlock
}
inline fun IrBuilderWithScope.irLetS(
value: IrExpression,
origin: IrStatementOrigin? = null,
@@ -185,13 +171,13 @@ fun IrBuilderWithScope.irEqeqeq(arg1: IrExpression, arg2: IrExpression) =
context.eqeqeq(startOffset, endOffset, arg1, arg2)
fun IrBuilderWithScope.irNull() =
IrConstImpl.constNull(startOffset, endOffset, context.irBuiltIns.nothingNType)
irNull(context.irBuiltIns.nothingNType)
fun IrBuilderWithScope.irNull(irType: IrType) =
IrConstImpl.constNull(startOffset, endOffset, irType)
fun IrBuilderWithScope.irEqualsNull(argument: IrExpression) =
primitiveOp2(
startOffset, endOffset, context.irBuiltIns.eqeqSymbol, context.irBuiltIns.booleanType, IrStatementOrigin.EQEQ,
argument, irNull()
)
irEquals(argument, irNull())
fun IrBuilderWithScope.irEquals(arg1: IrExpression, arg2: IrExpression, origin: IrStatementOrigin = IrStatementOrigin.EQEQ) =
primitiveOp2(