Implemented fast interface call & check

This commit is contained in:
Igor Chevdar
2019-09-11 14:56:40 +03:00
parent ea3d5dab4e
commit 2fe39525d4
15 changed files with 578 additions and 61 deletions
@@ -267,6 +267,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
ClassLayoutBuilder(irClass, this)
}
lateinit var globalHierarchyAnalysisResult: GlobalHierarchyAnalysisResult
// We serialize untouched descriptor tree and IR.
// But we have to wait until the code generation phase,
// to dump this information into generated file.
@@ -81,14 +81,52 @@ internal class OverriddenFunctionInfo(
}
}
internal class ClassGlobalHierarchyInfo(val classIdLo: Int, val classIdHi: Int) {
internal class ClassGlobalHierarchyInfo(val classIdLo: Int, val classIdHi: Int,
val interfaceId: Int, val interfaceColor: Int) {
companion object {
val DUMMY = ClassGlobalHierarchyInfo(0, 0)
val DUMMY = ClassGlobalHierarchyInfo(0, 0, 0, 0)
// 32-items table seems like a good threshold.
val MAX_BITS_PER_COLOR = 5
}
}
internal class GlobalHierarchyAnalysisResult(val bitsPerColor: Int)
internal class GlobalHierarchyAnalysis(val context: Context) {
fun run() {
/*
* The algorithm for fast interface call and check:
* Consider the following graph: the vertices are interfaces and two interfaces are
* connected with an edge if there exists a class which inherits both of them.
* Now find a proper vertex-coloring of that graph (such that no edge connects vertices of same color).
* Assign to each interface a unique id in such a way that its color is stored in the lower bits of its id.
* Assuming the number of colors used is reasonably small build then a perfect hash table for each class:
* for each interfaceId inherited: itable[interfaceId % size] == interfaceId
* Since we store the color in the lower bits the division can be replaced with (interfaceId & (size - 1)).
* This is indeed a perfect hash table by construction of the coloring of the interface graph.
* Now to perform an interface call store in all itables pointers to vtables of that particular interface.
* Interface call: *(itable[interfaceId & (size - 1)].vtable[methodIndex])(...)
* Interface check: itable[interfaceId & (size - 1)].id == interfaceId
*
* Note that we have a fallback to a more conservative version if the size of an itable is too large:
* just save all interface ids and vtables in sorted order and find the needed one with the binary search.
* We can signal that using the sign bit of the type info's size field:
* if (size >= 0) { .. fast path .. }
* else binary_search(0, -size)
*/
val interfaceColors = assignColorsToInterfaces()
val maxColor = interfaceColors.values.max() ?: 0
var bitsPerColor = 0
var x = maxColor
while (x > 0) {
++bitsPerColor
x /= 2
}
val maxInterfaceId = Int.MAX_VALUE shr bitsPerColor
val colorCounts = IntArray(maxColor + 1)
/*
* Here's the explanation of what's happening here:
* Given a tree we can traverse it with the DFS and save for each vertex two times:
@@ -110,9 +148,17 @@ internal class GlobalHierarchyAnalysis(val context: Context) {
}
override fun visitClass(declaration: IrClass) {
if (declaration.isInterface)
context.getLayoutBuilder(declaration).hierarchyInfo = ClassGlobalHierarchyInfo(0, 0)
else {
if (declaration.isInterface) {
val color = interfaceColors[declaration]!!
// Numerate from 1 (reserve 0 for invalid value).
val interfaceId = ++colorCounts[color]
assert (interfaceId <= maxInterfaceId) {
"Unable to assign interface id to ${declaration.name}"
}
context.getLayoutBuilder(declaration).hierarchyInfo =
ClassGlobalHierarchyInfo(0, 0,
color or (interfaceId shl bitsPerColor), color)
} else {
allClasses += declaration
if (declaration != root) {
val superClass = declaration.getSuperClassNotAny() ?: root
@@ -131,10 +177,83 @@ internal class GlobalHierarchyAnalysis(val context: Context) {
val enterTime = if (irClass == root) -1 else time
immediateInheritors[irClass]?.forEach { dfs(it) }
val exitTime = time
context.getLayoutBuilder(irClass).hierarchyInfo = ClassGlobalHierarchyInfo(enterTime, exitTime)
context.getLayoutBuilder(irClass).hierarchyInfo = ClassGlobalHierarchyInfo(enterTime, exitTime, 0, 0)
}
dfs(root)
context.globalHierarchyAnalysisResult = GlobalHierarchyAnalysisResult(bitsPerColor)
}
class InterfacesForbiddennessGraph(val nodes: List<IrClass>, val forbidden: List<List<Int>>) {
fun computeColoringGreedy(): IntArray {
val colors = IntArray(nodes.size) { -1 }
var numberOfColors = 0
val usedColors = BooleanArray(nodes.size)
for (v in nodes.indices) {
for (c in 0 until numberOfColors)
usedColors[c] = false
for (u in forbidden[v])
if (colors[u] >= 0)
usedColors[colors[u]] = true
var found = false
for (c in 0 until numberOfColors)
if (!usedColors[c]) {
colors[v] = c
found = true
break
}
if (!found)
colors[v] = numberOfColors++
}
return colors
}
companion object {
fun build(irModuleFragment: IrModuleFragment): InterfacesForbiddennessGraph {
val interfaceIndices = mutableMapOf<IrClass, Int>()
val interfaces = mutableListOf<IrClass>()
val forbidden = mutableListOf<MutableList<Int>>()
irModuleFragment.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
fun registerInterface(iface: IrClass) {
interfaceIndices.getOrPut(iface) {
forbidden.add(mutableListOf())
interfaces.add(iface)
interfaces.size - 1
}
}
override fun visitClass(declaration: IrClass) {
if (declaration.isInterface)
registerInterface(declaration)
else {
val implementedInterfaces = declaration.implementedInterfaces
implementedInterfaces.forEach { registerInterface(it) }
for (i in 0 until implementedInterfaces.size)
for (j in i + 1 until implementedInterfaces.size) {
val v = interfaceIndices[implementedInterfaces[i]]!!
val u = interfaceIndices[implementedInterfaces[j]]!!
forbidden[v].add(u)
forbidden[u].add(v)
}
}
super.visitClass(declaration)
}
})
return InterfacesForbiddennessGraph(interfaces, forbidden)
}
}
}
private fun assignColorsToInterfaces(): Map<IrClass, Int> {
val graph = InterfacesForbiddennessGraph.build(context.irModule!!)
val coloring = graph.computeColoringGreedy()
return graph.nodes.mapIndexed { v, irClass -> irClass to coloring[v] }.toMap()
}
}
@@ -232,6 +351,30 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
// TODO: probably method table should contain all accessible methods to improve binary compatibility
}
val interfaceTableEntries: List<IrSimpleFunction> by lazy {
irClass.sortedOverridableOrOverridingMethods
.filter { f ->
f.isReal || f.overriddenSymbols.any { OverriddenFunctionInfo(f, it.owner).needBridge }
}
.toList()
}
data class InterfaceTablePlace(val interfaceId: Int, val methodIndex: Int) {
companion object {
val INVALID = InterfaceTablePlace(0, -1)
}
}
fun itablePlace(function: IrSimpleFunction): InterfaceTablePlace {
assert (irClass.isInterface) { "An interface expected but was ${irClass.name}" }
val itable = interfaceTableEntries
val index = itable.indexOf(function)
if (index >= 0)
return InterfaceTablePlace(hierarchyInfo.interfaceId, index)
val superFunction = function.overriddenSymbols.first().owner
return context.getLayoutBuilder(superFunction.parentAsClass).itablePlace(superFunction)
}
/**
* All fields of the class instance.
* The order respects the class hierarchy, i.e. a class [fields] contains superclass [fields] as a prefix.
@@ -302,8 +445,7 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
private val IrClass.sortedOverridableOrOverridingMethods: List<IrSimpleFunction>
get() =
this.simpleFunctions()
.filter { (it.isOverridable || it.overriddenSymbols.isNotEmpty())
&& it.bridgeTarget == null }
.filter { it.isOverridableOrOverrides && it.bridgeTarget == null }
.sortedBy { it.uniqueId }
private val functionIds = mutableMapOf<IrFunction, Long>()
@@ -9,6 +9,7 @@ package org.jetbrains.kotlin.backend.konan.llvm
import kotlinx.cinterop.*
import llvm.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.ClassGlobalHierarchyInfo
import org.jetbrains.kotlin.backend.konan.llvm.objc.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.declarations.*
@@ -699,21 +700,63 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
return switch
}
fun loadTypeInfo(objPtr: LLVMValueRef): LLVMValueRef {
val typeInfoOrMetaPtr = structGep(objPtr, 0 /* typeInfoOrMeta_ */)
val typeInfoOrMetaWithFlags = load(typeInfoOrMetaPtr)
// Clear two lower bits.
val typeInfoOrMetaWithFlagsRaw = ptrToInt(typeInfoOrMetaWithFlags, codegen.intPtrType)
val typeInfoOrMetaRaw = and(typeInfoOrMetaWithFlagsRaw, codegen.immTypeInfoMask)
val typeInfoOrMeta = intToPtr(typeInfoOrMetaRaw, kTypeInfoPtr)
val typeInfoPtrPtr = structGep(typeInfoOrMeta, 0 /* typeInfo */)
return load(typeInfoPtrPtr)
}
fun lookupInterfaceTableRecord(typeInfo: LLVMValueRef, interfaceId: Int): LLVMValueRef {
val interfaceTableSize = load(structGep(typeInfo, 11 /* interfaceTableSize_ */))
val interfaceTable = load(structGep(typeInfo, 12 /* interfaceTable_ */))
fun fastPath(): LLVMValueRef {
// The fastest optimistic version.
val interfaceTableIndex = and(interfaceTableSize, Int32(interfaceId).llvm)
return gep(interfaceTable, interfaceTableIndex)
}
// See details in ClassLayoutBuilder.
return if (context.globalHierarchyAnalysisResult.bitsPerColor <= ClassGlobalHierarchyInfo.MAX_BITS_PER_COLOR
&& context.config.produce != CompilerOutputKind.FRAMEWORK) {
// All interface tables are small and no unknown interface inheritance.
fastPath()
} else {
val startLocationInfo = position()?.start
val fastPathBB = basicBlock("fast_path", startLocationInfo)
val slowPathBB = basicBlock("slow_path", startLocationInfo)
val takeResBB = basicBlock("take_res", startLocationInfo)
condBr(icmpGe(interfaceTableSize, kImmInt32Zero), fastPathBB, slowPathBB)
positionAtEnd(takeResBB)
val resultPhi = phi(pointerType(runtime.interfaceTableRecordType))
appendingTo(fastPathBB) {
val fastValue = fastPath()
br(takeResBB)
addPhiIncoming(resultPhi, currentBlock to fastValue)
}
appendingTo(slowPathBB) {
val actualInterfaceTableSize = sub(kImmInt32Zero, interfaceTableSize) // -interfaceTableSize
val slowValue = call(context.llvm.lookupInterfaceTableRecord,
listOf(interfaceTable, actualInterfaceTableSize, Int32(interfaceId).llvm))
br(takeResBB)
addPhiIncoming(resultPhi, currentBlock to slowValue)
}
resultPhi
}
}
fun lookupVirtualImpl(receiver: LLVMValueRef, irFunction: IrFunction): LLVMValueRef {
assert(LLVMTypeOf(receiver) == codegen.kObjHeaderPtr)
val typeInfoPtr: LLVMValueRef = if (irFunction.getObjCMethodInfo() != null) {
val typeInfoPtr: LLVMValueRef = if (irFunction.getObjCMethodInfo() != null)
call(context.llvm.getObjCKotlinTypeInfo, listOf(receiver))
} else {
val typeInfoOrMetaPtr = structGep(receiver, 0 /* typeInfoOrMeta_ */)
val typeInfoOrMetaWithFlags = load(typeInfoOrMetaPtr)
// Clear two lower bits.
val typeInfoOrMetaWithFlagsRaw = ptrToInt(typeInfoOrMetaWithFlags, codegen.intPtrType)
val typeInfoOrMetaRaw = and(typeInfoOrMetaWithFlagsRaw, codegen.immTypeInfoMask)
val typeInfoOrMeta = intToPtr(typeInfoOrMetaRaw, kTypeInfoPtr)
val typeInfoPtrPtr = structGep(typeInfoOrMeta, 0 /* typeInfo */)
load(typeInfoPtrPtr)
}
else
loadTypeInfo(receiver)
assert(typeInfoPtr.type == codegen.kTypeInfoPtr) { LLVMPrintTypeToString(typeInfoPtr.type)!!.toKString() }
@@ -724,25 +767,29 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
*/
val anyMethod = (irFunction as IrSimpleFunction).findOverriddenMethodOfAny()
val owner = (anyMethod ?: irFunction).parentAsClass
val methodHash = codegen.functionHash(irFunction)
val llvmMethod = if (!owner.isInterface) {
// If this is a virtual method of the class - we can call via vtable.
val index = context.getLayoutBuilder(owner).vtableIndex(anyMethod ?: irFunction)
val vtablePlace = gep(typeInfoPtr, Int32(1).llvm) // typeInfoPtr + 1
val vtable = bitcast(kInt8PtrPtr, vtablePlace)
val slot = gep(vtable, Int32(index).llvm)
load(slot)
} else {
// Otherwise, call by hash.
// 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(irFunction) // Calculate hash of the method to be invoked
val lookupArgs = listOf(typeInfoPtr, methodHash) // Prepare args for lookup
call(context.llvm.lookupOpenMethodFunction, lookupArgs)
val llvmMethod = when {
!owner.isInterface -> {
// If this is a virtual method of the class - we can call via vtable.
val index = context.getLayoutBuilder(owner).vtableIndex(anyMethod ?: irFunction)
val vtablePlace = gep(typeInfoPtr, Int32(1).llvm) // typeInfoPtr + 1
val vtable = bitcast(kInt8PtrPtr, vtablePlace)
val slot = gep(vtable, Int32(index).llvm)
load(slot)
}
!context.shouldOptimize() -> call(context.llvm.lookupOpenMethodFunction, listOf(typeInfoPtr, methodHash))
else -> {
// Essentially: typeInfo.itable[place(interfaceId)].vtable[method]
val itablePlace = context.getLayoutBuilder(owner).itablePlace(irFunction)
val interfaceTableRecord = lookupInterfaceTableRecord(typeInfoPtr, itablePlace.interfaceId)
load(gep(load(structGep(interfaceTableRecord, 2 /* vtable */)), Int32(itablePlace.methodIndex).llvm))
}
}
val functionPtrType = pointerType(codegen.getLlvmFunctionType(irFunction)) // Construct type of the method to be invoked
return bitcast(functionPtrType, llvmMethod) // Cast method address to the type
val functionPtrType = pointerType(codegen.getLlvmFunctionType(irFunction))
return bitcast(functionPtrType, llvmMethod)
}
private fun IrSimpleFunction.findOverriddenMethodOfAny(): IrSimpleFunction? {
@@ -435,6 +435,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val enterFrameFunction = importModelSpecificRtFunction("EnterFrame")
val leaveFrameFunction = importModelSpecificRtFunction("LeaveFrame")
val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod")
val lookupInterfaceTableRecord = importRtFunction("LookupInterfaceTableRecord")
val isInstanceFunction = importRtFunction("IsInstance")
val isInstanceOfClassFastFunction = importRtFunction("IsInstanceOfClassFast")
val throwExceptionFunction = importRtFunction("ThrowException")
@@ -1391,13 +1391,23 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, obj)
return if (context.shouldOptimize() && !dstClass.isInterface) {
val dstHierarchyInfo = context.getLayoutBuilder(dstClass).hierarchyInfo
call(context.llvm.isInstanceOfClassFastFunction,
listOf(srcObjInfoPtr, Int32(dstHierarchyInfo.classIdLo).llvm, Int32(dstHierarchyInfo.classIdHi).llvm))
return if (!context.shouldOptimize()) {
call(context.llvm.isInstanceFunction, listOf(srcObjInfoPtr, codegen.typeInfoValue(dstClass)))
} else {
val dstTypeInfo = codegen.typeInfoValue(dstClass)
call(context.llvm.isInstanceFunction, listOf(srcObjInfoPtr, dstTypeInfo))
val dstHierarchyInfo = context.getLayoutBuilder(dstClass).hierarchyInfo
if (!dstClass.isInterface) {
call(context.llvm.isInstanceOfClassFastFunction,
listOf(srcObjInfoPtr, Int32(dstHierarchyInfo.classIdLo).llvm, Int32(dstHierarchyInfo.classIdHi).llvm))
} else {
// Essentially: typeInfo.itable[place(interfaceId)].id == interfaceId
val interfaceId = dstHierarchyInfo.interfaceId
val typeInfo = functionGenerationContext.loadTypeInfo(srcObjInfoPtr)
with(functionGenerationContext) {
val interfaceTableRecord = lookupInterfaceTableRecord(typeInfo, interfaceId)
icmpEq(load(structGep(interfaceTableRecord, 0 /* id */)), Int32(interfaceId).llvm)
}
}
}
}
@@ -42,6 +42,13 @@ private class ConstGetElementPtr(val pointer: ConstPointer, val index: Int) : Co
internal fun ConstPointer.bitcast(toType: LLVMTypeRef) = constPointer(LLVMConstBitCast(this.llvm, toType)!!)
internal class ConstArray(elementType: LLVMTypeRef?, val elements: List<ConstValue>) : ConstValue {
init {
elements.forEach {
assert(it.llvmType == elementType) {
"Expected element type: ${llvmtype2string(elementType)}, actual: ${llvmtype2string(it.llvmType)}"
}
}
}
override val llvm = LLVMConstArray(elementType, elements.map { it.llvm }.toCValues(), elements.size)!!
}
@@ -144,6 +151,7 @@ internal val kBoolean = kInt1
internal val kInt8Ptr = pointerType(int8Type)
internal val kInt8PtrPtr = pointerType(kInt8Ptr)
internal val kNullInt8Ptr = LLVMConstNull(kInt8Ptr)!!
internal val kImmInt32Zero = Int32(0).llvm
internal val kImmInt32One = Int32(1).llvm
internal val ContextUtils.kNullObjHeaderPtr: LLVMValueRef
get() = LLVMConstNull(this.kObjHeaderPtr)!!
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.backend.konan.llvm
import llvm.*
import org.jetbrains.kotlin.backend.common.ir.simpleFunctions
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.*
@@ -68,6 +69,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
inner class MethodTableRecord(val nameSignature: LocalHash, methodEntryPoint: ConstPointer?) :
Struct(runtime.methodTableRecordType, nameSignature, methodEntryPoint)
inner class InterfaceTableRecord(id: Int32, vtableSize: Int32, vtable: ConstPointer) :
Struct(runtime.interfaceTableRecordType, id, vtableSize, vtable)
private inner class TypeInfo(
selfPtr: ConstPointer,
extendedInfo: ConstPointer,
@@ -79,6 +83,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
interfacesCount: Int,
methods: ConstValue,
methodsCount: Int,
interfaceTableSize: Int,
interfaceTable: ConstPointer?,
packageName: String?,
relativeName: String?,
flags: Int,
@@ -108,6 +114,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
methods,
Int32(methodsCount),
Int32(interfaceTableSize),
interfaceTable,
kotlinStringLiteral(packageName),
kotlinStringLiteral(relativeName),
@@ -181,13 +190,15 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
val bodyType = llvmDeclarations.bodyType
val size = getInstanceSize(bodyType, className)
val instanceSize = getInstanceSize(bodyType, className)
val superTypeOrAny = irClass.getSuperClassNotAny() ?: context.ir.symbols.any.owner
val superType = if (irClass.isAny()) NullPointer(runtime.typeInfoType)
else superTypeOrAny.typeInfoPtr
val interfaces = irClass.implementedInterfaces.map { it.typeInfoPtr }
val implementedInterfaces = irClass.implementedInterfaces
val interfaces = implementedInterfaces.map { it.typeInfoPtr }
val interfacesPtr = staticData.placeGlobalConstArray("kintf:$className",
pointerType(runtime.typeInfoType), interfaces)
@@ -209,6 +220,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
val methodsPtr = staticData.placeGlobalConstArray("kmethods:$className",
runtime.methodTableRecordType, methods)
val (interfaceTable, interfaceTableSize) = interfaceTable(irClass)
val reflectionInfo = getReflectionInfo(irClass)
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
val hierarchyInfo =
@@ -218,15 +231,18 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
val typeInfo = TypeInfo(
irClass.typeInfoPtr,
makeExtendedInfo(irClass),
size,
instanceSize,
superType,
objOffsetsPtr, objOffsetsCount,
interfacesPtr, interfaces.size,
methodsPtr, methods.size,
interfaceTableSize, interfaceTable,
reflectionInfo.packageName,
reflectionInfo.relativeName,
flagsFromClass(irClass),
hierarchyInfo.classIdLo,
if (irClass.isInterface)
hierarchyInfo.interfaceId
else hierarchyInfo.classIdLo,
llvmDeclarations.writableTypeInfoGlobal?.pointer,
associatedObjects = genAssociatedObjects(irClass)
)
@@ -285,6 +301,78 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
}.sortedBy { it.nameSignature.value }
}
private fun interfaceTable(irClass: IrClass): Pair<ConstPointer?, Int> {
val needInterfaceTable = context.shouldOptimize() && !irClass.isInterface
&& !irClass.isAbstract() && !irClass.isObjCClass()
if (!needInterfaceTable) return Pair(null, 0)
// The details are in ClassLayoutBuilder.
val interfaceLayouts = irClass.implementedInterfaces.map { context.getLayoutBuilder(it) }
val interfaceColors = interfaceLayouts.map { it.hierarchyInfo.interfaceColor }
// Find the optimal size. It must be a power of 2.
var size = 1
val maxSize = 1 shl ClassGlobalHierarchyInfo.MAX_BITS_PER_COLOR
val used = BooleanArray(maxSize)
while (size <= maxSize) {
for (i in 0 until size)
used[i] = false
// Check for collisions.
var ok = true
for (color in interfaceColors) {
val index = color % size
if (used[index]) {
ok = false
break
}
used[index] = true
}
if (ok) break
size *= 2
}
val conservative = size > maxSize
val interfaceTableSkeleton = if (conservative) {
size = interfaceLayouts.size
interfaceLayouts.sortedBy { it.hierarchyInfo.interfaceId }.toTypedArray()
} else arrayOfNulls<ClassLayoutBuilder?>(size).also {
for (interfaceLayout in interfaceLayouts)
it[interfaceLayout.hierarchyInfo.interfaceId % size] = interfaceLayout
}
val methodTableEntries = context.getLayoutBuilder(irClass).methodTableEntries
val className = irClass.fqNameForIrSerialization
val interfaceTableEntries = interfaceTableSkeleton.map { iface ->
val interfaceId = iface?.hierarchyInfo?.interfaceId ?: 0
InterfaceTableRecord(
Int32(interfaceId),
Int32(iface?.interfaceTableEntries?.size ?: 0),
if (iface == null)
NullPointer(kInt8Ptr)
else {
val vtableEntries = iface.interfaceTableEntries.map { ifaceFunction ->
val impl = OverriddenFunctionInfo(
methodTableEntries.first { ifaceFunction in it.function.allOverriddenFunctions }.function,
ifaceFunction
).implementation
if (impl == null || context.referencedFunctions?.contains(impl) == false)
NullPointer(int8Type)
else impl.entryPointAddress
}
staticData.placeGlobalConstArray("kifacevtable:${className}_$interfaceId",
kInt8Ptr, vtableEntries
)
}
)
}
return Pair(
staticData.placeGlobalConstArray("kifacetable:$className",
runtime.interfaceTableRecordType, interfaceTableEntries),
if (conservative) -size else (size - 1)
)
}
private fun mapRuntimeType(type: LLVMTypeRef): Int =
runtimeTypeMap[type] ?: throw Error("Unmapped type: ${llvmtype2string(type)}")
@@ -399,7 +487,22 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
val typeHierarchyInfo = if (!context.shouldOptimize())
ClassGlobalHierarchyInfo.DUMMY
else
ClassGlobalHierarchyInfo(-1, -1)
ClassGlobalHierarchyInfo(-1, -1, 0, 0)
val interfaceTable = if (!context.shouldOptimize()) null else {
val layoutBuilder = context.getLayoutBuilder(irClass)
val vtableEntries = layoutBuilder.interfaceTableEntries.map { methodImpls[it]!!.bitcast(int8TypePtr) }
val interfaceVTable = staticData.placeGlobalArray("", kInt8Ptr, vtableEntries)
staticData.placeGlobalConstArray("",
runtime.interfaceTableRecordType, listOf(
InterfaceTableRecord(
Int32(layoutBuilder.hierarchyInfo.interfaceId),
Int32(layoutBuilder.interfaceTableEntries.size),
interfaceVTable.pointer.getElementPtr(0)
)
))
}
val typeInfoWithVtable = Struct(TypeInfo(
selfPtr = result,
extendedInfo = NullPointer(runtime.extendedTypeInfoType),
@@ -408,6 +511,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
objOffsets = objOffsetsPtr, objOffsetsCount = objOffsetsCount,
interfaces = interfacesPtr, interfacesCount = interfaces.size,
methods = methodsPtr, methodsCount = methods.size,
// 0 is a perfectly valid itable size (since 0 is in form of (2^k - 1).
interfaceTableSize = 0, interfaceTable = interfaceTable,
packageName = reflectionInfo.packageName,
relativeName = reflectionInfo.relativeName,
flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0),
@@ -23,6 +23,7 @@ class Runtime(bitcodeFile: String) {
val extendedTypeInfoType = getStructType("ExtendedTypeInfo")
val writableTypeInfoType = getStructTypeOrNull("WritableTypeInfo")
val methodTableRecordType = getStructType("MethodTableRecord")
val interfaceTableRecordType = getStructType("InterfaceTableRecord")
val globalHashType = getStructType("GlobalHash")
val associatedObjectTableRecordType = getStructType("AssociatedObjectTableRecord")
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCCodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.backend.konan.serialization.resolveFakeOverrideMaybeAbstract
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.konan.CurrentKonanModuleOrigin
import org.jetbrains.kotlin.ir.declarations.*
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.isReal
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.name.Name
@@ -317,12 +319,15 @@ internal class ObjCExportCodeGenerator(
inner class KotlinToObjCMethodAdapter(
selector: String,
nameSignature: Long,
itablePlace: ClassLayoutBuilder.InterfaceTablePlace,
vtableIndex: Int,
kotlinImpl: ConstPointer
) : Struct(
runtime.kotlinToObjCMethodAdapter,
staticData.cStringLiteral(selector),
Int64(nameSignature),
Int32(itablePlace.interfaceId),
Int32(itablePlace.methodIndex),
Int32(vtableIndex),
kotlinImpl
)
@@ -333,6 +338,7 @@ internal class ObjCExportCodeGenerator(
vtable: ConstPointer?,
vtableSize: Int,
methodTable: List<RTTIGenerator.MethodTableRecord>,
itableSize: Int,
val objCName: String,
directAdapters: List<ObjCToKotlinMethodAdapter>,
classAdapters: List<ObjCToKotlinMethodAdapter>,
@@ -348,6 +354,8 @@ internal class ObjCExportCodeGenerator(
staticData.placeGlobalConstArray("", runtime.methodTableRecordType, methodTable),
Int32(methodTable.size),
Int32(itableSize),
staticData.cStringLiteral(objCName),
staticData.placeGlobalConstArray(
@@ -842,7 +850,8 @@ private fun ObjCExportCodeGenerator.createReverseAdapter(
irFunction: IrFunction,
baseMethod: IrFunction,
functionName: String,
vtableIndex: Int?
vtableIndex: Int?,
itablePlace: ClassLayoutBuilder.InterfaceTablePlace?
): ObjCExportCodeGenerator.KotlinToObjCMethodAdapter {
val nameSignature = functionName.localHash.value
@@ -853,7 +862,10 @@ private fun ObjCExportCodeGenerator.createReverseAdapter(
baseMethod
).bitcast(int8TypePtr)
return KotlinToObjCMethodAdapter(selector, nameSignature, vtableIndex ?: -1, kotlinToObjC)
return KotlinToObjCMethodAdapter(selector, nameSignature,
itablePlace ?: ClassLayoutBuilder.InterfaceTablePlace.INVALID,
vtableIndex ?: -1,
kotlinToObjC)
}
private fun ObjCExportCodeGenerator.createMethodVirtualAdapter(
@@ -917,6 +929,17 @@ private fun ObjCExportCodeGenerator.vtableIndex(irFunction: IrSimpleFunction): I
}
}
private fun ObjCExportCodeGenerator.itablePlace(irFunction: IrSimpleFunction): ClassLayoutBuilder.InterfaceTablePlace? {
assert(irFunction.isOverridable)
val irClass = irFunction.parentAsClass
return if (irClass.isInterface && context.shouldOptimize()
&& (irFunction.isReal || irFunction.resolveFakeOverrideMaybeAbstract().parent != context.irBuiltIns.anyClass.owner)) {
context.getLayoutBuilder(irClass).itablePlace(irFunction)
} else {
null
}
}
private fun ObjCExportCodeGenerator.createTypeAdapterForFileClass(
fileClass: ObjCClassForKotlinFile
): ObjCExportCodeGenerator.ObjCTypeAdapter {
@@ -930,6 +953,7 @@ private fun ObjCExportCodeGenerator.createTypeAdapterForFileClass(
vtable = null,
vtableSize = -1,
methodTable = emptyList(),
itableSize = -1,
objCName = name,
directAdapters = emptyList(),
classAdapters = adapters,
@@ -1003,6 +1027,10 @@ private fun ObjCExportCodeGenerator.createTypeAdapter(
emptyList()
}
val itableSize = if (irClass.isInterface)
context.getLayoutBuilder(irClass).interfaceTableEntries.size
else -1
when (irClass.kind) {
ClassKind.OBJECT -> {
classAdapters += if (irClass.isUnit()) {
@@ -1022,6 +1050,7 @@ private fun ObjCExportCodeGenerator.createTypeAdapter(
vtable,
vtableSize,
methodTable,
itableSize,
objCName,
adapters,
classAdapters,
@@ -1047,6 +1076,7 @@ private fun ObjCExportCodeGenerator.createReverseAdapters(
val presentVtableBridges = mutableSetOf<Int?>(null)
val presentMethodTableBridges = mutableSetOf<String>()
val presentItableBridges = mutableSetOf<ClassLayoutBuilder.InterfaceTablePlace?>(null)
val allOverriddenMethods = method.allOverriddenFunctions
@@ -1057,16 +1087,20 @@ private fun ObjCExportCodeGenerator.createReverseAdapters(
inherited.forEach {
presentVtableBridges += vtableIndex(it)
presentMethodTableBridges += it.functionName
presentItableBridges += itablePlace(it)
}
uninherited.forEach {
val vtableIndex = vtableIndex(it)
val functionName = it.functionName
val itablePlace = itablePlace(it)
if (vtableIndex !in presentVtableBridges || functionName !in presentMethodTableBridges) {
if (vtableIndex !in presentVtableBridges || functionName !in presentMethodTableBridges
|| itablePlace !in presentItableBridges) {
presentVtableBridges += vtableIndex
presentMethodTableBridges += functionName
result += createReverseAdapter(it, baseMethod, functionName, vtableIndex)
presentItableBridges += itablePlace
result += createReverseAdapter(it, baseMethod, functionName, vtableIndex, itablePlace)
}
}
@@ -1088,7 +1122,8 @@ private fun ObjCExportCodeGenerator.nonOverridableAdapter(
namer.getSelector(baseMethod),
-1,
vtableIndex = if (hasSelectorAmbiguity) -2 else -1, // Describes the reason.
kotlinImpl = NullPointer(int8Type)
kotlinImpl = NullPointer(int8Type),
itablePlace = ClassLayoutBuilder.InterfaceTablePlace.INVALID
)
private val ObjCTypeForKotlinType.kotlinMethods: List<ObjCMethodForKotlinMethod>
@@ -33,6 +33,7 @@ internal class InitializersLowering(val context: CommonBackendContext) : ClassLo
object DECLARATION_ORIGIN_ANONYMOUS_INITIALIZER : IrDeclarationOriginImpl("ANONYMOUS_INITIALIZER")
override fun lower(irClass: IrClass) {
if (irClass.isInterface) return
InitializersTransformer(irClass).lowerInitializers()
}
@@ -529,6 +529,9 @@ internal object DataFlowIR {
layoutBuilder.methodTableEntries.forEach {
type.itable[it.overriddenFunction.functionName.localHash.value] = mapFunction(it.getImplementation(context)!!)
}
} else if (irClass.isInterface) {
// Warmup interface table so it is computed before DCE.
context.getLayoutBuilder(irClass).interfaceTableEntries
}
return type
}
+125 -3
View File
@@ -51,6 +51,8 @@ struct ObjCToKotlinMethodAdapter {
struct KotlinToObjCMethodAdapter {
const char* selector;
MethodNameHash nameSignature;
ClassId interfaceId;
int itableIndex;
int vtableIndex;
const void* kotlinImpl;
};
@@ -64,6 +66,8 @@ struct ObjCTypeAdapter {
const MethodTableRecord* kotlinMethodTable;
int kotlinMethodTableSize;
int kotlinItableSize;
const char* objCName;
const ObjCToKotlinMethodAdapter* directAdapters;
@@ -717,11 +721,70 @@ static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj) {
return converter(obj);
}
static void buildITable(TypeInfo* result, const KStdOrderedMap<ClassId, KStdVector<VTableElement>>& interfaceVTables) {
// Check if can use fast optimistic version - check if the size of the itable could be 2^k and <= 32.
bool useFastITable;
int itableSize = 1;
for (; itableSize <= 32; itableSize <<= 1) {
useFastITable = true;
bool used[32];
memset(used, 0, sizeof(used));
for (auto& pair : interfaceVTables) {
auto interfaceId = pair.first;
auto index = interfaceId & (itableSize - 1);
if (used[index]) {
useFastITable = false;
break;
}
used[index] = true;
}
if (useFastITable) break;
}
if (!useFastITable)
itableSize = interfaceVTables.size();
auto itable_ = konanAllocArray<InterfaceTableRecord>(itableSize);
result->interfaceTable_ = itable_;
result->interfaceTableSize_ = useFastITable ? itableSize - 1 : -itableSize;
if (useFastITable) {
for (auto& pair : interfaceVTables) {
auto interfaceId = pair.first;
auto index = interfaceId & (itableSize - 1);
itable_[index].id = interfaceId;
}
} else {
// Otherwise: conservative version.
// The table will be sorted since we're using KStdOrderedMap.
int index = 0;
for (auto& pair : interfaceVTables) {
auto interfaceId = pair.first;
itable_[index++].id = interfaceId;
}
}
for (int i = 0; i < itableSize; ++i) {
auto interfaceId = itable_[i].id;
if (interfaceId == kInvalidInterfaceId) continue;
auto interfaceVTableIt = interfaceVTables.find(interfaceId);
RuntimeAssert(interfaceVTableIt != interfaceVTables.end(), "");
auto const& interfaceVTable = interfaceVTableIt->second;
int interfaceVTableSize = interfaceVTable.size();
auto interfaceVTable_ = interfaceVTableSize == 0 ? nullptr : konanAllocArray<VTableElement>(interfaceVTableSize);
for (int j = 0; j < interfaceVTableSize; ++j)
interfaceVTable_[j] = interfaceVTable[j];
itable_[i].vtable = interfaceVTable_;
itable_[i].vtableSize = interfaceVTableSize;
}
}
static const TypeInfo* createTypeInfo(
const TypeInfo* superType,
const KStdVector<const TypeInfo*>& superInterfaces,
const KStdVector<const void*>& vtable,
const KStdVector<MethodTableRecord>& methodTable
const KStdVector<VTableElement>& vtable,
const KStdVector<MethodTableRecord>& methodTable,
const KStdOrderedMap<ClassId, KStdVector<VTableElement>>& interfaceVTables,
bool itableEqualsSuper
) {
TypeInfo* result = (TypeInfo*)konanAllocMemory(sizeof(TypeInfo) + vtable.size() * sizeof(void*));
result->typeInfo_ = result;
@@ -756,6 +819,15 @@ static const TypeInfo* createTypeInfo(
result->implementedInterfaces_ = implementedInterfaces_;
result->implementedInterfacesCount_ = implementedInterfaces.size();
const InterfaceTableRecord* superItable = superType->interfaceTable_;
if (superItable != nullptr) {
if (itableEqualsSuper) {
result->interfaceTableSize_ = superType->interfaceTableSize_;
result->interfaceTable_ = superType->interfaceTable_;
} else {
buildITable(result, interfaceVTables);
}
}
MethodTableRecord* openMethods_ = konanAllocArray<MethodTableRecord>(methodTable.size());
for (int i = 0; i < methodTable.size(); ++i) openMethods_[i] = methodTable[i];
@@ -906,6 +978,23 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType) {
superMethodTable, superMethodTable + superMethodTableSize
);
InterfaceTableRecord const* superITable = superType->interfaceTable_;
KStdOrderedMap<ClassId, KStdVector<VTableElement>> interfaceVTables;
if (superITable != nullptr) {
int superITableSize = superType->interfaceTableSize_;
superITableSize = superITableSize >= 0 ? superITableSize + 1 : -superITableSize;
for (int i = 0; i < superITableSize; ++i) {
auto& record = superITable[i];
auto interfaceId = record.id;
if (interfaceId == kInvalidInterfaceId) continue;
int vtableSize = record.vtableSize;
KStdVector<VTableElement> interfaceVTable(vtableSize);
for (int j = 0; j < vtableSize; ++j)
interfaceVTable[j] = record.vtable[j];
interfaceVTables.emplace(interfaceId, std::move(interfaceVTable));
}
}
KStdVector<const TypeInfo*> addedInterfaces = getProtocolsAsInterfaces(clazz);
KStdVector<const TypeInfo*> supers(
@@ -917,6 +1006,16 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType) {
supers.push_back(t);
}
auto addToITable = [&interfaceVTables](ClassId interfaceId, int methodIndex, VTableElement entry) {
RuntimeAssert(interfaceId != kInvalidInterfaceId, "");
auto interfaceVTableIt = interfaceVTables.find(interfaceId);
RuntimeAssert(interfaceVTableIt != interfaceVTables.end(), "");
auto& interfaceVTable = interfaceVTableIt->second;
RuntimeAssert(methodIndex >= 0 && methodIndex < interfaceVTable.size(), "");
interfaceVTable[methodIndex] = entry;
};
bool itableEqualsSuper = true;
for (const TypeInfo* t : supers) {
const ObjCTypeAdapter* typeAdapter = getTypeAdapter(t);
if (typeAdapter == nullptr) continue;
@@ -927,27 +1026,50 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType) {
throwIfCantBeOverridden(clazz, adapter);
itableEqualsSuper = false;
insertOrReplace(methodTable, adapter->nameSignature, const_cast<void*>(adapter->kotlinImpl));
if (adapter->vtableIndex != -1) vtable[adapter->vtableIndex] = adapter->kotlinImpl;
if (adapter->itableIndex != -1 && superITable != nullptr)
addToITable(adapter->interfaceId, adapter->itableIndex, adapter->kotlinImpl);
}
}
for (const TypeInfo* typeInfo : addedInterfaces) {
const ObjCTypeAdapter* typeAdapter = getTypeAdapter(typeInfo);
if (typeAdapter == nullptr) continue;
if (superITable != nullptr) {
auto interfaceId = typeInfo->classId_;
int interfaceVTableSize = typeAdapter->kotlinItableSize;
RuntimeAssert(interfaceVTableSize >= 0, "");
auto interfaceVTablesIt = interfaceVTables.find(interfaceId);
if (interfaceVTablesIt == interfaceVTables.end()) {
itableEqualsSuper = false;
interfaceVTables.emplace(interfaceId, std::move(KStdVector<VTableElement>(interfaceVTableSize)));
} else {
auto const& interfaceVTable = interfaceVTablesIt->second;
RuntimeAssert(interfaceVTable.size() == interfaceVTableSize, "");
}
}
for (int i = 0; i < typeAdapter->reverseAdapterNum; ++i) {
itableEqualsSuper = false;
const KotlinToObjCMethodAdapter* adapter = &typeAdapter->reverseAdapters[i];
throwIfCantBeOverridden(clazz, adapter);
insertOrReplace(methodTable, adapter->nameSignature, const_cast<void*>(adapter->kotlinImpl));
RuntimeAssert(adapter->vtableIndex == -1, "");
if (adapter->itableIndex != -1 && superITable != nullptr)
addToITable(adapter->interfaceId, adapter->itableIndex, adapter->kotlinImpl);
}
}
// TODO: consider forbidding the class being abstract.
const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, methodTable);
const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, methodTable, interfaceVTables, itableEqualsSuper);
// TODO: it will probably never be requested, since such a class can't be instantiated in Kotlin.
result->writableInfo_->objCExport.objCClass = clazz;
+20
View File
@@ -56,4 +56,24 @@ void* LookupOpenMethod(const TypeInfo* info, MethodNameHash nameSignature) {
#endif
// Seeks for the specified id. In case of failure returns a valid pointer to some record, never returns nullptr.
// It is the caller's responsibility to check if the search has succeeded or not.
InterfaceTableRecord const* LookupInterfaceTableRecord(InterfaceTableRecord const* interfaceTable,
int interfaceTableSize, ClassId interfaceId) {
if (interfaceTableSize <= 8) {
// Linear search.
int i;
for (i = 0; i < interfaceTableSize - 1 && interfaceTable[i].id < interfaceId; ++i);
return interfaceTable + i;
}
int l = 0, r = interfaceTableSize - 1;
while (l < r) {
int m = (l + r) / 2;
if (interfaceTable[m].id < interfaceId)
l = m + 1;
else r = m;
}
return interfaceTable + l;
}
}
+22 -5
View File
@@ -79,6 +79,18 @@ struct ExtendedTypeInfo {
// TODO: do we want any other info here?
};
typedef void const* VTableElement;
typedef int32_t ClassId;
const ClassId kInvalidInterfaceId = 0;
struct InterfaceTableRecord {
ClassId id;
uint32_t vtableSize;
VTableElement const* vtable;
};
// This struct represents runtime type information and by itself is the compile time
// constant.
struct TypeInfo {
@@ -102,6 +114,8 @@ struct TypeInfo {
// Null for abstract classes and interfaces.
const MethodTableRecord* openMethods_;
uint32_t openMethodsCount_;
int32_t interfaceTableSize_;
InterfaceTableRecord const* interfaceTable_;
// String for the fully qualified dot-separated name of the package containing class,
// or `null` if the class is local or anonymous.
@@ -116,7 +130,7 @@ struct TypeInfo {
int32_t flags_;
// Class id built with the whole class hierarchy taken into account. The details are in ClassLayoutBuilder.
int32_t classId_;
ClassId classId_;
#if KONAN_TYPE_INFO_HAS_WRITABLE_PART
WritableTypeInfo* writableInfo_;
@@ -128,12 +142,12 @@ struct TypeInfo {
// vtable starts just after declared contents of the TypeInfo:
// void* const vtable_[];
#ifdef __cplusplus
inline const void* const * vtable() const {
return reinterpret_cast<void * const *>(this + 1);
inline VTableElement const* vtable() const {
return reinterpret_cast<VTableElement const*>(this + 1);
}
inline const void** vtable() {
return reinterpret_cast<const void**>(this + 1);
inline VTableElement* vtable() {
return reinterpret_cast<VTableElement*>(this + 1);
}
#endif
};
@@ -149,6 +163,9 @@ extern "C" {
// (as TypeInfo is compile time constant and type info pointers are stable).
void* LookupOpenMethod(const TypeInfo* info, MethodNameHash nameSignature) RUNTIME_CONST;
InterfaceTableRecord const* LookupInterfaceTableRecord(InterfaceTableRecord const* interfaceTable,
int interfaceTableSize, ClassId interfaceId) RUNTIME_CONST;
#ifdef __cplusplus
} // extern "C"
#endif
+4 -1
View File
@@ -25,6 +25,7 @@
#endif
#include <deque>
#include <map>
#include <string>
#include <set>
#include <unordered_map>
@@ -70,8 +71,10 @@ template<class Value>
using KStdUnorderedSet = std::unordered_set<Value,
std::hash<Value>, std::equal_to<Value>,
KonanAllocator<Value>>;
template<class Value, class Compare>
template<class Value, class Compare = std::less<Value>>
using KStdOrderedSet = std::set<Value, Compare, KonanAllocator<Value>>;
template<class Key, class Value, class Compare = std::less<Key>>
using KStdOrderedMap = std::map<Key, Value, Compare, KonanAllocator<std::pair<const Key, Value>>>;
template<class Value>
using KStdVector = std::vector<Value, KonanAllocator<Value>>;