diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index b8db54c0f49..f25855c1fdf 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.resolve.jvm.AsmTypes +import org.jetbrains.kotlin.resolve.jvm.AsmTypes.DEFAULT_CONSTRUCTOR_MARKER import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -169,7 +170,8 @@ class ExpressionCodegen( irFunction.markLineNumber(startOffset = irFunction is IrConstructor && irFunction.isPrimary) } val returnType = typeMapper.mapReturnType(irFunction) - result.coerce(returnType, if (irFunction !is IrConstructor) irFunction.returnType else null).materialize() + val returnIrType = if (irFunction !is IrConstructor) irFunction.returnType else context.irBuiltIns.unitType + result.coerce(returnType, returnIrType).materialize() mv.areturn(returnType) } val endLabel = markNewLabel() @@ -278,7 +280,7 @@ class ExpressionCodegen( } private fun visitStatementContainer(container: IrStatementContainer, data: BlockInfo) = - container.statements.fold(voidValue as PromisedValue) { prev, exp -> + container.statements.fold(immaterialUnitValue as PromisedValue) { prev, exp -> prev.discard() exp.accept(this, data).also { (exp as? IrExpression)?.markEndOfStatementIfNeeded() } } @@ -396,12 +398,12 @@ class ExpressionCodegen( returnType.substitute(typeSubstitutionMap).isNothing() -> { mv.aconst(null) mv.athrow() - voidValue + immaterialUnitValue } expression is IrConstructorCall -> MaterialValue(this, asmType, expression.type) expression is IrDelegatingConstructorCall -> - voidValue + immaterialUnitValue expression.type.isUnit() -> // NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail. MaterialValue(this, callable.returnType, returnType).discard().coerce(expression.type) @@ -423,7 +425,7 @@ class ExpressionCodegen( } data.variables.add(VariableInfo(declaration, index, varType, markNewLabel())) - return voidValue + return immaterialUnitValue } override fun visitGetValue(expression: IrGetValue, data: BlockInfo): PromisedValue { @@ -442,7 +444,7 @@ class ExpressionCodegen( assert(expression is IrSetField) { "read of const val ${expression.symbol.owner.name} not inlined by ConstLowering" } // This can only be the field's initializer; JVM implementations are required // to generate those for ConstantValue-marked fields automatically, so this is redundant. - return voidValue.coerce(expression.type) + return defaultValue(expression.type) } val realField = expression.symbol.owner.resolveFakeOverride()!! @@ -458,7 +460,7 @@ class ExpressionCodegen( isStatic -> mv.putstatic(ownerType, fieldName, fieldType.descriptor) else -> mv.putfield(ownerType, fieldName, fieldType.descriptor) } - voidValue.coerce(expression.type) + defaultValue(expression.type) } else { when { isStatic -> @@ -480,7 +482,7 @@ class ExpressionCodegen( val isFieldInitializer = expression.origin == null val skip = (inPrimaryConstructor || inClassInit) && isFieldInitializer && expressionValue is IrConst<*> && isDefaultValueForType(expression.symbol.owner.type.asmType, expressionValue.value) - return if (skip) voidValue.coerce(expression.type) else super.visitSetField(expression, data) + return if (skip) defaultValue(expression.type) else super.visitSetField(expression, data) } /** @@ -511,7 +513,7 @@ class ExpressionCodegen( expression.value.markLineNumber(startOffset = true) expression.value.accept(this, data).coerce(expression.symbol.owner.type).materialize() mv.store(findLocalIndex(expression.symbol), expression.symbol.owner.asmType) - return voidValue.coerce(expression.type) + return defaultValue(expression.type) } override fun visitConst(expression: IrConst, data: BlockInfo): PromisedValue { @@ -544,7 +546,7 @@ class ExpressionCodegen( // TODO maybe remove? override fun visitClass(declaration: IrClass, data: BlockInfo): PromisedValue { classCodegen.generateLocalClass(declaration) - return voidValue + return immaterialUnitValue } override fun visitVararg(expression: IrVararg, data: BlockInfo): PromisedValue { @@ -662,7 +664,7 @@ class ExpressionCodegen( mv, "java/lang/UnsupportedOperationException", "Non-local returns are not allowed with inlining disabled" ) - return voidValue + return immaterialUnitValue } val target = data.findBlock { it.returnSymbol == expression.returnTargetSymbol } @@ -682,7 +684,7 @@ class ExpressionCodegen( } mv.mark(afterReturnLabel) mv.nop()/*TODO check RESTORE_STACK_IN_TRY_CATCH processor*/ - return voidValue + return immaterialUnitValue } override fun visitWhen(expression: IrWhen, data: BlockInfo): PromisedValue { @@ -715,7 +717,7 @@ class ExpressionCodegen( } // Produce the default value for the type. Doesn't really matter right now, as non-exhaustive // conditionals cannot be used as expressions. - val result = voidValue.coerce(expression.type).materialized + val result = defaultValue(expression.type).materialized mv.mark(endLabel) return result } @@ -727,7 +729,7 @@ class ExpressionCodegen( IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> { expression.argument.accept(this, data).discard() expression.argument.markEndOfStatementIfNeeded() - voidValue.coerce(expression.asmType) + defaultValue(expression.type) } IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.IMPLICIT_INTEGER_COERCION -> { @@ -736,7 +738,7 @@ class ExpressionCodegen( IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> { val result = expression.argument.accept(this, data) - val boxedLeftType = result.irType?.let { typeMapper.boxType(it) } ?: OBJECT_TYPE + val boxedLeftType = typeMapper.boxType(result.irType) result.coerce(boxedLeftType, expression.argument.type).materialize() val boxedRightType = typeMapper.boxType(typeOperand) @@ -851,7 +853,7 @@ class ExpressionCodegen( } mv.goTo(continueLabel) mv.mark(endLabel) - return voidValue + return immaterialUnitValue } override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: BlockInfo): PromisedValue { @@ -868,7 +870,7 @@ class ExpressionCodegen( loop.condition.markLineNumber(true) loop.condition.accept(this, data).coerceToBoolean().jumpIfTrue(entry) mv.mark(endLabel) - return voidValue + return immaterialUnitValue } private fun unwindBlockStack(endLabel: Label, data: BlockInfo, stop: (ExpressionInfo) -> Boolean): ExpressionInfo? { @@ -886,7 +888,7 @@ class ExpressionCodegen( ?: throw AssertionError("Target label for break/continue not found") mv.fixStackAndJump(if (jump is IrBreak) stackElement.breakLabel else stackElement.continueLabel) mv.mark(endLabel) - return voidValue + return immaterialUnitValue } override fun visitTry(aTry: IrTry, data: BlockInfo): PromisedValue { @@ -985,7 +987,7 @@ class ExpressionCodegen( frameMap.leaveTemp(tryAsmType) return aTry.onStack } - return voidValue + return immaterialUnitValue } private fun genTryCatchCover(catchStart: Label, tryStart: Label, tryEnd: Label, tryGaps: List>, type: String?) { @@ -1030,9 +1032,11 @@ class ExpressionCodegen( override fun visitThrow(expression: IrThrow, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) - expression.value.accept(this, data).coerce(AsmTypes.JAVA_THROWABLE_TYPE).materialize() + expression.value.accept(this, data) + .coerce(AsmTypes.JAVA_THROWABLE_TYPE, context.irBuiltIns.throwableType) + .materialize() mv.athrow() - return voidValue + return immaterialUnitValue } override fun visitGetClass(expression: IrGetClass, data: BlockInfo) = @@ -1213,7 +1217,7 @@ fun DefaultCallArgs.generateOnStackIfNeeded(callGenerator: IrCallGenerator, isCo callGenerator.putValueIfNeeded(Type.INT_TYPE, StackValue.constant(mask, Type.INT_TYPE), ValueKind.DEFAULT_MASK, -1, codegen) } - val parameterType = if (isConstructor) AsmTypes.DEFAULT_CONSTRUCTOR_MARKER else OBJECT_TYPE + val parameterType = if (isConstructor) DEFAULT_CONSTRUCTOR_MARKER else OBJECT_TYPE callGenerator.putValueIfNeeded( parameterType, StackValue.constant(null, parameterType), diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt index 08ae50eeb19..c0e39fa7a03 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt @@ -19,7 +19,7 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter // A value that may not have been fully constructed yet. The ability to "roll back" code generation // is useful for certain optimizations. -abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val irType: IrType?) { +abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val irType: IrType) { // If this value is immaterial, construct an object on the top of the stack. This // must always be done before generating other values or emitting raw bytecode. abstract fun materialize() @@ -30,12 +30,17 @@ abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val val typeMapper: IrTypeMapper get() = codegen.typeMapper - val kotlinType: KotlinType? - get() = irType?.toKotlinType() + val kotlinType: KotlinType + get() = irType.toKotlinType() } // A value that *has* been fully constructed. -class MaterialValue(codegen: ExpressionCodegen, type: Type, irType: IrType?) : PromisedValue(codegen, type, irType) { +class MaterialValue(codegen: ExpressionCodegen, type: Type, irType: IrType) : PromisedValue(codegen, type, irType) { + override fun materialize() {} +} + +// A value that is only materialized through coercion. +class ImmaterialValue(codegen: ExpressionCodegen, type: Type, irType: IrType) : PromisedValue(codegen, type, irType) { override fun materialize() {} } @@ -66,11 +71,11 @@ val PromisedValue.materialized: MaterialValue // Materialize and disregard this value. Materialization is forced because, presumably, // we only wanted the side effects anyway. -fun PromisedValue.discard(): MaterialValue { +fun PromisedValue.discard(): PromisedValue { materialize() if (type !== Type.VOID_TYPE) AsmUtil.pop(mv, type) - return codegen.voidValue + return codegen.immaterialUnitValue } private val IrType.unboxed: IrType @@ -103,9 +108,8 @@ fun PromisedValue.coerceInlineClasses(type: Type, irType: IrType, target: Type, } // On materialization, cast the value to a different type. -fun PromisedValue.coerce(target: Type, irTarget: IrType? = null): PromisedValue { - if (irType != null && irTarget != null) - coerceInlineClasses(type, irType, target, irTarget)?.let { return it } +fun PromisedValue.coerce(target: Type, irTarget: IrType): PromisedValue { + coerceInlineClasses(type, irType, target, irTarget)?.let { return it } return when (type) { // All unsafe coercions between irTypes should use the UnsafeCoerce intrinsic target -> this @@ -129,14 +133,26 @@ fun PromisedValue.boxInlineClasses(irTarget: IrType) = coerceToBoxed(irTarget) else this // Same as above, but with a return type that allows conditional jumping. -fun PromisedValue.coerceToBoolean() = when (val coerced = coerce(Type.BOOLEAN_TYPE)) { - is BooleanValue -> coerced - else -> object : BooleanValue(codegen) { - override fun jumpIfFalse(target: Label) = coerced.materialize().also { mv.ifeq(target) } - override fun jumpIfTrue(target: Label) = coerced.materialize().also { mv.ifne(target) } - override fun materialize() = coerced.materialize() +fun PromisedValue.coerceToBoolean() = + when (val coerced = coerce(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)) { + is BooleanValue -> coerced + else -> object : BooleanValue(codegen) { + override fun jumpIfFalse(target: Label) = coerced.materialize().also { mv.ifeq(target) } + override fun jumpIfTrue(target: Label) = coerced.materialize().also { mv.ifne(target) } + override fun materialize() = coerced.materialize() + } } -} -val ExpressionCodegen.voidValue: MaterialValue - get() = MaterialValue(this, Type.VOID_TYPE, null) +// Non-materialized value of Unit type +// This value is only materialized when calling coerce, which is why it is represented +// as a MaterialValue even though there is nothing on the stack. +val ExpressionCodegen.immaterialUnitValue: ImmaterialValue + get() = ImmaterialValue(this, Type.VOID_TYPE, context.irBuiltIns.unitType) + +// Non-materialized default value for the given type +fun ExpressionCodegen.defaultValue(irType: IrType): PromisedValue = + object : PromisedValue(this, typeMapper.mapType(irType), irType) { + override fun materialize() { + StackValue.coerce(Type.VOID_TYPE, type, codegen.mv) + } + } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt index 54f505321b7..849c5296102 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt @@ -217,7 +217,7 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf } } - protected fun genBranchTargets(): PromisedValue { + private fun genBranchTargets(): PromisedValue { with(codegen) { val endLabel = Label() for ((thenExpression, label) in expressionToLabels) { @@ -226,8 +226,8 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf mv.goTo(endLabel) } mv.visitLabel(defaultLabel) - val stackValue = elseExpression?.accept(codegen, data) ?: voidValue - val result = stackValue.coerce(expression.type).materialized + val stackValue = elseExpression?.accept(codegen, data)?.coerce(expression.type) ?: defaultValue(expression.type) + val result = stackValue.materialized mv.mark(endLabel) return result } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt index 61ac15e29af..700e5526589 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt @@ -33,8 +33,8 @@ object JavaClassProperty : IntrinsicMethod() { fun invokeWith(value: PromisedValue) = when { value.type == Type.VOID_TYPE -> - invokeGetClass(value.coerce(AsmTypes.UNIT_TYPE)) - value.irType?.isInlined() == true -> + invokeGetClass(value.coerce(AsmTypes.UNIT_TYPE, value.codegen.context.irBuiltIns.unitType)) + value.irType.isInlined() -> invokeGetClass(value.coerceToBoxed(value.irType)) isPrimitive(value.type) -> { value.discard()