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 586e08d60ba..07490a22113 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 @@ -7,6 +7,8 @@ package org.jetbrains.kotlin.backend.jvm.codegen import org.jetbrains.kotlin.backend.common.descriptors.propertyIfAccessor import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty +import org.jetbrains.kotlin.backend.jvm.intrinsics.Not import org.jetbrains.kotlin.backend.jvm.lower.CrIrType import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.codegen.* @@ -15,9 +17,7 @@ import org.jetbrains.kotlin.codegen.ExpressionCodegen.putReifiedOperationMarkerI import org.jetbrains.kotlin.codegen.inline.* 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.JavaClassProperty import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysFalseIfeq -import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysTrueIfeq import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter import org.jetbrains.kotlin.codegen.state.GenerationState @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.isUnit import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementVisitor @@ -42,7 +43,6 @@ import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isNothing -import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.types.typesApproximation.approximateCapturedTypes import org.jetbrains.kotlin.types.upperIfFlexible import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull @@ -100,13 +100,80 @@ class BlockInfo private constructor(val parent: BlockInfo?) { class VariableInfo(val declaration: IrVariable, val index: Int, val type: Type, val startLabel: Label) +// 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) { + // 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() +} + +// A value that *has* been fully constructed. +class MaterialValue(codegen: ExpressionCodegen, type: Type) : PromisedValue(codegen, type) { + override fun materialize() {} +} + +// A value that can be branched on. JVM has certain branching instructions which can be used +// to optimize these. +abstract class BooleanValue(codegen: ExpressionCodegen) : PromisedValue(codegen, Type.BOOLEAN_TYPE) { + abstract fun jumpIfFalse(target: Label) + abstract fun jumpIfTrue(target: Label) + + override fun materialize() { + val const0 = Label() + val end = Label() + jumpIfFalse(const0) + codegen.mv.iconst(1) + codegen.mv.goTo(end) + codegen.mv.mark(const0) + codegen.mv.iconst(0) + codegen.mv.mark(end) + } +} + +// Same as materialize(), but return a representation of the result. +val PromisedValue.materialized: MaterialValue + get() { + materialize() + return MaterialValue(codegen, type) + } + +// Materialize and disregard this value. Materialization is forced because, presumably, +// we only wanted the side effects anyway. +fun PromisedValue.discard(): MaterialValue { + materialize() + if (type !== Type.VOID_TYPE) + AsmUtil.pop(codegen.mv, type) + return MaterialValue(codegen, Type.VOID_TYPE) +} + +// On materialization, cast the value to a different type. +fun PromisedValue.coerce(target: Type) = when (target) { + Type.VOID_TYPE -> discard() + type -> this + else -> object : PromisedValue(codegen, target) { + // TODO remove dependency + override fun materialize() = StackValue.coerce(this@coerce.materialized.type, type, codegen.mv) + } +} + +// 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 { codegen.mv.ifeq(target) } + override fun jumpIfTrue(target: Label) = coerced.materialize().also { codegen.mv.ifne(target) } + override fun materialize() = coerced.materialize() + } +} + class ExpressionCodegen( val irFunction: IrFunction, val frame: IrFrameMap, val mv: InstructionAdapter, val classCodegen: ClassCodegen, val isInlineLambda: Boolean = false -) : IrElementVisitor, BaseExpressionCodegen { +) : IrElementVisitor, BaseExpressionCodegen { var finallyDepth = 0 @@ -137,47 +204,16 @@ class ExpressionCodegen( private val CallableDescriptor.asmType: Type get() = typeMapper.mapType(this) - private val IrExpression.asmType: Type + val IrExpression.asmType: Type get() = type.asmType // Assume this expression's result has already been materialized on the stack // with the correct type. - val IrExpression.onStack: StackValue - get() = StackValue.onStack(asmType, type.toKotlinType()) + val IrExpression.onStack: MaterialValue + get() = MaterialValue(this@ExpressionCodegen, asmType) - // If this value is immaterial, construct an object on the top of the stack. This operation is - // idempotent and must always be done before generating other values or emitting raw bytecode. - internal fun StackValue.materialize(): StackValue { - put(type, kotlinType, mv) - return StackValue.onStack(type, kotlinType) - } - - // Same as above, but if the type is Unit, do not materialize the actual push of a constant value. - // This must be done before fan-in jumping to normalize the stack heights. - internal fun StackValue.materializeNonUnit(): StackValue = - if (kotlinType != null && kotlinType!!.isUnit()) - discard().coerce(kotlinType!!) - else - materialize() - - // Materialize and disregard this value. Materialization is forced because, presumably, - // we only wanted the side effects anyway. - internal fun StackValue.discard(): StackValue = - coerce(Type.VOID_TYPE).materialize() - - // On materialization, cast the value to a different type. - private fun StackValue.coerce(targetType: Type): StackValue = - CoercionValue(this, targetType, null, null) - - private fun StackValue.coerce(toKotlinType: KotlinType): StackValue { - // Avoid the code in `StackValue` that attempts to CHECKCAST java.lang.Objects into Unit. - if (type != Type.VOID_TYPE && toKotlinType.isUnit()) - return coerce(Type.VOID_TYPE).coerce(toKotlinType) - return CoercionValue(this, toKotlinType.asmType, toKotlinType, null) - } - - internal fun StackValue.coerce(toIrType: IrType): StackValue = - coerce(toIrType.toKotlinType()) + val voidValue: MaterialValue + get() = MaterialValue(this@ExpressionCodegen, Type.VOID_TYPE) private fun markNewLabel() = Label().apply { mv.visitLabel(this) } @@ -196,12 +232,8 @@ class ExpressionCodegen( // TODO remove fun gen(expression: IrElement, type: Type, data: BlockInfo): StackValue { - return expression.accept(this, data).coerce(type).materialize() - } - - // TODO remove - fun gen(expression: IrElement, data: BlockInfo): StackValue { - return expression.accept(this, data).materialize() + expression.accept(this, data).coerce(type).materialize() + return StackValue.onStack(type) } fun generate() { @@ -257,16 +289,12 @@ class ExpressionCodegen( ) } - override fun visitBlock(expression: IrBlock, data: BlockInfo): StackValue { + override fun visitBlock(expression: IrBlock, data: BlockInfo): PromisedValue { + if (expression.isTransparentScope) + return super.visitBlock(expression, data) val info = data.create() return super.visitBlock(expression, info).apply { - if (!expression.isTransparentScope) { - writeLocalVariablesInTable(info) - } else { - info.variables.forEach { - data.variables.add(it) - } - } + writeLocalVariablesInTable(info) } } @@ -292,26 +320,25 @@ class ExpressionCodegen( } } - private fun visitStatementContainer(container: IrStatementContainer, data: BlockInfo): StackValue = - container.statements.fold(StackValue.none()) { prev, exp -> + private fun visitStatementContainer(container: IrStatementContainer, data: BlockInfo) = + container.statements.fold(voidValue as PromisedValue) { prev, exp -> prev.discard() exp.accept(this, data).also { (exp as? IrExpression)?.markEndOfStatementIfNeeded() } } - override fun visitBlockBody(body: IrBlockBody, data: BlockInfo): StackValue { - return visitStatementContainer(body, data).discard() - } + override fun visitBlockBody(body: IrBlockBody, data: BlockInfo) = + visitStatementContainer(body, data).discard() - override fun visitContainerExpression(expression: IrContainerExpression, data: BlockInfo): StackValue { - return visitStatementContainer(expression, data).coerce(expression.type) - } + override fun visitContainerExpression(expression: IrContainerExpression, data: BlockInfo) = + visitStatementContainer(expression, data).coerce(expression.asmType) - override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): StackValue { + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) + mv.load(0, OBJECT_TYPE) // HACK return generateCall(expression, null, data) } - override fun visitCall(expression: IrCall, data: BlockInfo): StackValue { + override fun visitCall(expression: IrCall, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) if (expression.descriptor is ConstructorDescriptor) { return generateNewCall(expression, data) @@ -319,7 +346,7 @@ class ExpressionCodegen( return generateCall(expression, expression.superQualifier, data) } - private fun generateNewCall(expression: IrCall, data: BlockInfo): StackValue { + private fun generateNewCall(expression: IrCall, data: BlockInfo): PromisedValue { val type = expression.asmType if (type.sort == Type.ARRAY) { return generateNewArray(expression, data) @@ -331,7 +358,7 @@ class ExpressionCodegen( return expression.onStack } - private fun generateNewArray(expression: IrCall, data: BlockInfo): StackValue { + private fun generateNewArray(expression: IrCall, data: BlockInfo): PromisedValue { val args = expression.descriptor.valueParameters assert(args.size == 1 || args.size == 2) { "Unknown constructor called: " + args.size + " arguments" } @@ -345,15 +372,20 @@ class ExpressionCodegen( return generateCall(expression, expression.superQualifier, data) } - private fun generateCall(expression: IrFunctionAccessExpression, superQualifier: ClassDescriptor?, data: BlockInfo): StackValue { + private fun generateCall(expression: IrFunctionAccessExpression, superQualifier: ClassDescriptor?, data: BlockInfo): PromisedValue { classCodegen.context.irIntrinsics.getIntrinsic(expression.descriptor.original as CallableMemberDescriptor) - ?.invoke(expression, this, data)?.let { return it } + ?.invoke(expression, this, data)?.let { return it.coerce(expression.asmType) } val isSuperCall = superQualifier != null val callable = resolveToCallable(expression, isSuperCall) return generateCall(expression, callable, data, isSuperCall) } - fun generateCall(expression: IrMemberAccessExpression, callable: Callable, data: BlockInfo, isSuperCall: Boolean = false): StackValue { + fun generateCall( + expression: IrMemberAccessExpression, + callable: Callable, + data: BlockInfo, + isSuperCall: Boolean = false + ): PromisedValue { val callGenerator = getOrCreateCallGenerator(expression, expression.descriptor, data) val receiver = expression.dispatchReceiver @@ -425,64 +457,66 @@ class ExpressionCodegen( if (returnType != null && returnType.isNothing()) { mv.aconst(null) mv.athrow() - return expression.onStack + return voidValue } else if (expression.descriptor is ConstructorDescriptor) { - return StackValue.none() + return voidValue + } else if (expression.type.isUnit()) { + // NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail. + return MaterialValue(this, callable.returnType).discard().coerce(expression.asmType) } - - return StackValue.onStack(callable.returnType, returnType).coerce(expression.type) + return MaterialValue(this, callable.returnType).coerce(expression.asmType) } - override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: BlockInfo): StackValue { - //HACK - StackValue.local(0, OBJECT_TYPE).put(OBJECT_TYPE, mv) - return super.visitDelegatingConstructorCall(expression, data) - } - - override fun visitVariable(declaration: IrVariable, data: BlockInfo): StackValue { + override fun visitVariable(declaration: IrVariable, data: BlockInfo): PromisedValue { val varType = typeMapper.mapType(declaration.descriptor) val index = frame.enter(declaration.symbol, varType) declaration.markLineNumber(startOffset = true) declaration.initializer?.let { - StackValue.local(index, varType).store(it.accept(this, data).coerce(varType), mv) + it.accept(this, data).coerce(varType).materialize() + mv.store(index, varType) it.markLineNumber(startOffset = true) } data.variables.add(VariableInfo(declaration, index, varType, markNewLabel())) - return StackValue.none() + return voidValue } - override fun visitGetValue(expression: IrGetValue, data: BlockInfo): StackValue { + override fun visitGetValue(expression: IrGetValue, data: BlockInfo): PromisedValue { // Do not generate line number information for loads from compiler-generated // temporary variables. They do not correspond to variable loads in user code. if (expression.symbol.owner.origin != IrDeclarationOrigin.IR_TEMPORARY_VARIABLE) expression.markLineNumber(startOffset = true) - return StackValue.local(findLocalIndex(expression.symbol), expression.asmType).coerce(expression.type) + mv.load(findLocalIndex(expression.symbol), expression.asmType) + return expression.onStack } - private fun generateFieldValue(expression: IrFieldAccessExpression, data: BlockInfo, materialize: Boolean): StackValue { - val receiverValue = expression.receiver?.accept(this, data) ?: StackValue.none() - val materialReceiver = if (materialize) receiverValue.materialize() else receiverValue - val propertyDescriptor = expression.descriptor - - val realDescriptor = DescriptorUtils.unwrapFakeOverride(propertyDescriptor) - val fieldKotlinType = realDescriptor.original.type - val fieldType = typeMapper.mapType(fieldKotlinType) - val ownerType = typeMapper.mapImplementationOwner(propertyDescriptor) - val fieldName = propertyDescriptor.name.asString() - val isStatic = expression.receiver == null // TODO - - return StackValue.field(fieldType, fieldKotlinType, ownerType, fieldName, isStatic, materialReceiver, realDescriptor) - } - - override fun visitGetField(expression: IrGetField, data: BlockInfo): StackValue { + override fun visitFieldAccess(expression: IrFieldAccessExpression, data: BlockInfo): PromisedValue { + val realDescriptor = DescriptorUtils.unwrapFakeOverride(expression.descriptor) + val fieldType = typeMapper.mapType(realDescriptor.original.type) + val ownerType = typeMapper.mapImplementationOwner(expression.descriptor).internalName + val fieldName = expression.descriptor.name.asString() + val isStatic = expression.receiver == null expression.markLineNumber(startOffset = true) - return generateFieldValue(expression, data, false).coerce(expression.type) + expression.receiver?.accept(this, data)?.materialize() + return if (expression is IrSetField) { + expression.value.accept(this, data).coerce(fieldType).materialize() + when { + isStatic -> mv.putstatic(ownerType, fieldName, fieldType.descriptor) + else -> mv.putfield(ownerType, fieldName, fieldType.descriptor) + } + voidValue.coerce(expression.asmType) + } else { + when { + isStatic -> mv.getstatic(ownerType, fieldName, fieldType.descriptor) + else -> mv.getfield(ownerType, fieldName, fieldType.descriptor) + } + MaterialValue(this, fieldType).coerce(expression.asmType) + } } - override fun visitSetField(expression: IrSetField, data: BlockInfo): StackValue { + override fun visitSetField(expression: IrSetField, data: BlockInfo): PromisedValue { val expressionValue = expression.value // Do not add redundant field initializers that initialize to default values. // "expression.origin == null" means that the field is initialized when it is declared, @@ -490,11 +524,7 @@ class ExpressionCodegen( val skip = irFunction is IrConstructor && irFunction.isPrimary && expression.origin == null && expressionValue is IrConst<*> && isDefaultValueForType(expression.symbol.owner.type.asmType, expressionValue.value) - if (!skip) { - expression.markLineNumber(startOffset = true) - generateFieldValue(expression, data, true).store(expressionValue.accept(this, data), mv) - } - return StackValue.none().coerce(expression.type) + return if (skip) voidValue.coerce(expression.asmType) else super.visitSetField(expression, data) } /** @@ -524,37 +554,48 @@ class ExpressionCodegen( throw AssertionError("Non-mapped local declaration: $dump\n in ${irFunction.dump()}") } - override fun visitSetVariable(expression: IrSetVariable, data: BlockInfo): StackValue { + override fun visitSetVariable(expression: IrSetVariable, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) expression.value.markLineNumber(startOffset = true) - val value = expression.value.accept(this, data) - StackValue.local(findLocalIndex(expression.symbol), expression.descriptor.asmType).store(value, mv) - return StackValue.none().coerce(expression.type) + expression.value.accept(this, data).coerce(expression.descriptor.asmType).materialize() + mv.store(findLocalIndex(expression.symbol), expression.descriptor.asmType) + return voidValue.coerce(expression.asmType) } - override fun visitConst(expression: IrConst, data: BlockInfo): StackValue { + override fun visitConst(expression: IrConst, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) - return StackValue.constant(expression.value, expression.asmType).coerce(expression.type) + when (val value = expression.value) { + is Boolean -> return object : BooleanValue(this) { + override fun jumpIfFalse(target: Label) = if (value) Unit else codegen.mv.goTo(target) + override fun jumpIfTrue(target: Label) = if (value) codegen.mv.goTo(target) else Unit + override fun materialize() = codegen.mv.iconst(if (value) 1 else 0) + } + is Char -> mv.iconst(value.toInt()) + is Long -> mv.lconst(value) + is Float -> mv.fconst(value) + is Double -> mv.dconst(value) + is Number -> mv.iconst(value.toInt()) + else -> mv.aconst(value) + } + return expression.onStack } - override fun visitExpressionBody(body: IrExpressionBody, data: BlockInfo): StackValue { - return body.expression.accept(this, data) - } + override fun visitExpressionBody(body: IrExpressionBody, data: BlockInfo) = + body.expression.accept(this, data) - override fun visitElement(element: IrElement, data: BlockInfo): StackValue { + override fun visitElement(element: IrElement, data: BlockInfo) = throw AssertionError( "Unexpected IR element found during code generation. Either code generation for it " + "is not implemented, or it should have been lowered: ${element.render()}" ) - } // TODO maybe remove? - override fun visitClass(declaration: IrClass, data: BlockInfo): StackValue { + override fun visitClass(declaration: IrClass, data: BlockInfo): PromisedValue { classCodegen.generateLocalClass(declaration) - return StackValue.none() + return voidValue } - override fun visitVararg(expression: IrVararg, data: BlockInfo): StackValue { + override fun visitVararg(expression: IrVararg, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) val outType = expression.type val type = expression.asmType @@ -624,13 +665,11 @@ class ExpressionCodegen( } else { mv.iconst(size) newArrayInstruction(expression.type.toKotlinType()) - val elementKotlinType = classCodegen.context.builtIns.getArrayElementType(outType.toKotlinType()) for ((i, element) in expression.elements.withIndex()) { mv.dup() - val array = StackValue.onStack(elementType, outType.toKotlinType()) - val index = StackValue.constant(i).materialize() - val value = element.accept(this, data).coerce(elementType).materialize() - StackValue.arrayElement(elementType, elementKotlinType, array, index).store(value, mv) + mv.iconst(i) + element.accept(this, data).coerce(elementType).materialize() + mv.astore(elementType) } } return expression.onStack @@ -652,7 +691,7 @@ class ExpressionCodegen( } } - override fun visitReturn(expression: IrReturn, data: BlockInfo): StackValue { + override fun visitReturn(expression: IrReturn, data: BlockInfo): PromisedValue { val owner = expression.returnTargetSymbol.owner //TODO: should be owner != irFunction val isNonLocalReturn = state.typeMapper.mapFunctionName( @@ -665,7 +704,7 @@ class ExpressionCodegen( mv, "java/lang/UnsupportedOperationException", "Non-local returns are not allowed with inlining disabled" ) - return StackValue.none() + return voidValue } val returnType = typeMapper.mapReturnType(owner.descriptor) @@ -679,10 +718,10 @@ class ExpressionCodegen( mv.areturn(returnType) mv.mark(afterReturnLabel) mv.nop()/*TODO check RESTORE_STACK_IN_TRY_CATCH processor*/ - return StackValue.none() + return voidValue } - override fun visitWhen(expression: IrWhen, data: BlockInfo): StackValue { + override fun visitWhen(expression: IrWhen, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) SwitchGenerator(expression, data, this).generate()?.let { return it } @@ -699,9 +738,9 @@ class ExpressionCodegen( if (branch.condition.isFalseConst()) continue // The branch body is dead code. } else { - BranchedValue.condJump(branch.condition.accept(this, data), elseLabel, true, mv) + branch.condition.accept(this, data).coerceToBoolean().jumpIfFalse(elseLabel) } - val result = branch.result.accept(this, data).coerce(expression.type).materializeNonUnit() + val result = branch.result.accept(this, data).coerce(expression.asmType).materialized if (branch.condition.isTrueConst()) { // The rest of the expression is dead code. mv.mark(endLabel) @@ -712,23 +751,23 @@ 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 = StackValue.none().coerce(expression.type).materializeNonUnit() + val result = voidValue.coerce(expression.asmType).materialized mv.mark(endLabel) return result } - override fun visitTypeOperator(expression: IrTypeOperatorCall, data: BlockInfo): StackValue { + override fun visitTypeOperator(expression: IrTypeOperatorCall, data: BlockInfo): PromisedValue { val kotlinType = expression.typeOperand.toKotlinType() val asmType = kotlinType.asmType return when (expression.operator) { IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> { - val result = expression.argument.accept(this, data) + expression.argument.accept(this, data).discard() expression.argument.markEndOfStatementIfNeeded() - result.coerce(expression.type) + voidValue.coerce(expression.asmType) } - IrTypeOperator.IMPLICIT_CAST -> { - expression.argument.accept(this, data).coerce(expression.type) + IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.IMPLICIT_INTEGER_COERCION -> { + expression.argument.accept(this, data).coerce(expression.asmType) } IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> { @@ -746,7 +785,7 @@ class ExpressionCodegen( state.languageVersionSettings.isReleaseCoroutines() ) } - StackValue.onStack(boxedType).coerce(expression.type) + MaterialValue(this, boxedType).coerce(expression.asmType) } IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> { @@ -758,14 +797,15 @@ class ExpressionCodegen( } else { generateIsCheck(mv, kotlinType, type, state.languageVersionSettings.isReleaseCoroutines()) } - if (IrTypeOperator.NOT_INSTANCEOF == expression.operator) { - return StackValue.not(expression.onStack) - } - expression.onStack + // 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).materialize() + val value = expression.argument.accept(this, data).materialized mv.dup() mv.visitLdcInsn("TODO provide message for IMPLICIT_NOTNULL") /*TODO*/ mv.invokestatic( @@ -773,12 +813,7 @@ class ExpressionCodegen( "(Ljava/lang/Object;Ljava/lang/String;)V", false ) // Unbox primitives. - value.coerce(expression.type) - } - - IrTypeOperator.IMPLICIT_INTEGER_COERCION -> { - // Force-materialize the intermediate int to ensure that it is in fact an int. - expression.argument.accept(this, data).coerce(Type.INT_TYPE).materialize().coerce(expression.type) + value.coerce(expression.asmType) } else -> throw AssertionError("type operator ${expression.operator} should have been lowered") @@ -798,64 +833,57 @@ class ExpressionCodegen( } } - override fun visitStringConcatenation(expression: IrStringConcatenation, data: BlockInfo): StackValue { + override fun visitStringConcatenation(expression: IrStringConcatenation, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) - return when (expression.arguments.size) { - 0 -> StackValue.constant("", expression.asmType) + when (expression.arguments.size) { + 0 -> mv.aconst("") 1 -> { // Convert single arg to string. - val argStackValue = expression.arguments[0].accept(this, data).materialize() - AsmUtil.genToString(argStackValue, argStackValue.type, argStackValue.kotlinType, typeMapper).put(expression.asmType, mv) - expression.onStack + val type = expression.arguments[0].accept(this, data).materialized.type + AsmUtil.genToString(StackValue.onStack(type), type, null, typeMapper).put(expression.asmType, mv) } else -> { // Use StringBuilder to concatenate. AsmUtil.genStringBuilderConstructor(mv) expression.arguments.forEach { - val stackValue = it.accept(this, data).materialize() - AsmUtil.genInvokeAppendMethod(mv, stackValue.type, stackValue.kotlinType) + AsmUtil.genInvokeAppendMethod(mv, it.accept(this, data).materialized.type, null) } mv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false) - expression.onStack } } + return expression.onStack } - // Avoid true condition generation for tailrec - // ASM and the Java verifier assume that L1 is reachable which causes several verifications to fail. - // To avoid them, trivial jump elimination is required. - // L0 - // ICONST_1 //could be eliminated - // IFEQ L1 //could be eliminated - // .... // no jumps - // GOTO L0 - // L1 - //TODO: write elimination lower - private fun generateLoopJump(condition: IrExpression, data: BlockInfo, label: Label, jumpIfFalse: Boolean) { - condition.markLineNumber(true) - if (condition is IrConst<*> && condition.value == true) { - if (jumpIfFalse) { - mv.fakeAlwaysFalseIfeq(label) - } else { - mv.fakeAlwaysTrueIfeq(label) - } - } else { - BranchedValue.condJump(condition.accept(this, data), label, jumpIfFalse, mv) - } - } - - override fun visitWhileLoop(loop: IrWhileLoop, data: BlockInfo): StackValue { + override fun visitWhileLoop(loop: IrWhileLoop, data: BlockInfo): PromisedValue { val continueLabel = markNewLabel() val endLabel = Label() - generateLoopJump(loop.condition, data, endLabel, true) - + // Mark stack depth for break + mv.fakeAlwaysFalseIfeq(endLabel) + loop.condition.markLineNumber(true) + loop.condition.accept(this, data).coerceToBoolean().jumpIfFalse(endLabel) data.withBlock(LoopInfo(loop, continueLabel, endLabel)) { loop.body?.accept(this, data)?.discard() } mv.goTo(continueLabel) mv.mark(endLabel) + return voidValue + } - return StackValue.none() + override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: BlockInfo): PromisedValue { + val entry = markNewLabel() + val endLabel = Label() + val continueLabel = Label() + // Mark stack depth for break/continue + mv.fakeAlwaysFalseIfeq(continueLabel) + mv.fakeAlwaysFalseIfeq(endLabel) + data.withBlock(LoopInfo(loop, continueLabel, endLabel)) { + loop.body?.accept(this, data)?.discard() + } + mv.visitLabel(continueLabel) + loop.condition.markLineNumber(true) + loop.condition.accept(this, data).coerceToBoolean().jumpIfTrue(entry) + mv.mark(endLabel) + return voidValue } private fun unwindBlockStack(endLabel: Label, data: BlockInfo, loop: IrLoop? = null): LoopInfo? { @@ -868,36 +896,17 @@ class ExpressionCodegen( } } - override fun visitBreakContinue(jump: IrBreakContinue, data: BlockInfo): StackValue { + override fun visitBreakContinue(jump: IrBreakContinue, data: BlockInfo): PromisedValue { jump.markLineNumber(startOffset = true) val endLabel = Label() val stackElement = unwindBlockStack(endLabel, data, jump.loop) ?: throw AssertionError("Target label for break/continue not found") mv.fixStackAndJump(if (jump is IrBreak) stackElement.breakLabel else stackElement.continueLabel) mv.mark(endLabel) - return StackValue.none() + return voidValue } - override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: BlockInfo): StackValue { - val entry = markNewLabel() - val endLabel = Label() - val continueLabel = Label() - - mv.fakeAlwaysFalseIfeq(continueLabel) - mv.fakeAlwaysFalseIfeq(endLabel) - - data.withBlock(LoopInfo(loop, continueLabel, endLabel)) { - loop.body?.accept(this, data)?.discard() - } - - mv.visitLabel(continueLabel) - generateLoopJump(loop.condition, data, entry, false) - mv.mark(endLabel) - - return StackValue.none() - } - - override fun visitTry(aTry: IrTry, data: BlockInfo): StackValue { + override fun visitTry(aTry: IrTry, data: BlockInfo): PromisedValue { aTry.markLineNumber(startOffset = true) return if (aTry.finallyExpression != null) data.withBlock(TryInfo(aTry.finallyExpression!!)) { visitTryWithInfo(aTry, data, it) } @@ -905,15 +914,17 @@ class ExpressionCodegen( visitTryWithInfo(aTry, data, null) } - private fun visitTryWithInfo(aTry: IrTry, data: BlockInfo, tryInfo: TryInfo?): StackValue { + private fun visitTryWithInfo(aTry: IrTry, data: BlockInfo, tryInfo: TryInfo?): PromisedValue { val tryBlockStart = markNewLabel() mv.nop() + val tryAsmType = aTry.asmType val tryResult = aTry.tryResult.accept(this, data) val isExpression = true //TODO: more wise check is required - var savedValue: StackValue.Local? = null + var savedValue: Int? = null if (isExpression) { - savedValue = StackValue.local(frame.enterTemp(aTry.asmType), aTry.asmType) - savedValue.store(tryResult.coerce(aTry.type), mv) + tryResult.coerce(tryAsmType).materialize() + savedValue = frame.enterTemp(tryAsmType) + mv.store(savedValue, tryAsmType) } else { tryResult.discard() } @@ -939,7 +950,8 @@ class ExpressionCodegen( catchBody.markLineNumber(true) val catchResult = catchBody.accept(this, data) if (savedValue != null) { - savedValue.store(catchResult.coerce(aTry.type), mv) + catchResult.coerce(tryAsmType).materialize() + mv.store(savedValue, tryAsmType) } else { catchResult.discard() } @@ -986,11 +998,11 @@ class ExpressionCodegen( // TODO: generate a common `finally` for try & catch blocks here? Right now this breaks the inliner. if (savedValue != null) { - savedValue.put(mv) - frame.leaveTemp(aTry.asmType) - return StackValue.onStack(aTry.asmType) + mv.load(savedValue, tryAsmType) + frame.leaveTemp(tryAsmType) + return aTry.onStack } - return StackValue.none() + return voidValue } private fun genTryCatchCover(catchStart: Label, tryStart: Label, tryEnd: Label, tryGaps: List>, type: String?) { @@ -1007,7 +1019,7 @@ class ExpressionCodegen( if (isFinallyMarkerRequired()) { generateFinallyMarker(mv, finallyDepth, true) } - gen(tryInfo.onExit, data).discard() + tryInfo.onExit.accept(this, data).discard() if (isFinallyMarkerRequired()) { generateFinallyMarker(mv, finallyDepth, false) } @@ -1023,10 +1035,9 @@ class ExpressionCodegen( if (data.hasFinallyBlocks()) { if (Type.VOID_TYPE != returnType) { val returnValIndex = frame.enterTemp(returnType) - val localForReturnValue = StackValue.local(returnValIndex, returnType) - localForReturnValue.store(StackValue.onStack(returnType), mv) + mv.store(returnValIndex, returnType) unwindBlockStack(afterReturnLabel, data, null) - localForReturnValue.put(returnType, mv) + mv.load(returnValIndex, returnType) frame.leaveTemp(returnType) } else { unwindBlockStack(afterReturnLabel, data, null) @@ -1034,36 +1045,32 @@ class ExpressionCodegen( } } - override fun visitThrow(expression: IrThrow, data: BlockInfo): StackValue { + override fun visitThrow(expression: IrThrow, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) expression.value.accept(this, data).coerce(AsmTypes.JAVA_THROWABLE_TYPE).materialize() mv.athrow() - return StackValue.none() + return voidValue } - override fun visitGetClass(expression: IrGetClass, data: BlockInfo): StackValue { + override fun visitGetClass(expression: IrGetClass, data: BlockInfo) = generateClassLiteralReference(expression, true, data) - return expression.onStack - } - override fun visitClassReference(expression: IrClassReference, data: BlockInfo): StackValue { + override fun visitClassReference(expression: IrClassReference, data: BlockInfo) = generateClassLiteralReference(expression, true, data) - return expression.onStack - } fun generateClassLiteralReference( classReference: IrExpression, wrapIntoKClass: Boolean, data: BlockInfo - ) { - if (classReference !is IrClassReference /* && DescriptorUtils.isObjectQualifier(classReference.descriptor)*/) { - assert(classReference is IrGetClass) - JavaClassProperty.generateImpl(mv, (classReference as IrGetClass).argument.accept(this, data)) - } else { + ): PromisedValue { + if (classReference is IrGetClass) { + // TODO transform one sort of access into the other? + JavaClassProperty.invokeWith(classReference.argument.accept(this, data)) + } else if (classReference is IrClassReference) { val classType = classReference.classType if (classType is CrIrType) { putJavaLangClassInstance(mv, classType.type, null, typeMapper) - return + return classReference.onStack } else { val kotlinType = classType.toKotlinType() if (TypeUtils.isTypeParameter(kotlinType)) { @@ -1073,12 +1080,14 @@ class ExpressionCodegen( putJavaLangClassInstance(mv, typeMapper.mapType(kotlinType), kotlinType, typeMapper) } + } else { + throw AssertionError("not an IrGetClass or IrClassReference: ${classReference.dump()}") } if (wrapIntoKClass) { wrapJavaClassIntoKClass(mv) } - + return classReference.onStack } private fun resolveToCallable(irCall: IrMemberAccessExpression, isSuper: Boolean): Callable { 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 798e686eed5..fa17a8bd907 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 @@ -5,9 +5,7 @@ package org.jetbrains.kotlin.backend.jvm.codegen -import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.`when`.SwitchCodegen.Companion.preferLookupOverSwitch -import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.dump @@ -22,7 +20,7 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf data class ValueToLabel(val value: Any?, val label: Label) // @return null if the IrWhen cannot be emitted as lookupswitch or tableswitch. - fun generate(): StackValue? { + fun generate(): PromisedValue? { val expressionToLabels = ArrayList() var elseExpression: IrExpression? = null val callToLabels = ArrayList() @@ -187,7 +185,7 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf open fun shouldOptimize() = false - open fun genOptimizedIfEnoughCases(): StackValue? { + open fun genOptimizedIfEnoughCases(): PromisedValue? { if (!shouldOptimize()) return null @@ -219,17 +217,17 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf } } - protected fun genBranchTargets(): StackValue { + protected fun genBranchTargets(): PromisedValue { with(codegen) { val endLabel = Label() for ((thenExpression, label) in expressionToLabels) { mv.visitLabel(label) - thenExpression.accept(codegen, data).coerce(expression.type).materializeNonUnit() + thenExpression.accept(codegen, data).coerce(expression.asmType).materialized mv.goTo(endLabel) } mv.visitLabel(defaultLabel) - val stackValue = elseExpression?.accept(codegen, data) ?: StackValue.none() - val result = stackValue.coerce(expression.type).materializeNonUnit() + val stackValue = elseExpression?.accept(codegen, data) ?: voidValue + val result = stackValue.coerce(expression.asmType).materialized mv.mark(endLabel) return result } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/ArrayConstructor.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/ArrayConstructor.kt index 610cfd4012e..accf489d2fb 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/ArrayConstructor.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/ArrayConstructor.kt @@ -35,11 +35,7 @@ object ArrayConstructor : IntrinsicMethod() { return object : IrIntrinsicFunction(expression, signature, context, expression.argTypes(context)) { override fun invoke(v: InstructionAdapter, codegen: ExpressionCodegen, data: BlockInfo): StackValue { - // TODO fix this hack - val result = codegen.generateCall(expression, this, data) - with(codegen) { - result.materialize() - } + codegen.generateCall(expression, this, data).materialize() return StackValue.onStack(Type.getObjectType("[" + AsmTypes.OBJECT_TYPE.internalName)) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt index fa6b5385b3f..745533a88ac 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt @@ -16,16 +16,19 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics +import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.backend.jvm.JvmBackendContext -import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo -import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen +import org.jetbrains.kotlin.backend.jvm.codegen.* import org.jetbrains.kotlin.codegen.AsmUtil.comparisonOperandType -import org.jetbrains.kotlin.codegen.StackValue +import org.jetbrains.kotlin.codegen.BranchedValue +import org.jetbrains.kotlin.codegen.NumberCompare +import org.jetbrains.kotlin.codegen.ObjectCompare import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression import org.jetbrains.kotlin.lexer.KtSingleValueToken import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import java.lang.UnsupportedOperationException @@ -62,19 +65,34 @@ class CompareTo : IntrinsicMethod() { } } +class BooleanComparison(val op: IElementType, val a: MaterialValue, val b: MaterialValue) : BooleanValue(a.codegen) { + override fun jumpIfFalse(target: Label) { + // TODO 1. get rid of the dependency; 2. take `b.type` into account. + val opcode = if (a.type.sort == Type.OBJECT) + ObjectCompare.getObjectCompareOpcode(op) + else + NumberCompare.patchOpcode(NumberCompare.getNumberCompareOpcode(op), codegen.mv, op, a.type) + codegen.mv.visitJumpInsn(opcode, target) + } + + override fun jumpIfTrue(target: Label) { + val opcode = if (a.type.sort == Type.OBJECT) + BranchedValue.negatedOperations[ObjectCompare.getObjectCompareOpcode(op)]!! + else + NumberCompare.patchOpcode(BranchedValue.negatedOperations[NumberCompare.getNumberCompareOpcode(op)]!!, codegen.mv, op, a.type) + codegen.mv.visitJumpInsn(opcode, target) + } +} class PrimitiveComparison( private val primitiveNumberType: KotlinType, private val operatorToken: KtSingleValueToken ) : IntrinsicMethod() { - override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): StackValue? { + override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? { val parameterType = codegen.typeMapper.mapType(primitiveNumberType) val (left, right) = expression.receiverAndArgs() - return StackValue.cmp( - operatorToken, - parameterType, - codegen.gen(left, parameterType, data), - codegen.gen(right, parameterType, data) - ) + val a = left.accept(codegen, data).coerce(parameterType).materialized + val b = right.accept(codegen, data).coerce(parameterType).materialized + return BooleanComparison(operatorToken, a, b) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt index 4ca3abb2db7..4e56dd2348c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt @@ -7,8 +7,7 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.backend.jvm.JvmBackendContext -import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo -import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen +import org.jetbrains.kotlin.backend.jvm.codegen.* import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.AsmUtil.* import org.jetbrains.kotlin.codegen.StackValue @@ -19,46 +18,46 @@ import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.util.isNullConst import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE +import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType import org.jetbrains.kotlin.types.typeUtil.upperBoundedByPrimitiveNumberOrNullableType -import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter class Equals(val operator: IElementType) : IntrinsicMethod() { - override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): StackValue? { - val (leftArg, rightArg) = expression.receiverAndArgs() - var leftType = codegen.typeMapper.mapType(leftArg.type.toKotlinType()) - var rightType = codegen.typeMapper.mapType(rightArg.type.toKotlinType()) - if (isPrimitive(leftType) != isPrimitive(rightType)) { - leftType = boxType(leftType) - rightType = boxType(rightType) - } - - val opToken = expression.origin - return if (leftArg.isNullConst() || rightArg.isNullConst()) { - val other = if (leftArg.isNullConst()) rightArg else leftArg - return StackValue.compareWithNull(other.accept(codegen, data), Opcodes.IFNONNULL) - } else if (leftType.isFloatingPoint && rightType.isFloatingPoint) { - genEqualsBoxedOnStack(operator) - } else if (opToken === IrStatementOrigin.EQEQEQ || opToken === IrStatementOrigin.EXCLEQEQ) { - // TODO: always casting to the type of the left operand in case of primitives looks wrong - val operandType = if (isPrimitive(leftType)) leftType else OBJECT_TYPE - StackValue.cmp(operator, operandType, codegen.gen(leftArg, operandType, data), codegen.gen(rightArg, operandType, data)) - } else { - genEqualsForExpressionsOnStack(operator, codegen.gen(leftArg, leftType, data), codegen.gen(rightArg, rightType, data)) - } + private class BooleanNullCheck(val value: PromisedValue) : BooleanValue(value.codegen) { + override fun jumpIfFalse(target: Label) = value.materialize().also { codegen.mv.ifnonnull(target) } + override fun jumpIfTrue(target: Label) = value.materialize().also { codegen.mv.ifnull(target) } } - private val Type.isFloatingPoint: Boolean - get() = this == Type.DOUBLE_TYPE || this == Type.FLOAT_TYPE + override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? { + val (a, b) = expression.receiverAndArgs() + if (a.isNullConst() || b.isNullConst()) { + return BooleanNullCheck(if (a.isNullConst()) b.accept(codegen, data) else a.accept(codegen, data)) + } + + val leftType = with(codegen) { a.asmType } + val rightType = with(codegen) { b.asmType } + val opToken = expression.origin + val useEquals = opToken !== IrStatementOrigin.EQEQEQ && opToken !== IrStatementOrigin.EXCLEQEQ && + // `==` is `equals` for objects and floating-point numbers. In the latter case, the difference + // is that `equals` is a total order (-0 < +0 and NaN == NaN) and `===` is IEEE754-compliant. + (!isPrimitive(leftType) || leftType != rightType || leftType == Type.FLOAT_TYPE || leftType == Type.DOUBLE_TYPE) + val operandType = if (!isPrimitive(leftType) || useEquals) AsmTypes.OBJECT_TYPE else leftType + val aValue = a.accept(codegen, data).coerce(operandType).materialized + val bValue = b.accept(codegen, data).coerce(operandType).materialized + if (useEquals) { + AsmUtil.genAreEqualCall(codegen.mv) + return MaterialValue(codegen, Type.BOOLEAN_TYPE) + } + return BooleanComparison(operator, aValue, bValue) + } } class Ieee754Equals(val operandType: Type) : IntrinsicMethod() { - private val boxedOperandType = AsmUtil.boxType(operandType) override fun toCallable( @@ -110,4 +109,3 @@ class Ieee754Equals(val operandType: Type) : IntrinsicMethod() { } } } - diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt index de7ccd6b741..b142d2223a9 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt @@ -8,8 +8,7 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen -import org.jetbrains.kotlin.codegen.StackValue -import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression @@ -25,12 +24,14 @@ abstract class IntrinsicMethod { context: JvmBackendContext ): IrIntrinsicFunction = TODO("implement toCallable() or invoke() of $this") - open fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): StackValue? = - toCallable( - expression, - codegen.typeMapper.mapSignatureSkipGeneric(expression.descriptor), - codegen.context - ).invoke(codegen.mv, codegen, data) + open fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? = + with(codegen) { + val descriptor = typeMapper.mapSignatureSkipGeneric(expression.descriptor) + val stackValue = toCallable(expression, descriptor, context).invoke(mv, codegen, data) + return object : PromisedValue(codegen, stackValue.type) { + override fun materialize() = stackValue.put(codegen.mv) + } + } companion object { fun calcReceiverType(call: IrMemberAccessExpression, context: JvmBackendContext): Type { 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 af1adc54b62..efb3ff7719b 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 @@ -16,41 +16,29 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics -import org.jetbrains.kotlin.backend.jvm.JvmBackendContext -import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo -import org.jetbrains.kotlin.codegen.AsmUtil +import org.jetbrains.kotlin.backend.jvm.codegen.* import org.jetbrains.kotlin.codegen.AsmUtil.boxType import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive -import org.jetbrains.kotlin.codegen.StackValue -import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression -import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature +import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression +import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.org.objectweb.asm.Type -import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter object JavaClassProperty : IntrinsicMethod() { - - override fun toCallable(expression: IrMemberAccessExpression, signature: JvmMethodSignature, context: JvmBackendContext): IrIntrinsicFunction { - return object: IrIntrinsicFunction(expression, signature, context) { - - override fun invoke(v: InstructionAdapter, codegen: org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen, data: BlockInfo): StackValue { - val value = codegen.gen(expression.extensionReceiver!!, data) - val type = value.type - when { - type == Type.VOID_TYPE -> { - StackValue.unit().put(v) - v.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false) - } - isPrimitive(type) -> { - AsmUtil.pop(v, type) - v.getstatic(boxType(type).internalName, "TYPE", "Ljava/lang/Class;") - } - else -> v.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false) - } - - return with(codegen) { - expression.onStack - } - } + fun invokeWith(value: PromisedValue) { + if (value.type == Type.VOID_TYPE) { + return invokeWith(value.coerce(AsmTypes.UNIT_TYPE)) + } + if (isPrimitive(value.type)) { + value.discard() + value.codegen.mv.getstatic(boxType(value.type).internalName, "TYPE", "Ljava/lang/Class;") + } else { + value.materialize() + value.codegen.mv.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false) } } + + override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? { + invokeWith(expression.extensionReceiver!!.accept(codegen, data)) + return with(codegen) { expression.onStack } + } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/KClassJavaProperty.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/KClassJavaProperty.kt index d70ede93a40..7eb80c7f43c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/KClassJavaProperty.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/KClassJavaProperty.kt @@ -18,18 +18,16 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen -import org.jetbrains.kotlin.codegen.StackValue +import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue import org.jetbrains.kotlin.ir.expressions.IrClassReference import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression import org.jetbrains.kotlin.ir.expressions.IrGetClass class KClassJavaProperty : IntrinsicMethod() { - override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): StackValue? { + override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? { val extensionReceiver = expression.extensionReceiver - if (extensionReceiver !is IrClassReference && extensionReceiver !is IrGetClass) { + if (extensionReceiver !is IrClassReference && extensionReceiver !is IrGetClass) return null - } - codegen.generateClassLiteralReference(extensionReceiver, false, data) - return with(codegen) { expression.onStack } + return codegen.generateClassLiteralReference(extensionReceiver, false, data) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt index a739b2abd40..6037a32e56b 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt @@ -17,11 +17,18 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics import org.jetbrains.kotlin.backend.jvm.codegen.BlockInfo +import org.jetbrains.kotlin.backend.jvm.codegen.BooleanValue import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen -import org.jetbrains.kotlin.codegen.StackValue +import org.jetbrains.kotlin.backend.jvm.codegen.coerceToBoolean import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression +import org.jetbrains.org.objectweb.asm.Label class Not : IntrinsicMethod() { + class BooleanNegation(val value: BooleanValue) : BooleanValue(value.codegen) { + override fun jumpIfFalse(target: Label) = value.jumpIfTrue(target) + override fun jumpIfTrue(target: Label) = value.jumpIfFalse(target) + } + override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo) = - StackValue.not(expression.dispatchReceiver!!.accept(codegen, data)) + BooleanNegation(expression.dispatchReceiver!!.accept(codegen, data).coerceToBoolean()) } diff --git a/compiler/testData/codegen/box/primitiveTypes/kt2269NotOptimizable.kt b/compiler/testData/codegen/box/primitiveTypes/kt2269NotOptimizable.kt index c61ecc0c28c..d5a9458f673 100644 --- a/compiler/testData/codegen/box/primitiveTypes/kt2269NotOptimizable.kt +++ b/compiler/testData/codegen/box/primitiveTypes/kt2269NotOptimizable.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR fun identity(x: Int): Int { return when { x < 0 -> identity(x + 1) - 1 diff --git a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/largeMethodWithCoercionToUnit.kt b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/largeMethodWithCoercionToUnit.kt index 4e0d9509d97..1aa84d5cdea 100644 --- a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/largeMethodWithCoercionToUnit.kt +++ b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/largeMethodWithCoercionToUnit.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JVM_IR inline fun inlineFunVoid(f: () -> Unit): Unit { return f() }