Implement virtual invoke via vtables. (#181)

Small refactoring to share vtable computation code.
This commit is contained in:
Nikolay Igotti
2017-01-16 15:25:36 +03:00
committed by Konstantin Anisimov
parent 965bb031a0
commit 24219c86b3
8 changed files with 102 additions and 60 deletions
@@ -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<FunctionDescriptor>
get() {
val contributedDescriptors = unsubstitutedMemberScope.getContributedDescriptors()
// (includes declarations from supers)
val functions = contributedDescriptors.filterIsInstance<FunctionDescriptor>()
val properties = contributedDescriptors.filterIsInstance<PropertyDescriptor>()
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<FunctionDescriptor>
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<FunctionDescriptor>
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
}
@@ -1844,14 +1844,29 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
//-------------------------------------------------------------------------//
fun callVirtual(descriptor: FunctionDescriptor, args: List<LLVMValueRef>): 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
@@ -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()}"
@@ -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<FunctionDescriptor> {
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<FunctionDescriptor> {
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<FunctionDescriptor> {
val contributedDescriptors = unsubstitutedMemberScope.getContributedDescriptors()
// (includes declarations from supers)
val functions = contributedDescriptors.filterIsInstance<FunctionDescriptor>()
val properties = contributedDescriptors.filterIsInstance<PropertyDescriptor>()
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
+2
View File
@@ -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
+1 -1
View File
@@ -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
+9 -2
View File
@@ -3,6 +3,7 @@
#include <cstdint>
#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"
+3 -2
View File
@@ -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 {