From a8d6113308ef09d148afd71ca5851246abda11ed Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 4 Jul 2018 10:10:58 +0300 Subject: [PATCH] Dispatch Any methods called on interface via vtable instead of itable (#1753) --- .../backend/konan/llvm/CodeGenerator.kt | 47 +++++++++++++------ backend.native/tests/build.gradle | 5 ++ .../tests/datagen/rtti/vtable_any.kt | 18 +++++++ 3 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 backend.native/tests/datagen/rtti/vtable_any.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index 68ff5709153..5ce7cf81f5a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -19,16 +19,20 @@ package org.jetbrains.kotlin.backend.konan.llvm import kotlinx.cinterop.* import llvm.* -import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.* +import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.descriptors.isInterface -import org.jetbrains.kotlin.backend.konan.descriptors.stdlibModule -import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.backend.konan.irasdescriptors.* -import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator -import org.jetbrains.kotlin.ir.util.constructors -import org.jetbrains.kotlin.ir.util.defaultType -import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.backend.konan.irasdescriptors.ClassConstructorDescriptor +import org.jetbrains.kotlin.backend.konan.irasdescriptors.ClassDescriptor +import org.jetbrains.kotlin.backend.konan.irasdescriptors.FunctionDescriptor +import org.jetbrains.kotlin.backend.konan.irasdescriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.backend.konan.llvm.objc.* +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.konan.target.* +import org.jetbrains.kotlin.backend.konan.descriptors.resolveFakeOverride internal class CodeGenerator(override val context: Context) : ContextUtils { @@ -618,8 +622,6 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, fun lookupVirtualImpl(receiver: LLVMValueRef, descriptor: FunctionDescriptor): LLVMValueRef { assert(LLVMTypeOf(receiver) == codegen.kObjHeaderPtr) - val owner = descriptor.containingDeclaration as ClassDescriptor - val typeInfoPtr: LLVMValueRef = if (descriptor.getObjCMethodInfo() != null) { call(context.llvm.getObjCKotlinTypeInfo, listOf(receiver)) } else { @@ -629,14 +631,21 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, load(typeInfoPtrPtr) } - assert (typeInfoPtr.type == codegen.kTypeInfoPtr) { LLVMPrintTypeToString(typeInfoPtr.type)!!.toKString() } + assert(typeInfoPtr.type == codegen.kTypeInfoPtr) { LLVMPrintTypeToString(typeInfoPtr.type)!!.toKString() } + + /* + * Resolve owner of the call with special handling of Any methods: + * if toString/eq/hc is invoked on an interface instance, we resolve + * owner as Any and dispatch it via vtable + */ + val anyMethod = (descriptor as SimpleFunctionDescriptor).findOverriddenMethodOfAny() + val owner = (anyMethod ?: 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 = context.getVtableBuilder(owner).vtableIndex(descriptor as SimpleFunctionDescriptor) - + val index = context.getVtableBuilder(owner).vtableIndex(anyMethod ?: descriptor) val vtablePlace = gep(typeInfoPtr, Int32(1).llvm) // typeInfoPtr + 1 val vtable = bitcast(kInt8PtrPtr, vtablePlace) - val slot = gep(vtable, Int32(index).llvm) load(slot) } else { @@ -652,6 +661,16 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return bitcast(functionPtrType, llvmMethod) // Cast method address to the type } + private fun IrSimpleFunction.findOverriddenMethodOfAny(): IrSimpleFunction? { + if (modality == Modality.ABSTRACT) return null + val resolved = resolveFakeOverride() + if ((resolved.parent as ClassDescriptor).isAny()) { + return resolved + } + + return null + } + fun getObjectValue( descriptor: ClassDescriptor, shared: Boolean, diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 7061b979410..cfaa76eba6c 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1478,6 +1478,11 @@ task vtable1(type: RunKonanTest) { source = "datagen/rtti/vtable1.kt" } +task vtable_any(type: RunKonanTest) { + source = "datagen/rtti/vtable_any.kt" + goldValue = "[1]\n[2]\n[3]\n[4]\n" +} + task strdedup1(type: RunKonanTest) { goldValue = "true\ntrue\n" source = "datagen/literals/strdedup1.kt" diff --git a/backend.native/tests/datagen/rtti/vtable_any.kt b/backend.native/tests/datagen/rtti/vtable_any.kt new file mode 100644 index 00000000000..bf8c137ca11 --- /dev/null +++ b/backend.native/tests/datagen/rtti/vtable_any.kt @@ -0,0 +1,18 @@ +package datagen.rtti.vtable_any + +import kotlin.test.* + +@Test +fun runTest() { + // Try to trick devirtualizer + println(anyMethods(arrayListOf("1"))) + println(anyMethods(mapOf("2" to 1).keys)) + println(anyMethods(mapOf("1" to 3).values)) + println(anyMethods(setOf("4"))) +} + +fun anyMethods(iterable: Iterable<*>): String { + assert(iterable.equals(iterable)) + assert(iterable.hashCode() != 0) + return iterable.toString() +}