Implement virtual invoke via vtables. (#181)
Small refactoring to share vtable computation code.
This commit is contained in:
committed by
Konstantin Anisimov
parent
965bb031a0
commit
24219c86b3
+55
@@ -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
|
||||
}
|
||||
+22
-7
@@ -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
|
||||
|
||||
+6
-4
@@ -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
-44
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user