diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index c0c7455f12d..bd2d9398769 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -3,25 +3,24 @@ package org.jetbrains.kotlin.backend.konan import llvm.LLVMDumpModule import llvm.LLVMModuleRef import org.jetbrains.kotlin.backend.jvm.descriptors.initialize -import org.jetbrains.kotlin.backend.konan.InteropBuiltIns -import org.jetbrains.kotlin.backend.konan.descriptors.deepPrint -import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName +import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.ir.Ir -import org.jetbrains.kotlin.backend.konan.llvm.Llvm -import org.jetbrains.kotlin.backend.konan.llvm.LlvmDeclarations -import org.jetbrains.kotlin.backend.konan.llvm.verifyModule +import org.jetbrains.kotlin.backend.konan.llvm.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ReceiverParameterDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.util.DumpIrTreeVisitor import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver import java.lang.System.out -internal class SpecialDescriptorsFactory { +internal class SpecialDescriptorsFactory(val context: Context) { private val outerThisDescriptors = mutableMapOf() + private val bridgesDescriptors = mutableMapOf() fun getOuterThisFieldDescriptor(innerClassDescriptor: ClassDescriptor): PropertyDescriptor = if (!innerClassDescriptor.isInner) throw AssertionError("Class is not inner: $innerClassDescriptor") @@ -34,13 +33,75 @@ internal class SpecialDescriptorsFactory { false, "this$0".synthesizedName, CallableMemberDescriptor.Kind.SYNTHESIZED, SourceElement.NO_SOURCE, false, false, false, false, false, false).initialize(outerClassDescriptor.defaultType, dispatchReceiverParameter = receiver) } + + fun getBridgeDescriptor(descriptor: FunctionDescriptor): FunctionDescriptor { + return bridgesDescriptors.getOrPut(descriptor) { + SimpleFunctionDescriptorImpl.create( + descriptor.containingDeclaration, + Annotations.EMPTY, + ("" + descriptor.functionName).synthesizedName, + CallableMemberDescriptor.Kind.DECLARATION, + SourceElement.NO_SOURCE).apply { + initializeBridgeDescriptor(this, descriptor) + } + } + } + + private fun initializeBridgeDescriptor(bridgeDescriptor: SimpleFunctionDescriptorImpl, descriptor: FunctionDescriptor) { + val bridgeDirections = descriptor.bridgeDirections + if (bridgeDirections.allNotNeeded()) + throw AssertionError("Function $descriptor is not needed in a bridge") + + val returnType = when (bridgeDirections[0]) { + BridgeDirection.TO_VALUE_TYPE -> descriptor.returnType!! + BridgeDirection.NOT_NEEDED -> descriptor.returnType + BridgeDirection.FROM_VALUE_TYPE -> context.builtIns.anyType + } + + val extensionReceiverType = when (bridgeDirections[1]) { + BridgeDirection.TO_VALUE_TYPE -> descriptor.extensionReceiverParameter!!.type + BridgeDirection.NOT_NEEDED -> descriptor.extensionReceiverParameter?.type + BridgeDirection.FROM_VALUE_TYPE -> context.builtIns.anyType + } + + bridgeDescriptor.initialize( + extensionReceiverType, + descriptor.dispatchReceiverParameter, + descriptor.typeParameters, + descriptor.valueParameters.mapIndexed { index, valueParameterDescriptor -> + when (bridgeDirections[index + 2]) { + BridgeDirection.TO_VALUE_TYPE -> valueParameterDescriptor + BridgeDirection.NOT_NEEDED -> valueParameterDescriptor + BridgeDirection.FROM_VALUE_TYPE -> ValueParameterDescriptorImpl( + valueParameterDescriptor.containingDeclaration, + null, + index, + Annotations.EMPTY, + valueParameterDescriptor.name, + context.builtIns.anyType, + valueParameterDescriptor.declaresDefaultValue(), + valueParameterDescriptor.isCrossinline, + valueParameterDescriptor.isNoinline, + valueParameterDescriptor.varargElementType, + SourceElement.NO_SOURCE) + } + }, + returnType, + descriptor.modality, + Visibilities.PRIVATE) + } } internal final class Context(val config: KonanConfig) : KonanBackendContext() { var moduleDescriptor: ModuleDescriptor? = null - val specialDescriptorsFactory = SpecialDescriptorsFactory() + val specialDescriptorsFactory = SpecialDescriptorsFactory(this) + private val vtableBuilders = mutableMapOf() + + fun getVtableBuilder(classDescriptor: ClassDescriptor) = vtableBuilders.getOrPut(classDescriptor) { + ClassVtablesBuilder(classDescriptor, this) + } // TODO: make lateinit? var irModule: IrModuleFragment? = null 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 4e6a17c3ea0..0b1fab7cd8a 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 @@ -55,6 +55,9 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.LOWER_CALLABLES) { CallableReferenceLowering(context).runOnFilePostfix(irFile) } + phaser.phase(KonanPhase.BRIDGES_BUILDING) { + BridgesBuilding(context).lower(irFile) + } phaser.phase(KonanPhase.AUTOBOX) { Autoboxing(context).lower(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 45d700dd42b..fc226819486 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 @@ -24,6 +24,7 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_INNER_CLASSES("Inner classes lowering"), /* ... ... */ LOWER_STRING_CONCAT("String concatenation lowering"), /* ... ... */ LOWER_INITIALIZERS("Initializers lowering"), + /* ... ... */ BRIDGES_BUILDING("Bridges building"), /* ... */ BITCODE("LLVM BitCode Generation"), /* ... ... */ RTTI("RTTI Generation"), /* ... ... */ CODEGEN("Code Generation"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt index 077b39fa032..b460628a8a3 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt @@ -1,7 +1,7 @@ package org.jetbrains.kotlin.backend.konan -import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.types.KotlinType /** @@ -69,4 +69,6 @@ tailrec fun KotlinType.notNullableIsRepresentedAs(valueType: ValueType): Boolean // See comment in [isRepresentedAs]. val firstSupertype = this.constructor.supertypes.firstOrNull() ?: return false return firstSupertype.notNullableIsRepresentedAs(valueType) -} \ No newline at end of file +} + +internal fun KotlinType.isValueType() = ValueType.values().any { this.isRepresentedAs(it) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt new file mode 100644 index 00000000000..7c0c581b3be --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt @@ -0,0 +1,93 @@ +package org.jetbrains.kotlin.backend.konan.descriptors + +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.llvm.functionName +import org.jetbrains.kotlin.backend.konan.llvm.localHash +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.OverridingUtil +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny + +internal class OverriddenFunctionDescriptor(val descriptor: FunctionDescriptor, overriddenDescriptor: FunctionDescriptor) { + val overriddenDescriptor = overriddenDescriptor.original + + val needBridge: Boolean + get() { + if (descriptor.modality == Modality.ABSTRACT) return false + return descriptor.needBridgeTo(overriddenDescriptor) + || descriptor.target.needBridgeTo(overriddenDescriptor) + } + + fun trivialOverride() = descriptor.original == overriddenDescriptor + + override fun toString(): String { + return "(descriptor=$descriptor, overriddenDescriptor=$overriddenDescriptor)" + } +} + +internal class ClassVtablesBuilder(val classDescriptor: ClassDescriptor, val context: Context) { + val vtableEntries: List by lazy { + + assert(!classDescriptor.isInterface) + + val superVtableEntries = if (KotlinBuiltIns.isSpecialClassWithNoSupertypes(classDescriptor)) { + emptyList() + } else { + context.getVtableBuilder(classDescriptor.getSuperClassOrAny()).vtableEntries + } + + val methods = classDescriptor.contributedMethods + val newVtableSlots = mutableListOf() + + val inheritedVtableSlots = superVtableEntries.map { superMethod -> + val overridingMethod = methods.singleOrNull { OverridingUtil.overrides(it, superMethod.descriptor) } + if (overridingMethod == null) { + superMethod + } else { + if (!superMethod.trivialOverride()) + newVtableSlots.add(OverriddenFunctionDescriptor(overridingMethod, superMethod.descriptor)) + newVtableSlots.add(OverriddenFunctionDescriptor(overridingMethod, overridingMethod)) + OverriddenFunctionDescriptor(overridingMethod, superMethod.overriddenDescriptor) + } + } + + methods.filterNot { method -> inheritedVtableSlots.any { it.descriptor == method } } // Find newly defined methods. + .mapTo(newVtableSlots) { OverriddenFunctionDescriptor(it, it) } + + val list = inheritedVtableSlots + newVtableSlots.filter { it.descriptor.isOverridable }.sortedBy { + it.overriddenDescriptor.functionName.localHash.value + } + list + } + + fun vtableIndex(function: FunctionDescriptor): Int { + val target = function.target + val index = vtableEntries.indexOfFirst { it.overriddenDescriptor == target } + if (index < 0) throw Error(function.toString() + " not in vtable of " + this.toString()) + return index + } + + private val ClassDescriptor.contributedMethodsWithOverridden: List + get() { + return contributedMethods.flatMap { method -> + method.allOverriddenDescriptors.map { OverriddenFunctionDescriptor(method, it) } + }.distinctBy { + Triple(it.overriddenDescriptor.functionName, it.descriptor, it.needBridge) + }.sortedBy { + it.overriddenDescriptor.functionName.localHash.value + } + } + + val methodTableEntries: List by lazy { + assert(!classDescriptor.isAbstract()) + + classDescriptor.contributedMethodsWithOverridden.filter { + // We check that either method is open, or one of declarations it overrides + // is open. + it.overriddenDescriptor.isOverridable || DescriptorUtils.getAllOverriddenDeclarations(it.overriddenDescriptor).any { it.isOverridable } + } + // TODO: probably method table should contain all accessible methods to improve binary compatibility + } + +} \ No newline at end of file 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 0e7a0261d1d..46142597e43 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 @@ -3,27 +3,23 @@ package org.jetbrains.kotlin.backend.konan.descriptors import org.jetbrains.kotlin.backend.konan.KonanBuiltIns import org.jetbrains.kotlin.backend.konan.ValueType import org.jetbrains.kotlin.backend.konan.isRepresentedAs +import org.jetbrains.kotlin.backend.konan.isValueType import org.jetbrains.kotlin.backend.konan.llvm.functionName -import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.backend.konan.llvm.localHash import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor import org.jetbrains.kotlin.builtins.getFunctionalClassKind import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.annotations.Annotations -import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl -import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl -import org.jetbrains.kotlin.descriptors.impl.PropertySetterDescriptorImpl import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.OverridingUtil -import org.jetbrains.kotlin.resolve.descriptorUtil.* +import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.TypeSubstitution -import org.jetbrains.kotlin.types.TypeSubstitutor -import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.util.OperatorNameConventions @@ -46,14 +42,14 @@ internal val ClassDescriptor.implementedInterfaces: List * * TODO: this method is actually a part of resolve and probably duplicates another one */ -internal fun FunctionDescriptor.resolveFakeOverride(): FunctionDescriptor { +internal fun T.resolveFakeOverride(): T { if (this.kind.isReal) { return this } else { val overridden = OverridingUtil.getOverriddenDeclarations(this) val filtered = OverridingUtil.filterOutOverridden(overridden) // TODO: is it correct to take first? - return filtered.first { it.modality != Modality.ABSTRACT } as FunctionDescriptor + return filtered.first { it.modality != Modality.ABSTRACT } as T } } @@ -157,7 +153,7 @@ internal val KotlinType.isKFunctionType: Boolean internal val FunctionDescriptor.isFunctionInvoke: Boolean get() { val dispatchReceiver = dispatchReceiverParameter ?: return false - assert (!dispatchReceiver.type.isKFunctionType) + assert(!dispatchReceiver.type.isKFunctionType) return dispatchReceiver.type.isFunctionType && this.isOperator && this.name == OperatorNameConventions.INVOKE @@ -165,8 +161,19 @@ internal val FunctionDescriptor.isFunctionInvoke: Boolean internal fun ClassDescriptor.isUnit() = this.defaultType.isUnit() -internal val ClassDescriptor.contributedMethods: List +internal val T.allOverriddenDescriptors: List get() { + val result = mutableListOf() + fun traverse(descriptor: T) { + result.add(descriptor) + descriptor.overriddenDescriptors.forEach { traverse(it as T) } + } + traverse(this) + return result + } + +internal val ClassDescriptor.contributedMethods: List + get () { val contributedDescriptors = unsubstitutedMemberScope.getContributedDescriptors() // (includes declarations from supers) @@ -177,49 +184,85 @@ internal val ClassDescriptor.contributedMethods: List val setters = properties.mapNotNull { it.setter } val allMethods = (functions + getters + setters).sortedBy { - // TODO: use local hash instead, but it needs major refactoring. - it.functionName.hashCode() + it.functionName.localHash.value } + return allMethods } fun ClassDescriptor.isAbstract() = this.modality == Modality.SEALED || this.modality == Modality.ABSTRACT || this.kind == ClassKind.ENUM_CLASS -// TODO: optimize -val ClassDescriptor.vtableEntries: List - get() { - assert(!this.isInterface) - - val superVtableEntries = if (KotlinBuiltIns.isSpecialClassWithNoSupertypes(this)) { - emptyList() - } else { - this.getSuperClassOrAny().vtableEntries - } - - val methods = this.contributedMethods - - val inheritedVtableSlots = superVtableEntries.map { superMethod -> - methods.singleOrNull { OverridingUtil.overrides(it, superMethod) } ?: superMethod - } - - return inheritedVtableSlots + (methods - inheritedVtableSlots).filter { it.isOverridable } +internal fun FunctionDescriptor.hasValueTypeAt(index: Int): Boolean { + when (index) { + 0 -> return returnType.let { it != null && it.isValueType() } + 1 -> return extensionReceiverParameter.let { it != null && it.type.isValueType() } + else -> return this.valueParameters[index - 2].type.isValueType() } - -fun ClassDescriptor.vtableIndex(function: FunctionDescriptor): Int { - this.vtableEntries.forEachIndexed { index, functionDescriptor -> - if (functionDescriptor == function.original) return index - } - throw Error(function.toString() + " not in vtable of " + this.toString()) } -val ClassDescriptor.methodTableEntries: List +internal fun FunctionDescriptor.hasReferenceAt(index: Int): Boolean { + when (index) { + 0 -> return returnType.let { it != null && !it.isValueType() } + 1 -> return extensionReceiverParameter.let { it != null && !it.type.isValueType() } + else -> return !this.valueParameters[index - 2].type.isValueType() + } +} + +private fun FunctionDescriptor.overridesFunWithReferenceAt(index: Int) + = allOverriddenDescriptors.any { it.original.hasReferenceAt(index) } + +private fun FunctionDescriptor.overridesFunWithValueTypeAt(index: Int) + = allOverriddenDescriptors.any { it.original.hasValueTypeAt(index) } + +private fun FunctionDescriptor.needBridgeToAt(target: FunctionDescriptor, index: Int) + = hasValueTypeAt(index) xor target.hasValueTypeAt(index) + +internal fun FunctionDescriptor.needBridgeTo(target: FunctionDescriptor) + = (0..this.valueParameters.size + 1).any { needBridgeToAt(target, it) } + +internal val FunctionDescriptor.target: FunctionDescriptor + get() = (if (modality == Modality.ABSTRACT) this else resolveFakeOverride()).original + +internal enum class BridgeDirection { + NOT_NEEDED, + FROM_VALUE_TYPE, + TO_VALUE_TYPE +} + +private fun FunctionDescriptor.bridgeDirectionAt(index: Int) : BridgeDirection { + if (kind.isReal) { + if (hasValueTypeAt(index) && overridesFunWithReferenceAt(index)) + return BridgeDirection.FROM_VALUE_TYPE + return BridgeDirection.NOT_NEEDED + } + + val target = this.target + return when { + hasValueTypeAt(index) && target.hasValueTypeAt(index) && overridesFunWithReferenceAt(index) -> BridgeDirection.FROM_VALUE_TYPE + hasValueTypeAt(index) && target.hasReferenceAt(index) && overridesFunWithValueTypeAt(index) -> BridgeDirection.TO_VALUE_TYPE + else -> BridgeDirection.NOT_NEEDED + } +} + +private fun bridgesEqual(first: Array, second: Array) + = first.indices.none { first[it] != second[it] } + +internal fun Array.allNotNeeded() = this.all { it == BridgeDirection.NOT_NEEDED } + +internal val FunctionDescriptor.bridgeDirections: Array get() { - assert(!this.isAbstract()) - return this.contributedMethods.filter { - // We check that either method is open, or one of declarations it overrides - // is open. - it.isOverridable || DescriptorUtils.getAllOverriddenDeclarations(it).any { it.isOverridable } + val ourDirections = Array(this.valueParameters.size + 2, { BridgeDirection.NOT_NEEDED }) + if (modality == Modality.ABSTRACT) + return ourDirections + for (index in ourDirections.indices) + ourDirections[index] = this.bridgeDirectionAt(index) + + if (!kind.isReal && bridgesEqual(ourDirections, this.target.bridgeDirections)) { + // Bridge is inherited from supers + for (index in ourDirections.indices) + ourDirections[index] = BridgeDirection.NOT_NEEDED } - // TODO: probably method table should contain all accessible methods to improve binary compatibility - } \ No newline at end of file + + return ourDirections + } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index 40cc4fd67e4..690a885f1fa 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -3,6 +3,8 @@ package org.jetbrains.kotlin.backend.konan.llvm import llvm.LLVMTypeRef import org.jetbrains.kotlin.backend.konan.descriptors.allValueParameters import org.jetbrains.kotlin.backend.konan.descriptors.isUnit +import org.jetbrains.kotlin.backend.konan.isValueType +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.name.FqName @@ -81,15 +83,20 @@ private fun typeToHashString(type: KotlinType): String { private val FunctionDescriptor.signature: String get() { val extensionReceiverPart = this.extensionReceiverParameter?.let { "${it.type}." } ?: "" - val actualDescriptor = this.findOriginalTopMostOverriddenDescriptors().firstOrNull() ?: this - val argsPart = actualDescriptor.valueParameters.map { + val argsPart = this.valueParameters.map { typeToHashString(it.type) }.joinToString(";") - // TODO: add return type - // (it is not simple because return type can be changed when overriding) - return "$extensionReceiverPart($argsPart)" + // Just distinguish value types and references - it's needed for calling virtual methods through bridges. + val returnTypePart = + when { + returnType.let { it != null && it.isValueType() } -> "ValueType" + returnType.let { it != null && !KotlinBuiltIns.isUnitOrNullableUnit(it) } -> "Reference" + else -> "" + } + + return "$extensionReceiverPart($argsPart)$returnTypePart" } // TODO: rename to indicate that it has signature included diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 4877f111965..34915d9dc14 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -151,16 +151,6 @@ internal interface ContextUtils : RuntimeAware { return base64Encode(globalHashBytes) } - /** - * Converts this string to the sequence of bytes to be used for hashing/storing to binary/etc. - * - * TODO: share this implementation - */ - private fun stringAsBytes(str: String) = str.toByteArray(Charsets.UTF_8) - - val String.localHash: LocalHash - get() = LocalHash(localHash(stringAsBytes(this))) - val String.globalHash: ConstValue get() = memScoped { val hashBytes = this@globalHash.globalHashBytes @@ -170,13 +160,22 @@ internal interface ContextUtils : RuntimeAware { val FqName.globalHash: ConstValue get() = this.toString().globalHash - val Name.localHash: LocalHash - get() = this.toString().localHash - - val FqName.localHash: LocalHash - get() = this.toString().localHash } +/** + * Converts this string to the sequence of bytes to be used for hashing/storing to binary/etc. + */ +internal fun stringAsBytes(str: String) = str.toByteArray(Charsets.UTF_8) + +internal val String.localHash: LocalHash + get() = LocalHash(localHash(stringAsBytes(this))) + +internal val Name.localHash: LocalHash + get() = this.toString().localHash + +internal val FqName.localHash: LocalHash + get() = this.toString().localHash + internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { private fun importFunction(name: String, otherModule: LLVMModuleRef): LLVMValueRef { 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 d048f362e9a..1f6a6f4ee6a 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 @@ -1654,7 +1654,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid "konan.internal.areEqualByValue" -> { val arg0 = args[0] val arg1 = args[1] - assert (arg0.type == arg1.type) + assert (arg0.type == arg1.type, { "Types are different: '${llvmtype2string(arg0.type)}' and '${llvmtype2string(arg1.type)}'" }) return when (LLVMGetTypeKind(arg0.type)) { LLVMTypeKind.LLVMFloatTypeKind, LLVMTypeKind.LLVMDoubleTypeKind -> @@ -1749,7 +1749,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid fun callDirect(descriptor: FunctionDescriptor, args: List, resultLifetime: Lifetime): LLVMValueRef { - val realDescriptor = descriptor.resolveFakeOverride().original + val realDescriptor = descriptor.target val llvmFunction = codegen.functionLlvmValue(realDescriptor) return call(descriptor, llvmFunction, args, resultLifetime) } @@ -1766,7 +1766,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid val owner = descriptor.containingDeclaration as ClassDescriptor val llvmMethod = if (!owner.isInterface) { // If this is a virtual method of the class - we can call via vtable. - val index = owner.vtableIndex(descriptor) + val index = context.getVtableBuilder(owner).vtableIndex(descriptor) val vtablePlace = codegen.gep(typeInfoPtr, Int32(1).llvm) // typeInfoPtr + 1 val vtable = codegen.bitcast(kInt8PtrPtr, vtablePlace) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt index 6fe15b54f89..b8244176c7a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt @@ -232,7 +232,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) : val typeInfoWithVtableType = structType( runtime.typeInfoType, - LLVMArrayType(int8TypePtr, descriptor.vtableEntries.size)!! + LLVMArrayType(int8TypePtr, context.getVtableBuilder(descriptor).vtableEntries.size)!! ) typeInfoGlobal = staticData.createGlobal(typeInfoWithVtableType, typeInfoGlobalName, isExported = false) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 4770fd86994..9390564aa71 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -121,15 +121,21 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { val fieldsPtr = staticData.placeGlobalConstArray("kfields:$className", runtime.fieldTableRecordType, fields) - val methods = if (!classDesc.isAbstract()) { - classDesc.methodTableEntries.map { - val nameSignature = it.functionName.localHash + val methods = if (classDesc.isAbstract()) { + emptyList() + } else { + val functionNames = mutableMapOf() + context.getVtableBuilder(classDesc).methodTableEntries.map { + val functionName = it.overriddenDescriptor.functionName + val nameSignature = functionName.localHash + val previous = functionNames.putIfAbsent(nameSignature.value, it) + if (previous != null) + throw AssertionError("Duplicate method table entry: functionName = '$functionName', hash = '${nameSignature.value}', entry1 = $previous, entry2 = $it") + // TODO: compile-time resolution limits binary compatibility - val methodEntryPoint = it.resolveFakeOverride().original.entryPointAddress + val methodEntryPoint = it.implementation.entryPointAddress MethodTableRecord(nameSignature, methodEntryPoint) }.sortedBy { it.nameSignature.value } - } else { - emptyList() } val methodsPtr = staticData.placeGlobalConstArray("kmethods:$className", @@ -148,7 +154,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { typeInfo } else { // TODO: compile-time resolution limits binary compatibility - val vtableEntries = classDesc.vtableEntries.map { it.resolveFakeOverride().original.entryPointAddress } + val vtableEntries = context.getVtableBuilder(classDesc).vtableEntries.map { it.implementation.entryPointAddress } val vtable = ConstArray(int8TypePtr, vtableEntries) Struct(typeInfo, vtable) } @@ -159,4 +165,11 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { exportTypeInfoIfRequired(classDesc, classDesc.llvmTypeInfoPtr) } + internal val OverriddenFunctionDescriptor.implementation: FunctionDescriptor + get() { + val target = descriptor.target + if (!needBridge) return target + val bridgeOwner = if (descriptor.bridgeDirections.allNotNeeded()) target else descriptor + return context.specialDescriptorsFactory.getBridgeDescriptor(bridgeOwner) + } } 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 4844d205380..5f29fe36887 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 @@ -4,15 +4,12 @@ import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.ValueType -import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalClass -import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions +import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.ir.isNullConst import org.jetbrains.kotlin.backend.konan.notNullableIsRepresentedAs import org.jetbrains.kotlin.backend.konan.isRepresentedAs import org.jetbrains.kotlin.backend.konan.util.atMostOne -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor -import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl @@ -94,6 +91,38 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr } } + override fun visitCall(expression: IrCall): IrExpression { + val irCall = super.visitCall(expression) as IrCall + + val descriptor = irCall.descriptor as? FunctionDescriptor ?: return irCall + if (descriptor.modality == Modality.ABSTRACT || (irCall.superQualifier == null && descriptor.isOverridable)) + return irCall // A virtual call. box/unbox will be in the corresponding bridge. + + val target = descriptor.target + + if (!descriptor.original.needBridgeTo(target)) return irCall + + val overriddenCall = IrCallImpl(irCall.startOffset, irCall.endOffset, + target, remapTypeArguments(irCall, target)).apply { + dispatchReceiver = irCall.dispatchReceiver + extensionReceiver = irCall.extensionReceiver + mapValueParameters { irCall.getValueArgument(it)!! } + } + + return super.visitCall(overriddenCall) + } + + private fun remapTypeArguments(oldExpression: IrMemberAccessExpression, newCallee: CallableDescriptor): Map? { + val oldCallee = oldExpression.descriptor + + return if (oldCallee.typeParameters.isEmpty()) + null + else oldCallee.typeParameters.associateBy( + { newCallee.typeParameters[it.index] }, + { oldExpression.getTypeArgument(it)!! } + ) + } + /** * @return the [ValueType] given type represented in generated code as, * or `null` if represented as object reference. diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BridgesBuilding.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BridgesBuilding.kt new file mode 100644 index 00000000000..6515a234a98 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BridgesBuilding.kt @@ -0,0 +1,79 @@ +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.descriptors.* +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.types.typeUtil.isUnit + +internal class BridgesBuilding(val context: Context) : FileLoweringPass { + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(object : IrElementTransformerVoid() { + + override fun visitClass(declaration: IrClass): IrStatement { + val irClass = super.visitClass(declaration) as IrClass + + val functions = mutableSetOf() + irClass.declarations.forEach { + when (it) { + is IrFunction -> functions.add(it.descriptor) + is IrProperty -> { + functions.add(it.getter?.descriptor) + functions.add(it.setter?.descriptor) + } + } + } + + irClass.descriptor.contributedMethods.forEach { functions.add(it) } + + functions.forEach { buildBridge(it, irClass) } + + return irClass + } + }) + + } + + private object DECLARATION_ORIGIN_BRIDGE_METHOD : + IrDeclarationOriginImpl("BRIDGE_METHOD") + + private fun buildBridge(descriptor: FunctionDescriptor?, irClass: IrClass) { + if (descriptor == null) return + if (descriptor.bridgeDirections.allNotNeeded()) + return + + val bridgeDescriptor = context.specialDescriptorsFactory.getBridgeDescriptor(descriptor) + + val delegatingCall = IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, descriptor.target, + superQualifier = descriptor.target.containingDeclaration as ClassDescriptor /* Call non-virtually */).apply { + val dispatchReceiverParameter = bridgeDescriptor.dispatchReceiverParameter + if (dispatchReceiverParameter != null) + dispatchReceiver = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, dispatchReceiverParameter) + val extensionReceiverParameter = bridgeDescriptor.extensionReceiverParameter + if (extensionReceiverParameter != null) + extensionReceiver = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, extensionReceiverParameter) + bridgeDescriptor.valueParameters.forEach { + this.putValueArgument(it.index, IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, it)) + } + } + + val bridgeBody = if (bridgeDescriptor.returnType.let { it != null && !KotlinBuiltIns.isUnitOrNullableUnit(it) }) + IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, bridgeDescriptor, delegatingCall) + else + delegatingCall + irClass.declarations.add(IrFunctionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, DECLARATION_ORIGIN_BRIDGE_METHOD, + bridgeDescriptor, IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(bridgeBody)))) + } +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InitializersLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InitializersLowering.kt index 246008e19b4..c2330945e95 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InitializersLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InitializersLowering.kt @@ -4,7 +4,6 @@ import org.jetbrains.kotlin.backend.common.ClassLoweringPass import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.declarations.impl.IrFieldImpl import org.jetbrains.kotlin.ir.expressions.IrBlockBody import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall import org.jetbrains.kotlin.ir.expressions.IrInstanceInitializerCall diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 95c290f0c54..8738a95f5e3 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -441,6 +441,76 @@ task initializers_correctOrder2(type: RunKonanTest) { source = "codegen/initializers/correctOrder2.kt" } +task bridges_test0(type: RunKonanTest) { + goldValue = "42\n42\n" + source = "codegen/bridges/test0.kt" +} + +task bridges_test1(type: RunKonanTest) { + goldValue = "1042\n" + source = "codegen/bridges/test1.kt" +} + +task bridges_test2(type: RunKonanTest) { + goldValue = "42\n42\n" + source = "codegen/bridges/test2.kt" +} + +task bridges_test3(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/bridges/test3.kt" +} + +task bridges_test4(type: RunKonanTest) { + goldValue = "42\n42\n" + source = "codegen/bridges/test4.kt" +} + +task bridges_test5(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/bridges/test5.kt" +} + +task bridges_test6(type: RunKonanTest) { + goldValue = "42\n42\n42\n42\n42\n" + source = "codegen/bridges/test6.kt" +} + +task bridges_test7(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/bridges/test7.kt" +} + +task bridges_test8(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/bridges/test8.kt" +} + +task bridges_test9(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/bridges/test9.kt" +} + +task bridges_test10(type: RunKonanTest) { + goldValue = "42\n42\n" + source = "codegen/bridges/test10.kt" +} + +task bridges_test11(type: RunKonanTest) { + goldValue = "42\n42\n42\n" + source = "codegen/bridges/test11.kt" +} + +task bridges_test12(type: RunKonanTest) { + goldValue = "B: 42\nC: 42\n" + source = "codegen/bridges/test12.kt" +} + +task bridges_test13(type: RunKonanTest) { + goldValue = "42\n42\n" + source = "codegen/bridges/test13.kt" +} + task array0(type: RunKonanTest) { goldValue = "5\n6\n7\n8\n9\n10\n11\n12\n13\n" source = "runtime/collections/array0.kt" diff --git a/backend.native/tests/codegen/bridges/test0.kt b/backend.native/tests/codegen/bridges/test0.kt new file mode 100644 index 00000000000..c228fab2f0a --- /dev/null +++ b/backend.native/tests/codegen/bridges/test0.kt @@ -0,0 +1,15 @@ +// vtable call +open class A { + open fun foo(): Any = "A" +} + +open class C : A() { + override fun foo(): Int = 42 +} + +fun main(args: Array) { + val c = C() + val a: A = c + println(c.foo().toString()) + println(a.foo().toString()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test1.kt b/backend.native/tests/codegen/bridges/test1.kt new file mode 100644 index 00000000000..ac1c87e9d58 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test1.kt @@ -0,0 +1,18 @@ +// interface call, bridge overridden +interface Z1 { + fun foo(x: Int) : Any +} + +open class A : Z1 { + override fun foo(x: Int) : Int = 5 +} + +open class B : A() { + override fun foo(x: Int) : Int = 42 +} + +fun main(args: Array) { + val z1: A = B() + println((z1.foo(1) + 1000).toString()) +} + diff --git a/backend.native/tests/codegen/bridges/test10.kt b/backend.native/tests/codegen/bridges/test10.kt new file mode 100644 index 00000000000..b8eec342a19 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test10.kt @@ -0,0 +1,28 @@ +open class A { + open fun foo(x: T) { + println(x.toString()) + } +} + +interface I { + fun foo(x: Int) +} + +class B : A(), I { + var z: Int = 5 + override fun foo(x: Int) { + z = x + } +} + +fun zzz(a: A) { + a.foo(42) +} + +fun main(args: Array) { + val b = B() + zzz(b) + val a = A() + zzz(a) + println(b.z) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test11.kt b/backend.native/tests/codegen/bridges/test11.kt new file mode 100644 index 00000000000..2ca66389333 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test11.kt @@ -0,0 +1,20 @@ +interface I { + fun foo(x: Int) +} + +abstract class A { + abstract fun foo(x: T) +} + +class B : A(), I { + override fun foo(x: Int) = println(x) +} + +fun main(args: Array) { + val b = B() + val a: A = b + val c: I = b + b.foo(42) + a.foo(42) + c.foo(42) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test12.kt b/backend.native/tests/codegen/bridges/test12.kt new file mode 100644 index 00000000000..5bb43bdb29c --- /dev/null +++ b/backend.native/tests/codegen/bridges/test12.kt @@ -0,0 +1,24 @@ +abstract class A { + abstract fun foo(x: T) +} + +class B : A() { + override fun foo(x: Int) { + println("B: $x") + } +} + +class C : A() { + override fun foo(x: Any) { + println("C: $x") + } +} + +fun foo(arg: A) { + arg.foo(42) +} + +fun main(args: Array) { + foo(B()) + foo(C()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test13.kt b/backend.native/tests/codegen/bridges/test13.kt new file mode 100644 index 00000000000..76bc2b1af14 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test13.kt @@ -0,0 +1,22 @@ +open class A { + open fun T.foo() { + println(this.toString()) + } + + fun bar(x: T) { + x.foo() + } +} + +open class B: A() { + override fun Int.foo() { + println(this) + } +} + +fun main(args : Array) { + val b = B() + val a = A() + b.bar(42) + a.bar(42) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test2.kt b/backend.native/tests/codegen/bridges/test2.kt new file mode 100644 index 00000000000..4d505c1f86c --- /dev/null +++ b/backend.native/tests/codegen/bridges/test2.kt @@ -0,0 +1,17 @@ +// vtable call, bridge inherited +open class A { + open fun foo(): Any = "A" +} + +open class C : A() { + override fun foo(): Int = 42 +} + +open class D: C() + +fun main(args: Array) { + val c = D() + val a: A = c + println(c.foo().toString()) + println(a.foo().toString()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test3.kt b/backend.native/tests/codegen/bridges/test3.kt new file mode 100644 index 00000000000..94623b31de5 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test3.kt @@ -0,0 +1,30 @@ +// non-generic interface, generic impl, non-virtual call + interface call +open class A { + var size: T = 56 as T +} + +interface C { + var size: Int +} + +class B : C, A() + +fun box(): String { + val b = B() + if (b.size != 56) return "fail 1" + + b.size = 55 + if (b.size != 55) return "fail 2" + + val c: C = b + if (c.size != 55) return "fail 3" + + c.size = 57 + if (c.size != 57) return "fail 4" + + return "OK" +} + +fun main(args: Array) { + println(box()) +} diff --git a/backend.native/tests/codegen/bridges/test4.kt b/backend.native/tests/codegen/bridges/test4.kt new file mode 100644 index 00000000000..4f79d18deb0 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test4.kt @@ -0,0 +1,21 @@ +// vtable call + interface call +interface Z { + fun foo(): Any +} + +interface Y { + fun foo(): Int +} + +open class A { + fun foo(): Int = 42 +} + +open class B: A(), Z, Y + +fun main(args: Array) { + val z: Z = B() + val y: Y = z as Y + println(z.foo().toString()) + println(y.foo().toString()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test5.kt b/backend.native/tests/codegen/bridges/test5.kt new file mode 100644 index 00000000000..a20acc36b84 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test5.kt @@ -0,0 +1,30 @@ +// non-generic interface, generic impl, vtable call + interface call +open class A { + open var size: T = 56 as T +} + +interface C { + var size: Int +} + +open class B : C, A() + +fun box(): String { + val b = B() + if (b.size != 56) return "fail 1" + + b.size = 55 + if (b.size != 55) return "fail 2" + + val c: C = b + if (c.size != 55) return "fail 3" + + c.size = 57 + if (c.size != 57) return "fail 4" + + return "OK" +} + +fun main(args: Array) { + println(box()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test6.kt b/backend.native/tests/codegen/bridges/test6.kt new file mode 100644 index 00000000000..59e1fef952d --- /dev/null +++ b/backend.native/tests/codegen/bridges/test6.kt @@ -0,0 +1,31 @@ +// vtable call + interface call +interface Z { + fun foo(): Any +} + +interface Y { + fun foo(): Int +} + +open class A { + open fun foo(): Any = "A" +} + +open class C : A() { + override fun foo(): Int = 42 +} + +open class D: C(), Y, Z + +fun main(args: Array) { + val d = D() + val y: Y = d + val z: Z = d + val c: C = d + val a: A = d + println(d.foo().toString()) + println(y.foo().toString()) + println(z.foo().toString()) + println(c.foo().toString()) + println(a.foo().toString()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test7.kt b/backend.native/tests/codegen/bridges/test7.kt new file mode 100644 index 00000000000..a6cea1eb0aa --- /dev/null +++ b/backend.native/tests/codegen/bridges/test7.kt @@ -0,0 +1,30 @@ +// generic interface, non-generic impl, vtable call + interface call +open class A { + open var size: Int = 56 +} + +interface C { + var size: T +} + +open class B : C, A() + +fun box(): String { + val b = B() + if (b.size != 56) return "fail 1" + + b.size = 55 + if (b.size != 55) return "fail 2" + + val c: C = b + if (c.size != 55) return "fail 3" + + c.size = 57 + if (c.size != 57) return "fail 4" + + return "OK" +} + +fun main(args: Array) { + println(box()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test8.kt b/backend.native/tests/codegen/bridges/test8.kt new file mode 100644 index 00000000000..993febf2dae --- /dev/null +++ b/backend.native/tests/codegen/bridges/test8.kt @@ -0,0 +1,30 @@ +// generic interface, non-generic impl, non-virtual call + interface call +open class A { + var size: Int = 56 +} + +interface C { + var size: T +} + +class B : C, A() + +fun box(): String { + val b = B() + if (b.size != 56) return "fail 1" + + b.size = 55 + if (b.size != 55) return "fail 2" + + val c: C = b + if (c.size != 55) return "fail 3" + + c.size = 57 + if (c.size != 57) return "fail 4" + + return "OK" +} + +fun main(args: Array) { + println(box()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/bridges/test9.kt b/backend.native/tests/codegen/bridges/test9.kt new file mode 100644 index 00000000000..e50453174c6 --- /dev/null +++ b/backend.native/tests/codegen/bridges/test9.kt @@ -0,0 +1,27 @@ +// abstract class vtable call +abstract class A { + abstract fun foo(): String +} + +abstract class B : A() + +class Z : B() { + override fun foo() = "Z" +} + + +fun box(): String { + val z = Z() + val b: B = z + val a: A = z + return when { + z.foo() != "Z" -> "Fail #1" + b.foo() != "Z" -> "Fail #2" + a.foo() != "Z" -> "Fail #3" + else -> "OK" + } +} + +fun main(args: Array) { + println(box()) +} \ No newline at end of file