From 24219c86b3d2ee37e847d83a043436ea6e57a23b Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Mon, 16 Jan 2017 15:25:36 +0300 Subject: [PATCH] Implement virtual invoke via vtables. (#181) Small refactoring to share vtable computation code. --- .../konan/descriptors/DescriptorUtils.kt | 55 +++++++++++++++++++ .../kotlin/backend/konan/llvm/IrToBitcode.kt | 29 +++++++--- .../kotlin/backend/konan/llvm/NameUtils.kt | 10 ++-- .../backend/konan/llvm/RTTIGenerator.kt | 48 ++-------------- runtime/src/main/cpp/Common.h | 2 + runtime/src/main/cpp/TypeInfo.cpp | 2 +- runtime/src/main/cpp/TypeInfo.h | 11 +++- runtime/src/main/cpp/Types.h | 5 +- 8 files changed, 102 insertions(+), 60 deletions(-) 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 b6c5dd58f24..968d34cc5e5 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 @@ -1,6 +1,8 @@ package org.jetbrains.kotlin.backend.konan.descriptors import org.jetbrains.kotlin.backend.konan.KonanBuiltIns +import org.jetbrains.kotlin.backend.konan.llvm.functionName +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation @@ -149,3 +151,56 @@ internal val FunctionDescriptor.isFunctionInvoke: Boolean } internal fun ClassDescriptor.isUnit() = this.defaultType.isUnit() + +internal val ClassDescriptor.сontributedMethods: List + get() { + val contributedDescriptors = unsubstitutedMemberScope.getContributedDescriptors() + // (includes declarations from supers) + + val functions = contributedDescriptors.filterIsInstance() + + val properties = contributedDescriptors.filterIsInstance() + val getters = properties.mapNotNull { it.getter } + 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() + } + return allMethods + } + +// TODO: optimize +val ClassDescriptor.vtableEntries: List + get() { + assert(!this.isInterface) + + val superVtableEntries = if (KotlinBuiltIns.isSpecialClassWithNoSupertypes(this)) { + emptyList() + } else { + this.getSuperClassOrAny().vtableEntries + } + + val methods = this.сontributedMethods + + val inheritedVtableSlots = superVtableEntries.map { superMethod -> + methods.single { OverridingUtil.overrides(it, superMethod) } + } + + return inheritedVtableSlots + (methods - inheritedVtableSlots).filter { it.isOverridable } + } + +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 + get() { + assert (this.modality != Modality.ABSTRACT) + + return this.сontributedMethods.filter { it.isOverridableOrOverrides } + // 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/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 1ae4389b65d..4514317f07d 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 @@ -1844,14 +1844,29 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// fun callVirtual(descriptor: FunctionDescriptor, args: List): LLVMValueRef { - val thisI8PtrPtr = codegen.bitcast(kInt8PtrPtr, args[0]) // Cast "this (i8*)" to i8**. - val typeInfoI8Ptr = codegen.load(thisI8PtrPtr) // Load TypeInfo address. - val typeInfoPtr = codegen.bitcast(codegen.kTypeInfoPtr, typeInfoI8Ptr) // Cast TypeInfo (i8*) to TypeInfo*. - val methodHash = codegen.functionHash(descriptor) // Calculate hash of the method to be invoked - val lookupArgs = listOf(typeInfoPtr, methodHash) // Prepare args for lookup - val llvmMethod = call(context.llvm.lookupOpenMethodFunction, - lookupArgs) // Get method ptr to be invoked + val typeInfoPtrPtr = LLVMBuildStructGEP(codegen.builder, args[0], 0 /* type_info */, "")!! + val typeInfoPtr = codegen.load(typeInfoPtrPtr) + 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 vtablePtr = LLVMBuildStructGEP(codegen.builder, typeInfoPtr, 7 /* vtable */, "")!! + val vtable = codegen.load(vtablePtr) + // TODO: those two loads are bad, we shall rework vtable to follow TypeInfo in memory, so + // this code shall just take end address of TypeInfo and use numbered slot from there. + val slot = codegen.gep(vtable, Int32(index).llvm) + codegen.load(slot) + } else { + // Otherwise, call via hashtable. + // TODO: optimize by storing interface number in lower bits of 'this' pointer + // when passing object as an interface. This way we can use those bits as index + // for an additional per-interface vtable. + val methodHash = codegen.functionHash(descriptor) // Calculate hash of the method to be invoked + val lookupArgs = listOf(typeInfoPtr, methodHash) // Prepare args for lookup + call(context.llvm.lookupOpenMethodFunction, + lookupArgs) + } val functionPtrType = pointerType(codegen.getLlvmFunctionType(descriptor)) // Construct type of the method to be invoked val function = codegen.bitcast(functionPtrType, llvmMethod) // Cast method address to the type return call(descriptor, function, args) // Invoke the method diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt index 700dc304de1..bb0fb5c62b0 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/NameUtils.kt @@ -1,13 +1,15 @@ package org.jetbrains.kotlin.backend.konan.llvm -import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.backend.konan.descriptors.isInterface +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.ClassKind.* -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils @@ -94,4 +96,4 @@ internal val ClassDescriptor.typeInfoSymbolName: String get() = "ktype:" + this.fqNameSafe.toString() internal val PropertyDescriptor.symbolName:String -get() = "kvar:${this.containingDeclaration.name.asString()}.${this.name.asString()}" +get() = "kvar:${this.containingDeclaration.name.asString()}.${this.name.asString()}" \ No newline at end of file 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 4bbe52711dc..fc15959cc1b 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 @@ -4,16 +4,16 @@ 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.descriptors.methodTableEntries +import org.jetbrains.kotlin.backend.konan.descriptors.vtableEntries import org.jetbrains.kotlin.backend.konan.descriptors.implementation import org.jetbrains.kotlin.backend.konan.descriptors.implementedInterfaces import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe -import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny @@ -71,46 +71,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { return classType } - // TODO: optimize - private fun getVtableEntries(classDesc: ClassDescriptor): List { - assert (!classDesc.isInterface) - - val superVtableEntries = if (KotlinBuiltIns.isSpecialClassWithNoSupertypes(classDesc)) { - emptyList() - } else { - getVtableEntries(classDesc.getSuperClassOrAny()) - } - - val methods = classDesc.getContributedMethods() // TODO: ensure order is well-defined - - val inheritedVtableSlots = superVtableEntries.map { superMethod -> - methods.single { OverridingUtil.overrides(it, superMethod) } - } - - return inheritedVtableSlots + (methods - inheritedVtableSlots).filter { it.isOverridable } - } - - private fun getMethodTableEntries(classDesc: ClassDescriptor): List { - assert (classDesc.modality != Modality.ABSTRACT) - - return classDesc.getContributedMethods().filter { it.isOverridableOrOverrides } - // TODO: probably method table should contain all accessible methods to improve binary compatibility - } - - private fun ClassDescriptor.getContributedMethods(): List { - val contributedDescriptors = unsubstitutedMemberScope.getContributedDescriptors() - // (includes declarations from supers) - - val functions = contributedDescriptors.filterIsInstance() - - val properties = contributedDescriptors.filterIsInstance() - val getters = properties.mapNotNull { it.getter } - val setters = properties.mapNotNull { it.setter } - - val allMethods = functions + getters + setters - return allMethods - } - private fun exportTypeInfoIfRequired(classDesc: ClassDescriptor, typeInfoGlobal: LLVMValueRef?) { val annot = classDesc.annotations.findAnnotation(FqName("konan.ExportTypeInfo")) if (annot != null) { @@ -186,9 +146,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { if (classDesc.modality != Modality.ABSTRACT) { // TODO: compile-time resolution limits binary compatibility - vtable = getVtableEntries(classDesc).map { it.implementation.entryPointAddress } + vtable = classDesc.vtableEntries.map { it.implementation.entryPointAddress } - methods = getMethodTableEntries(classDesc).map { + methods = classDesc.methodTableEntries.map { val nameSignature = it.functionName.localHash // TODO: compile-time resolution limits binary compatibility val methodEntryPoint = it.implementation.entryPointAddress diff --git a/runtime/src/main/cpp/Common.h b/runtime/src/main/cpp/Common.h index 69bcead4f24..287344d2193 100644 --- a/runtime/src/main/cpp/Common.h +++ b/runtime/src/main/cpp/Common.h @@ -2,5 +2,7 @@ #define RUNTIME_COMMON_H #define RUNTIME_NOTHROW __attribute__((nothrow)) +#define RUNTIME_CONST __attribute__((const)) +#define RUNTIME_PURE __attribute__((pure)) #endif // RUNTIME_COMMON_H diff --git a/runtime/src/main/cpp/TypeInfo.cpp b/runtime/src/main/cpp/TypeInfo.cpp index b88aa05dbdc..8524f532266 100644 --- a/runtime/src/main/cpp/TypeInfo.cpp +++ b/runtime/src/main/cpp/TypeInfo.cpp @@ -3,7 +3,7 @@ // If one shall use binary search when looking up methods and fields. // TODO: maybe select strategy basing on number of elements. -#define USE_BINARY_SEARCH 0 +#define USE_BINARY_SEARCH 1 extern "C" { #if USE_BINARY_SEARCH diff --git a/runtime/src/main/cpp/TypeInfo.h b/runtime/src/main/cpp/TypeInfo.h index 6593b65b914..92f25ee478d 100644 --- a/runtime/src/main/cpp/TypeInfo.h +++ b/runtime/src/main/cpp/TypeInfo.h @@ -3,6 +3,7 @@ #include +#include "Common.h" #include "Names.h" // An element of sorted by hash in-place array representing methods. @@ -47,10 +48,16 @@ struct TypeInfo { extern "C" { #endif // Find offset of given hash in table. -int LookupFieldOffset(const TypeInfo* type_info, FieldNameHash hash); +// Note, that we use attribute const, which assumes function doesn't +// dereference global memory, while this function does. However, it seems +// to be safe, as actual result of this computation depends only on 'type_info' +// and 'hash' numeric values and doesn't really depends on global memory state +// (as TypeInfo is compile time constant and type info pointers are stable). +int LookupFieldOffset(const TypeInfo* type_info, FieldNameHash hash) RUNTIME_CONST; // Find open method by its hash. Other methods are resolved in compile-time. -void* LookupOpenMethod(const TypeInfo* info, MethodNameHash nameSignature); +// See comment in LookupFieldOffset(). +void* LookupOpenMethod(const TypeInfo* info, MethodNameHash nameSignature) RUNTIME_CONST; #ifdef __cplusplus } // extern "C" diff --git a/runtime/src/main/cpp/Types.h b/runtime/src/main/cpp/Types.h index 4ee1a02aa1f..771655ad3d2 100644 --- a/runtime/src/main/cpp/Types.h +++ b/runtime/src/main/cpp/Types.h @@ -1,6 +1,7 @@ #ifndef RUNTIME_TYPES_H #define RUNTIME_TYPES_H +#include "Common.h" #include "Memory.h" #include "TypeInfo.h" @@ -36,9 +37,9 @@ extern const TypeInfo* theBooleanArrayTypeInfo; extern const TypeInfo* theStringTypeInfo; extern const TypeInfo* theThrowableTypeInfo; -KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info); +KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE; void CheckCast(const ObjHeader* obj, const TypeInfo* type_info); -KBoolean IsArray(KConstRef obj); +KBoolean IsArray(KConstRef obj) RUNTIME_PURE; typedef void (*Initializer)(); struct InitNode {