diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 5f843d00b59..ae1c5692891 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -17,6 +17,10 @@ internal class KonanLower(val context: Context) { fun lower(irFile: IrFile) { val phaser = PhaseManager(context) + phaser.phase("Lower_builtin_operators") { + BuiltinOperatorLowering(context).runOnFilePostfix(irFile) + } + phaser.phase("Lower_shared_variables") { SharedVariablesLowering(context).runOnFilePostfix(irFile) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index 43d06b7a1ae..a4ad5401885 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -10,6 +10,7 @@ object KonanPhases { val phases = mapOf ( "Backend" to KonanPhase("All backend"), "Optimizer" to KonanPhase("IR Optimizer"), + "Lower_builtin_operators" to KonanPhase("BuiltIn Operators Lowering"), "Lower_shared_variables" to KonanPhase("Shared Variable Lowering"), "Lower_local_functions" to KonanPhase("Local Function Lowering"), "Lower_callables" to KonanPhase("Callable references Lowering"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index be57e8236d8..0141c520cb9 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -43,6 +43,12 @@ internal val FunctionDescriptor.implementation: FunctionDescriptor } } +private val intrinsicAnnotation = FqName("konan.internal.Intrinsic") + +// TODO: check it is external? +internal val FunctionDescriptor.isIntrinsic: Boolean + get() = this.annotations.findAnnotation(intrinsicAnnotation) != null + private val intrinsicTypes = setOf( "kotlin.Unit", "kotlin.Boolean", "kotlin.Char", diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 53532069190..432b4448b9d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isNothing @@ -1723,6 +1724,10 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid return evaluateFunctionInvoke(descriptor, args) } + if (descriptor.isIntrinsic) { + return evaluateIntrinsicCall(callee, args) + } + when (descriptor) { is IrBuiltinOperatorDescriptorBase -> return evaluateOperatorCall (callee, args) is ClassConstructorDescriptor -> return evaluateConstructorCall (callee, args) @@ -1808,14 +1813,31 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid } //-------------------------------------------------------------------------// - private val kEqeq = Name.identifier("EQEQ") - private val ktEqeqeq = Name.identifier("EQEQEQ") - private val kGt0 = Name.identifier("GT0") - private val kGteq0 = Name.identifier("GTEQ0") - private val kLt0 = Name.identifier("LT0") - private val kLteq0 = Name.identifier("LTEQ0") - private val kNot = Name.identifier("NOT") - private val kThrowNpe = Name.identifier("THROW_NPE") + + private fun evaluateIntrinsicCall(callee: IrCall, args: List): LLVMValueRef { + val descriptor = callee.descriptor + val name = descriptor.fqNameUnsafe.asString() + + return when (name) { + "konan.internal.areEqualByValue" -> { + val arg0 = args[0]!! + val arg1 = args[1]!! + assert (arg0.type == arg1.type) + + when (LLVMGetTypeKind(arg0.type)) { + LLVMTypeKind.LLVMFloatTypeKind, LLVMTypeKind.LLVMDoubleTypeKind -> + codegen.fcmpEq(arg0, arg1) + + else -> + codegen.icmpEq(arg0, arg1) + } + } + + else -> TODO(name) + } + } + + //-------------------------------------------------------------------------// private val kImmZero = LLVMConstInt(LLVMInt32Type(), 0, 1)!! private val kImmOne = LLVMConstInt(LLVMInt32Type(), 1, 1)!! private val kTrue = LLVMConstInt(LLVMInt1Type(), 1, 1)!! @@ -1825,16 +1847,14 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid private fun evaluateOperatorCall(callee: IrCall, args: List): LLVMValueRef { context.log("evaluateCall : origin:${ir2string(callee)}") val descriptor = callee.descriptor - when (descriptor.name) { - kEqeq -> return evaluateOperatorEqeq (callee as IrBinaryPrimitiveImpl, args[0]!!, args[1]!!) - ktEqeqeq -> return evaluateOperatorEqeqeq(callee as IrBinaryPrimitiveImpl, args[0]!!, args[1]!!) - kGt0 -> return codegen.icmpGt(args[0]!!, kImmZero) - kGteq0 -> return codegen.icmpGe(args[0]!!, kImmZero) - kLt0 -> return codegen.icmpLt(args[0]!!, kImmZero) - kLteq0 -> return codegen.icmpLe(args[0]!!, kImmZero) - kNot -> return codegen.icmpNe(args[0]!!, kTrue) - // TODO: reconsider. - kThrowNpe -> return evaluateOperatorThrowNpe() + val ib = context.irModule!!.irBuiltins + when (descriptor) { + ib.eqeqeq -> return codegen.icmpEq(args[0]!!, args[1]!!) + ib.gt0 -> return codegen.icmpGt(args[0]!!, kImmZero) + ib.gteq0 -> return codegen.icmpGe(args[0]!!, kImmZero) + ib.lt0 -> return codegen.icmpLt(args[0]!!, kImmZero) + ib.lteq0 -> return codegen.icmpLe(args[0]!!, kImmZero) + ib.booleanNot -> return codegen.icmpNe(args[0]!!, kTrue) else -> { TODO(descriptor.name.toString()) } @@ -1843,19 +1863,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - private fun getEquals(type: KotlinType) : SimpleFunctionDescriptor { - - val name = Name.identifier("equals") - val descriptors = type.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).filter { - it.valueParameters.size == 1 && KotlinBuiltIns.isAnyOrNullableAny(it.valueParameters[0].type) - } - assert(descriptors.size == 1) - val descriptor = descriptors.first() - return descriptor - } - - //-------------------------------------------------------------------------// - private fun getToString(type: KotlinType): SimpleFunctionDescriptor { val descriptor = type.memberScope.getContributedFunctions(kNameToString, NoLookupLocation.FROM_BACKEND).first() return descriptor @@ -1863,81 +1870,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// - private fun getThrowNpe(): SimpleFunctionDescriptor { - val name = Name.identifier("ThrowNullPointerException") - val moduleDescriptor = KonanPlatform.builtIns.builtInsModule - val packageDescriptor = moduleDescriptor.getPackage(FqName("konan.internal")) - val descriptor = packageDescriptor.memberScope.getContributedFunctions( - name, NoLookupLocation.FROM_BACKEND).first() - return descriptor - } - - //-------------------------------------------------------------------------// - - private fun evaluateOperatorEqeq(callee: IrBinaryPrimitiveImpl, arg0: LLVMValueRef, arg1: LLVMValueRef): LLVMValueRef { - - val arg0Type = callee.argument0.type - val isFp = KotlinBuiltIns.isDouble(arg0Type) || KotlinBuiltIns.isFloat(arg0Type) - val isInt = KotlinBuiltIns.isPrimitiveType(arg0Type) && !isFp - when { - isFp -> return codegen.fcmpEq(arg0, arg1) - isInt -> return codegen.icmpEq(arg0, arg1) - else -> return generateEqeqForObjects(callee, arg0, arg1) - } - } - - //-------------------------------------------------------------------------// - - private fun generateEqeqForObjects(callee: IrBinaryPrimitiveImpl, arg0: LLVMValueRef, arg1: LLVMValueRef): LLVMValueRef { - val arg0Type = callee.argument0.type - val descriptor = getEquals(arg0Type) // Get descriptor for "arg0.equals". - if (arg0Type.isMarkedNullable) { // If arg0 is nullable. - val bbEq2 = codegen.basicBlock() // Block to process "eqeq". - val bbEq3 = codegen.basicBlock() // Block to process "eqeqeq". - val bbExit = codegen.basicBlock() // Exit block for feather generation. - val result = codegen.alloca(callee.type) - - val condition = codegen.icmpEq(arg0, codegen.kNullObjHeaderPtr) // Compare arg0 with "null". - codegen.condBr(condition, bbEq3, bbEq2) // If (arg0 == null) bbEq3 else bbEq2. - - codegen.positionAtEnd(bbEq3) // Get generation to bbEq3. - val resultIdentityEq = evaluateOperatorEqeqeq(callee, arg0, arg1) - codegen.store(resultIdentityEq, result) // Store "eqeq" result in "result". - codegen.br(bbExit) // - - codegen.positionAtEnd(bbEq2) // Get generation to bbEq2. - val resultEquals = evaluateSimpleFunctionCall(descriptor, listOf(arg0, arg1)) - codegen.store(resultEquals, result) // Store "eqeqeq" result in "result". - codegen.br(bbExit) // - - codegen.positionAtEnd(bbExit) // Get generation to bbExit. - return codegen.load(result) // Load result. - } else { - return evaluateSimpleFunctionCall(descriptor, listOf(arg0, arg1)) - } - } - - //-------------------------------------------------------------------------// - - private fun evaluateOperatorEqeqeq(callee: IrBinaryPrimitiveImpl, - arg0: LLVMValueRef, arg1: LLVMValueRef): LLVMValueRef { - - val arg0Type = callee.argument0.type - - return when { - KotlinBuiltIns.isPrimitiveType(arg0Type) -> TODO("${ir2string(callee)}") - else -> codegen.icmpEq(arg0, arg1) - } - } - - private fun evaluateOperatorThrowNpe(): LLVMValueRef { - val result = evaluateSimpleFunctionCall(getThrowNpe(), listOf()) - codegen.unreachable() - return result - } - - //-------------------------------------------------------------------------// - private fun generateWhenCase(isUnit:Boolean, isNothing:Boolean, resultPtr: LLVMValueRef?, branch: IrBranch, bbNext: LLVMBasicBlockRef?, bbExit: LLVMBasicBlockRef?) { val neitherUnitNorNothing = !isNothing && !isUnit // If branches doesn't end with 'return' either result hasn't got 'unit' type. val branchResult = branch.result @@ -2001,7 +1933,11 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid // In such case it would be possible to check that all args are available and in the correct order. // However, it currently requires some refactoring to be performed. private fun call(descriptor: FunctionDescriptor, function: LLVMValueRef, args: List): LLVMValueRef { - return currentCodeContext.genCall(function, args) + val result = currentCodeContext.genCall(function, args) + if (descriptor.returnType?.isNothing() == true) { + codegen.unreachable() + } + return result } //-------------------------------------------------------------------------// diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt index 823dffeec64..79ed2578c40 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt @@ -109,15 +109,6 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr } } - override fun IrExpression.useAsArgument(parameter: ParameterDescriptor): IrExpression { - return if (parameter.containingDeclaration == irBuiltins.eqeq) { - this // Do not box arguments of `==` because codegen expects them to be unboxed. - // TODO: implement `==` lowering at IR level and remove this hack. - } else { - this.useAsValue(parameter) - } - } - /** * @return the element of [primitiveTypes] if given type is represented as primitive type in generated code, * or `null` if represented as object reference. @@ -168,7 +159,8 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr private fun IrExpression.box(primitiveType: KotlinType): IrExpression { val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!! - val boxFunction = context.builtIns.getKonanInternalFunctions("box${primitiveTypeClass.name}").single() + val boxFunction = context.builtIns.getKonanInternalFunctions("box${primitiveTypeClass.name}").singleOrNull() ?: + TODO(primitiveType.toString()) return IrCallImpl(startOffset, endOffset, boxFunction).apply { putValueArgument(0, this@box) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt new file mode 100644 index 00000000000..d9a1e48bd49 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt @@ -0,0 +1,91 @@ +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions +import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptor +import org.jetbrains.kotlin.ir.expressions.IrBody +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrTry +import org.jetbrains.kotlin.ir.expressions.impl.IrBinaryPrimitiveImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrTryImpl +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf + +/** + * This lowering pass lowers some calls to [IrBuiltinOperatorDescriptor]s. + */ +internal class BuiltinOperatorLowering(val context: Context) : BodyLoweringPass { + + private val transformer = BuiltinOperatorTransformer(context) + + override fun lower(irBody: IrBody) { + irBody.transformChildrenVoid(transformer) + } + +} + +private class BuiltinOperatorTransformer(val context: Context) : IrElementTransformerVoid() { + + private val builtIns = context.builtIns + private val irBuiltins = context.irModule!!.irBuiltins + + override fun visitTry(aTry: IrTry): IrExpression { + // Workaround for the bug in IrTryImpl.transformChildren: transform `finallyExpression` too. + // TODO: fix the bug and remove it. + val transformer = this + return (aTry as IrTryImpl).apply { + tryResult = tryResult.transform(transformer, null) + catches.forEachIndexed { i, irCatch -> + catches[i] = irCatch.transform(transformer, null) + } + finallyExpression = finallyExpression?.transform(transformer, null) + } + } + + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid(this) + + if (expression.descriptor is IrBuiltinOperatorDescriptor) { + return transformBuiltinOperator(expression) + } + + return expression + } + + private fun transformBuiltinOperator(expression: IrCall): IrExpression { + val descriptor = expression.descriptor + + return when (descriptor) { + irBuiltins.eqeq -> { + val binary = expression as IrBinaryPrimitiveImpl + lowerEqeq(binary.argument0, binary.argument1, expression.startOffset, expression.endOffset) + } + + irBuiltins.throwNpe -> IrCallImpl(expression.startOffset, expression.endOffset, + builtIns.getKonanInternalFunctions("ThrowNullPointerException").single()) + + else -> expression + } + } + + private fun lowerEqeq(lhs: IrExpression, rhs: IrExpression, startOffset: Int, endOffset: Int): IrExpression { + // TODO: optimize boxing? + + // TODO: areEqualByValue intrinsics are specially treated by code generator + // and thus can be declared synthetically in the compiler instead of explicitly in the runtime. + + // Find a type-compatible `konan.internal.areEqualByValue` intrinsic: + val equals = builtIns.getKonanInternalFunctions("areEqualByValue").firstOrNull { + lhs.type.isSubtypeOf(it.valueParameters[0].type) && rhs.type.isSubtypeOf(it.valueParameters[1].type) + } ?: builtIns.getKonanInternalFunctions("areEqual").single() // or use the general implementation. + + return IrCallImpl(startOffset, endOffset, equals).apply { + putValueArgument(0, lhs) + putValueArgument(1, rhs) + } + } +} diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 6f142d3f269..82ec63facbb 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -528,6 +528,11 @@ task boxing12(type: RunKonanTest) { source = "codegen/boxing/boxing12.kt" } +task boxing13(type: RunKonanTest) { + goldValue = "false\nfalse\ntrue\ntrue\nfalse\nfalse\n" + source = "codegen/boxing/boxing13.kt" +} + task interface0(type: RunKonanTest) { goldValue = "PASSED\n" source = "runtime/basic/interface0.kt" diff --git a/backend.native/tests/codegen/boxing/boxing13.kt b/backend.native/tests/codegen/boxing/boxing13.kt new file mode 100644 index 00000000000..7dd6d62e927 --- /dev/null +++ b/backend.native/tests/codegen/boxing/boxing13.kt @@ -0,0 +1,10 @@ +fun is42(x: Any?) { + println(x == 42) + println(42 == x) +} + +fun main(args: Array) { + is42(16) + is42(42) + is42("42") +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Annotations.kt b/runtime/src/main/kotlin/konan/internal/Annotations.kt index ddf1b96c5c4..8d85bd8047e 100644 --- a/runtime/src/main/kotlin/konan/internal/Annotations.kt +++ b/runtime/src/main/kotlin/konan/internal/Annotations.kt @@ -16,3 +16,9 @@ annotation class ExportForCppRuntime(val name: String = "") // in the absence of IR. annotation class HasBackingField +/** + * This annotation denotes that the element is intrinsic and its usages require special handling in compiler. + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +annotation class Intrinsic \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt new file mode 100644 index 00000000000..a3509e77fc8 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -0,0 +1,18 @@ +package konan.internal + +@Intrinsic external fun areEqualByValue(first: Boolean, second: Boolean): Boolean +@Intrinsic external fun areEqualByValue(first: Char, second: Char): Boolean +@Intrinsic external fun areEqualByValue(first: Byte, second: Byte): Boolean +@Intrinsic external fun areEqualByValue(first: Short, second: Short): Boolean +@Intrinsic external fun areEqualByValue(first: Int, second: Int): Boolean +@Intrinsic external fun areEqualByValue(first: Long, second: Long): Boolean +@Intrinsic external fun areEqualByValue(first: Float, second: Float): Boolean +@Intrinsic external fun areEqualByValue(first: Double, second: Double): Boolean + +// For comparing with `null`: +@Intrinsic external fun areEqualByValue(first: Nothing?, second: Any?): Boolean +@Intrinsic external fun areEqualByValue(first: Any?, second: Nothing?): Boolean + +inline fun areEqual(first: Any?, second: Any?): Boolean { + return if (first == null) second == null else first.equals(second) +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt index 52371c57162..c7511e3e58b 100644 --- a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt +++ b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt @@ -1,17 +1,17 @@ package konan.internal @ExportForCppRuntime -internal fun ThrowNullPointerException() { +internal fun ThrowNullPointerException(): Nothing { throw NullPointerException() } @ExportForCppRuntime -internal fun ThrowArrayIndexOutOfBoundsException() { +internal fun ThrowArrayIndexOutOfBoundsException(): Nothing { throw IndexOutOfBoundsException() } @ExportForCppRuntime -internal fun ThrowClassCastException() { +internal fun ThrowClassCastException(): Nothing { throw ClassCastException() }