From 3b3318ab06e8c55b1c49da5734decd59524c3d9f Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Sun, 2 May 2021 11:28:34 +0500 Subject: [PATCH] [K/N][codegen] Refactored interface calls Removed old impl for debug builds Fixes https://youtrack.jetbrains.com/issue/KT-44547 as a side effect --- .../konan/descriptors/ClassLayoutBuilder.kt | 76 ++++++------ .../backend/konan/llvm/CodeGenerator.kt | 10 +- .../kotlin/backend/konan/llvm/ContextUtils.kt | 1 - .../backend/konan/llvm/RTTIGenerator.kt | 117 +++++------------- .../kotlin/backend/konan/llvm/Runtime.kt | 1 - .../objcexport/ObjCExportCodeGenerator.kt | 30 ++--- .../konan/optimizations/CallGraphBuilder.kt | 2 +- .../backend/konan/optimizations/DFGBuilder.kt | 14 ++- .../backend/konan/optimizations/DataFlowIR.kt | 18 +-- .../konan/optimizations/Devirtualization.kt | 4 +- .../backend.native/tests/build.gradle | 4 + .../interfaceCallsNCasts/functionNameClash.kt | 28 +++++ .../runtime/src/main/cpp/ObjCExport.mm | 47 +------ .../runtime/src/main/cpp/TypeInfo.cpp | 37 ------ kotlin-native/runtime/src/main/cpp/TypeInfo.h | 22 ---- 15 files changed, 139 insertions(+), 272 deletions(-) create mode 100644 kotlin-native/backend.native/tests/codegen/interfaceCallsNCasts/functionNameClash.kt diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt index 4dae07c4451..50fb765731d 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt @@ -85,10 +85,9 @@ internal class OverriddenFunctionInfo( } } -internal class ClassGlobalHierarchyInfo(val classIdLo: Int, val classIdHi: Int, - val interfaceId: Int, val interfaceColor: Int) { +internal class ClassGlobalHierarchyInfo(val classIdLo: Int, val classIdHi: Int, val interfaceId: Int) { companion object { - val DUMMY = ClassGlobalHierarchyInfo(0, 0, 0, 0) + val DUMMY = ClassGlobalHierarchyInfo(0, 0, 0) // 32-items table seems like a good threshold. val MAX_BITS_PER_COLOR = 5 @@ -160,8 +159,7 @@ internal class GlobalHierarchyAnalysis(val context: Context, val irModule: IrMod "Unable to assign interface id to ${declaration.name}" } context.getLayoutBuilder(declaration).hierarchyInfo = - ClassGlobalHierarchyInfo(0, 0, - color or (interfaceId shl bitsPerColor), color) + ClassGlobalHierarchyInfo(0, 0, color or (interfaceId shl bitsPerColor)) } else { allClasses += declaration if (declaration != root) { @@ -181,7 +179,7 @@ internal class GlobalHierarchyAnalysis(val context: Context, val irModule: IrMod val enterTime = if (irClass == root) -1 else time immediateInheritors[irClass]?.forEach { dfs(it) } val exitTime = time - context.getLayoutBuilder(irClass).hierarchyInfo = ClassGlobalHierarchyInfo(enterTime, exitTime, 0, 0) + context.getLayoutBuilder(irClass).hierarchyInfo = ClassGlobalHierarchyInfo(enterTime, exitTime, 0) } dfs(root) @@ -263,7 +261,7 @@ internal class GlobalHierarchyAnalysis(val context: Context, val irModule: IrMod internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, val isLowered: Boolean) { val vtableEntries: List by lazy { - assert(!irClass.isInterface) + require(!irClass.isInterface) context.logMultiple { +"" @@ -277,7 +275,7 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va context.getLayoutBuilder(superClass).vtableEntries } - val methods = irClass.sortedOverridableOrOverridingMethods + val methods = irClass.overridableOrOverridingMethods val newVtableSlots = mutableListOf() val overridenVtableSlots = mutableMapOf() @@ -336,7 +334,7 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va +"DONE vTable for ${irClass.render()}" } - inheritedVtableSlots + filteredNewVtableSlots.sortedBy { it.overriddenFunction.uniqueId } + inheritedVtableSlots + filteredNewVtableSlots.sortedBy { it.overriddenFunction.uniqueName } } fun vtableIndex(function: IrSimpleFunction): Int { @@ -346,21 +344,16 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va return index } - val methodTableEntries: List by lazy { - irClass.sortedOverridableOrOverridingMethods - .flatMap { method -> method.allOverriddenFunctions.map { OverriddenFunctionInfo(method, it) } } - .filter { it.canBeCalledVirtually } - .distinctBy { it.overriddenFunction.uniqueId } - .sortedBy { it.overriddenFunction.uniqueId } - // TODO: probably method table should contain all accessible methods to improve binary compatibility - } + fun overridingOf(function: IrSimpleFunction) = + irClass.overridableOrOverridingMethods.firstOrNull { function in it.allOverriddenFunctions }?.let { + OverriddenFunctionInfo(it, function).getImplementation(context) + } - val interfaceTableEntries: List by lazy { - irClass.sortedOverridableOrOverridingMethods - .filter { f -> - f.isReal || f.overriddenSymbols.any { OverriddenFunctionInfo(f, it.owner).needBridge } - } - .toList() + val interfaceVTableEntries: List by lazy { + require(irClass.isInterface) + irClass.overridableOrOverridingMethods + .filter { f -> f.isReal || f.overriddenSymbols.any { OverriddenFunctionInfo(f, it.owner).needBridge } } + .sortedBy { it.uniqueName } } data class InterfaceTablePlace(val interfaceId: Int, val itableSize: Int, val methodIndex: Int) { @@ -369,12 +362,30 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va } } + val classId: Int get() = when { + irClass.isKotlinObjCClass() -> 0 + irClass.isInterface -> { + if (context.ghaEnabled()) { + hierarchyInfo.interfaceId + } else { + localHash(irClass.fqNameForIrSerialization.asString().toByteArray()).toInt() + } + } + else -> { + if (context.ghaEnabled()) { + hierarchyInfo.classIdLo + } else { + 0 + } + } + } + fun itablePlace(function: IrSimpleFunction): InterfaceTablePlace { - assert (irClass.isInterface) { "An interface expected but was ${irClass.name}" } - val itable = interfaceTableEntries - val index = itable.indexOf(function) + require(irClass.isInterface) { "An interface expected but was ${irClass.name}" } + val interfaceVTable = interfaceVTableEntries + val index = interfaceVTable.indexOf(function) if (index >= 0) - return InterfaceTablePlace(hierarchyInfo.interfaceId, itable.size, index) + return InterfaceTablePlace(classId, interfaceVTable.size, index) val superFunction = function.overriddenSymbols.first().owner return context.getLayoutBuilder(superFunction.parentAsClass).itablePlace(superFunction) } @@ -454,13 +465,8 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, va return fields.sortedByDescending{ LLVMStoreSizeOfType(context.llvm.runtime.targetData, it.type.llvmType(context)) } } - private val IrClass.sortedOverridableOrOverridingMethods: List - get() = - this.simpleFunctions() - .filter { it.isOverridableOrOverrides && it.bridgeTarget == null } - .sortedBy { it.uniqueId } + private val IrClass.overridableOrOverridingMethods: List + get() = this.simpleFunctions().filter { it.isOverridableOrOverrides && it.bridgeTarget == null } - private val functionIds = mutableMapOf() - - private val IrFunction.uniqueId get() = functionIds.getOrPut(this) { computeFunctionName().localHash.value } + private val IrFunction.uniqueName get() = computeFunctionName() } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index 15d6e5b5d80..33dd364403f 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -987,8 +987,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, } fun lookupInterfaceTableRecord(typeInfo: LLVMValueRef, interfaceId: Int): LLVMValueRef { - val interfaceTableSize = load(structGep(typeInfo, 11 /* interfaceTableSize_ */)) - val interfaceTable = load(structGep(typeInfo, 12 /* interfaceTable_ */)) + val interfaceTableSize = load(structGep(typeInfo, 9 /* interfaceTableSize_ */)) + val interfaceTable = load(structGep(typeInfo, 10 /* interfaceTable_ */)) fun fastPath(): LLVMValueRef { // The fastest optimistic version. @@ -997,7 +997,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, } // See details in ClassLayoutBuilder. - return if (context.globalHierarchyAnalysisResult.bitsPerColor <= ClassGlobalHierarchyInfo.MAX_BITS_PER_COLOR + return if (context.ghaEnabled() + && context.globalHierarchyAnalysisResult.bitsPerColor <= ClassGlobalHierarchyInfo.MAX_BITS_PER_COLOR && context.config.produce != CompilerOutputKind.FRAMEWORK) { // All interface tables are small and no unknown interface inheritance. fastPath() @@ -1042,7 +1043,6 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, */ val anyMethod = (irFunction as IrSimpleFunction).findOverriddenMethodOfAny() val owner = (anyMethod ?: irFunction).parentAsClass - val methodHash = codegen.functionHash(irFunction) val llvmMethod = when { !owner.isInterface -> { @@ -1054,8 +1054,6 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, load(slot) } - !context.ghaEnabled() -> call(context.llvm.lookupOpenMethodFunction, listOf(typeInfoPtr, methodHash)) - else -> { // Essentially: typeInfo.itable[place(interfaceId)].vtable[method] val itablePlace = context.getLayoutBuilder(owner).itablePlace(irFunction) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index e2e055d3f10..9ff4a8dd14b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -463,7 +463,6 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val zeroArrayRefsFunction = importRtFunction("ZeroArrayRefs") val enterFrameFunction = importRtFunction("EnterFrame") val leaveFrameFunction = importRtFunction("LeaveFrame") - val lookupOpenMethodFunction = importRtFunction("LookupOpenMethod") val lookupInterfaceTableRecord = importRtFunction("LookupInterfaceTableRecord") val isInstanceFunction = importRtFunction("IsInstance") val isInstanceOfClassFastFunction = importRtFunction("IsInstanceOfClassFast") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index fc4b3cceefc..4d86dac810e 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -82,9 +82,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { return result } - 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) @@ -97,8 +94,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { objOffsetsCount: Int, interfaces: ConstValue, interfacesCount: Int, - methods: ConstValue, - methodsCount: Int, interfaceTableSize: Int, interfaceTable: ConstValue, packageName: String?, @@ -130,9 +125,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { interfaces, Int32(interfacesCount), - methods, - Int32(methodsCount), - Int32(interfaceTableSize), interfaceTable, @@ -205,20 +197,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { return LLVMStoreSizeOfType(llvmTargetData, classType).toInt() } - private fun getClassId(irClass: IrClass): Int { - if (irClass.isKotlinObjCClass()) return 0 - val hierarchyInfo = if (context.ghaEnabled()) { - context.getLayoutBuilder(irClass).hierarchyInfo - } else { - ClassGlobalHierarchyInfo.DUMMY - } - return if (irClass.isInterface) { - hierarchyInfo.interfaceId - } else { - hierarchyInfo.classIdLo - } - } - fun generate(irClass: IrClass) { val className = irClass.fqNameForIrSerialization @@ -254,16 +232,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { objOffsets.size } - val methods = if (irClass.isAbstract()) { - emptyList() - } else { - methodTableRecords(irClass) - } - val methodsPtr = staticData.placeGlobalConstArray("kmethods:$className", - runtime.methodTableRecordType, methods) - - val needInterfaceTable = context.ghaEnabled() && !irClass.isInterface - && !irClass.isAbstract() && !irClass.isObjCClass() + val needInterfaceTable = !irClass.isInterface && !irClass.isAbstract() && !irClass.isObjCClass() val (interfaceTable, interfaceTableSize) = if (needInterfaceTable) { interfaceTableRecords(irClass) } else { @@ -281,12 +250,11 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { superType, objOffsetsPtr, objOffsetsCount, interfacesPtr, interfaces.size, - methodsPtr, methods.size, interfaceTableSize, interfaceTablePtr, reflectionInfo.packageName, reflectionInfo.relativeName, flagsFromClass(irClass), - getClassId(irClass), + context.getLayoutBuilder(irClass).classId, llvmDeclarations.writableTypeInfoGlobal?.pointer, associatedObjects = genAssociatedObjects(irClass) ) @@ -326,25 +294,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { return ConstArray(int8TypePtr, vtableEntries) } - fun methodTableRecords(irClass: IrClass): List { - val functionNames = mutableMapOf() - return context.getLayoutBuilder(irClass).methodTableEntries.map { - val functionName = it.overriddenFunction.computeFunctionName() - val nameSignature = functionName.localHash - val previous = functionNames.putIfAbsent(nameSignature.value, it) - if (previous != null) - throw AssertionError("Duplicate method table entry: functionName = '$functionName', hash = '${nameSignature.value}', entry1 = $previous, entry2 = $it") - - // TODO: compile-time resolution limits binary compatibility. - val implementation = it.implementation - val methodEntryPoint = - if (implementation == null || context.referencedFunctions?.contains(implementation) == false) - null - else implementation.entryPointAddress - MethodTableRecord(nameSignature, methodEntryPoint) - }.sortedBy { it.nameSignature.value } - } - fun interfaceTableRecords(irClass: IrClass): Pair, Int> { // The details are in ClassLayoutBuilder. val interfaces = irClass.implementedInterfaces @@ -356,7 +305,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { private fun interfaceTableSkeleton(interfaces: List): Pair, Int> { val interfaceLayouts = interfaces.map { context.getLayoutBuilder(it) } - val interfaceColors = interfaceLayouts.map { it.hierarchyInfo.interfaceColor } + val interfaceIds = interfaceLayouts.map { it.classId } // Find the optimal size. It must be a power of 2. var size = 1 @@ -367,8 +316,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { used[i] = false // Check for collisions. var ok = true - for (color in interfaceColors) { - val index = color % size + for (id in interfaceIds) { + val index = id and (size - 1) // This is not an optimization but rather for not to bother with negative numbers. if (used[index]) { ok = false break @@ -378,17 +327,25 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { if (ok) break size *= 2 } - val conservative = size > maxSize + val useFastITable = size <= maxSize - val interfaceTableSkeleton = if (conservative) { + val interfaceTableSkeleton = if (useFastITable) { + arrayOfNulls(size).also { + for (interfaceLayout in interfaceLayouts) + it[interfaceLayout.classId and (size - 1)] = interfaceLayout + } + } else { size = interfaceLayouts.size - interfaceLayouts.sortedBy { it.hierarchyInfo.interfaceId }.toTypedArray() - } else arrayOfNulls(size).also { - for (interfaceLayout in interfaceLayouts) - it[interfaceLayout.hierarchyInfo.interfaceId % size] = interfaceLayout + val sortedInterfaceLayouts = interfaceLayouts.sortedBy { it.classId }.toTypedArray() + for (i in 1 until sortedInterfaceLayouts.size) + require(sortedInterfaceLayouts[i - 1].classId != sortedInterfaceLayouts[i].classId) { + "Different interfaces ${sortedInterfaceLayouts[i - 1].irClass.render()} and ${sortedInterfaceLayouts[i].irClass.render()}" + + " have same class id: ${sortedInterfaceLayouts[i].classId}" + } + sortedInterfaceLayouts } - val interfaceTableSize = if (conservative) -size else (size - 1) + val interfaceTableSize = if (useFastITable) (size - 1) else -size return Pair(interfaceTableSkeleton, interfaceTableSize) } @@ -396,22 +353,19 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { irClass: IrClass, interfaceTableSkeleton: Array ): List { - val methodTableEntries = context.getLayoutBuilder(irClass).methodTableEntries + val layoutBuilder = context.getLayoutBuilder(irClass) val className = irClass.fqNameForIrSerialization return interfaceTableSkeleton.map { iface -> - val interfaceId = iface?.hierarchyInfo?.interfaceId ?: 0 + val interfaceId = iface?.classId ?: 0 InterfaceTableRecord( Int32(interfaceId), - Int32(iface?.interfaceTableEntries?.size ?: 0), + Int32(iface?.interfaceVTableEntries?.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 + val vtableEntries = iface.interfaceVTableEntries.map { ifaceFunction -> + val impl = layoutBuilder.overridingOf(ifaceFunction) if (impl == null || context.referencedFunctions?.contains(impl) == false) NullPointer(int8Type) else impl.entryPointAddress @@ -542,15 +496,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { val objOffsetsPtr = staticData.placeGlobalConstArray("", int32Type, objOffsets) val objOffsetsCount = objOffsets.size - val methods = (methodTableRecords(superClass) + methodImpls.map { (method, impl) -> - assert(method.parent == irClass) - MethodTableRecord(method.computeFunctionName().localHash, impl.bitcast(int8TypePtr)) - }).sortedBy { it.nameSignature.value }.also { - assert(it.distinctBy { it.nameSignature.value } == it) - } - - val methodsPtr = staticData.placeGlobalConstArray("", runtime.methodTableRecordType, methods) - val reflectionInfo = ReflectionInfo(null, null) val writableTypeInfoType = runtime.writableTypeInfoType @@ -568,21 +513,20 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { val typeHierarchyInfo = if (!context.ghaEnabled()) ClassGlobalHierarchyInfo.DUMMY else - ClassGlobalHierarchyInfo(-1, -1, 0, 0) + ClassGlobalHierarchyInfo(-1, -1, 0) // TODO: interfaces (e.g. FunctionN and Function) should have different colors. - val (interfaceTableSkeleton, interfaceTableSize) = - if (context.ghaEnabled()) interfaceTableSkeleton(interfaces) else Pair(emptyArray(), -1) + val (interfaceTableSkeleton, interfaceTableSize) = interfaceTableSkeleton(interfaces) val interfaceTable = interfaceTableSkeleton.map { layoutBuilder -> if (layoutBuilder == null) { InterfaceTableRecord(Int32(0), Int32(0), null) } else { - val vtableEntries = layoutBuilder.interfaceTableEntries.map { methodImpls[it]!!.bitcast(int8TypePtr) } + val vtableEntries = layoutBuilder.interfaceVTableEntries.map { methodImpls[it]!!.bitcast(int8TypePtr) } val interfaceVTable = staticData.placeGlobalArray("", kInt8Ptr, vtableEntries) InterfaceTableRecord( - Int32(layoutBuilder.hierarchyInfo.interfaceId), - Int32(layoutBuilder.interfaceTableEntries.size), + Int32(layoutBuilder.classId), + Int32(layoutBuilder.interfaceVTableEntries.size), interfaceVTable.pointer.getElementPtr(0) ) } @@ -596,7 +540,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { superType = superClass.typeInfoPtr, objOffsets = objOffsetsPtr, objOffsetsCount = objOffsetsCount, interfaces = interfacesPtr, interfacesCount = interfaces.size, - methods = methodsPtr, methodsCount = methods.size, interfaceTableSize = interfaceTableSize, interfaceTable = interfaceTablePtr, packageName = reflectionInfo.packageName, relativeName = reflectionInfo.relativeName, diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt index 5d2024cab81..5a04d3f66e3 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt @@ -26,7 +26,6 @@ class Runtime(bitcodeFile: String) { val typeInfoType = getStructType("TypeInfo") val extendedTypeInfoType = getStructType("ExtendedTypeInfo") val writableTypeInfoType = getStructTypeOrNull("WritableTypeInfo") - val methodTableRecordType = getStructType("MethodTableRecord") val interfaceTableRecordType = getStructType("InterfaceTableRecord") val associatedObjectTableRecordType = getStructType("AssociatedObjectTableRecord") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 9417a4d45c5..ffffadc5897 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -438,14 +438,12 @@ 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.itableSize), Int32(itablePlace.methodIndex), @@ -458,7 +456,6 @@ internal class ObjCExportCodeGenerator( typeInfo: ConstPointer?, vtable: ConstPointer?, vtableSize: Int, - methodTable: List, itable: List, itableSize: Int, val objCName: String, @@ -473,9 +470,6 @@ internal class ObjCExportCodeGenerator( vtable, Int32(vtableSize), - staticData.placeGlobalConstArray("", runtime.methodTableRecordType, methodTable), - Int32(methodTable.size), - staticData.placeGlobalConstArray("", runtime.interfaceTableRecordType, itable), Int32(itableSize), @@ -1147,12 +1141,10 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( private fun ObjCExportCodeGenerator.createReverseAdapter( irFunction: IrFunction, baseMethod: ObjCMethodSpec.BaseMethod, - functionName: String, vtableIndex: Int?, itablePlace: ClassLayoutBuilder.InterfaceTablePlace? ): ObjCExportCodeGenerator.KotlinToObjCMethodAdapter { - val nameSignature = functionName.localHash.value val selector = baseMethod.selector val kotlinToObjC = generateKotlinToObjCBridge( @@ -1160,7 +1152,7 @@ private fun ObjCExportCodeGenerator.createReverseAdapter( baseMethod ).bitcast(int8TypePtr) - return KotlinToObjCMethodAdapter(selector, nameSignature, + return KotlinToObjCMethodAdapter(selector, itablePlace ?: ClassLayoutBuilder.InterfaceTablePlace.INVALID, vtableIndex ?: -1, kotlinToObjC) @@ -1230,8 +1222,9 @@ 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.ghaEnabled() - && (irFunction.isReal || irFunction.resolveFakeOverrideMaybeAbstract().parent != context.irBuiltIns.anyClass.owner)) { + return if (irClass.isInterface + && (irFunction.isReal || irFunction.resolveFakeOverrideMaybeAbstract().parent != context.irBuiltIns.anyClass.owner) + ) { context.getLayoutBuilder(irClass).itablePlace(irFunction) } else { null @@ -1250,7 +1243,6 @@ private fun ObjCExportCodeGenerator.createTypeAdapterForFileClass( typeInfo = null, vtable = null, vtableSize = -1, - methodTable = emptyList(), itable = emptyList(), itableSize = -1, objCName = name, @@ -1333,15 +1325,9 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( null } - val methodTable = if (!irClass.isInterface && irClass.isAbstract()) { - rttiGenerator.methodTableRecords(irClass) - } else { - emptyList() - } - val (itable, itableSize) = when { - irClass.isInterface -> Pair(emptyList(), context.getLayoutBuilder(irClass).interfaceTableEntries.size) - irClass.isAbstract() && context.ghaEnabled() -> rttiGenerator.interfaceTableRecords(irClass) + irClass.isInterface -> Pair(emptyList(), context.getLayoutBuilder(irClass).interfaceVTableEntries.size) + irClass.isAbstract() -> rttiGenerator.interfaceTableRecords(irClass) else -> Pair(emptyList(), -1) } @@ -1350,7 +1336,6 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( typeInfo, vtable, vtableSize, - methodTable, itable, itableSize, objCName, @@ -1445,7 +1430,7 @@ private fun ObjCExportCodeGenerator.createReverseAdapters( presentVtableBridges += vtableIndex presentMethodTableBridges += functionName presentItableBridges += itablePlace - result += createReverseAdapter(it, baseMethod, functionName, vtableIndex, itablePlace) + result += createReverseAdapter(it, baseMethod, vtableIndex, itablePlace) coveredMethods += it } } @@ -1466,7 +1451,6 @@ private fun ObjCExportCodeGenerator.nonOverridableAdapter( hasSelectorAmbiguity: Boolean ): ObjCExportCodeGenerator.KotlinToObjCMethodAdapter = KotlinToObjCMethodAdapter( selector, - -1, vtableIndex = if (hasSelectorAmbiguity) -2 else -1, // Describes the reason. kotlinImpl = NullPointer(int8Type), itablePlace = ClassLayoutBuilder.InterfaceTablePlace.INVALID diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt index 2c8485ff1d8..219c99de237 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt @@ -182,7 +182,7 @@ internal class CallGraphBuilder( receiverType.vtable[call.calleeVtableIndex] is DataFlowIR.Node.ItableCall -> - receiverType.itable[call.calleeHash]!! + receiverType.itable[call.interfaceId]!![call.calleeItableIndex] else -> error("Unreachable") } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt index abe1a7f1f58..b43e75fb17b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -785,18 +785,24 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag symbolTable.mapClassReferenceType(owner) } } - if (owner.isInterface) { - val calleeHash = callee.computeFunctionName().localHash.value + + val isAnyMethod = callee.target.parentAsClass.isAny() + if (owner.isInterface && !isAnyMethod) { + val itablePlace = context.getLayoutBuilder(owner).itablePlace(callee) DataFlowIR.Node.ItableCall( symbolTable.mapFunction(callee.target), receiverType, - calleeHash, + itablePlace.interfaceId, + itablePlace.methodIndex, arguments, mapReturnType(value.type, callee.target.returnType), value ) } else { - val vtableIndex = context.getLayoutBuilder(owner).vtableIndex(callee) + val vtableIndex = if (isAnyMethod) + context.getLayoutBuilder(context.irBuiltIns.anyClass.owner).vtableIndex(callee.target) + else + context.getLayoutBuilder(owner).vtableIndex(callee) DataFlowIR.Node.VtableCall( symbolTable.mapFunction(callee.target), receiverType, diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt index ac6f9c5b9cb..4a82d86575c 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt @@ -6,9 +6,10 @@ package org.jetbrains.kotlin.backend.konan.optimizations import org.jetbrains.kotlin.backend.konan.* -import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract -import org.jetbrains.kotlin.backend.konan.descriptors.isBuiltInOperator import org.jetbrains.kotlin.backend.common.ir.allParameters +import org.jetbrains.kotlin.backend.konan.descriptors.* +import org.jetbrains.kotlin.backend.konan.descriptors.OverriddenFunctionInfo +import org.jetbrains.kotlin.backend.konan.descriptors.implementedInterfaces import org.jetbrains.kotlin.backend.konan.ir.isOverridableOrOverrides import org.jetbrains.kotlin.backend.konan.llvm.computeFunctionName import org.jetbrains.kotlin.backend.konan.llvm.computeSymbolName @@ -64,7 +65,7 @@ internal object DataFlowIR { : Type(isFinal, isAbstract, primitiveBinaryType, name) { val superTypes = mutableListOf() val vtable = mutableListOf() - val itable = mutableMapOf() + val itable = mutableMapOf>() } class Public(val hash: Long, index: Int, isFinal: Boolean, isAbstract: Boolean, primitiveBinaryType: PrimitiveBinaryType?, @@ -244,7 +245,7 @@ internal object DataFlowIR { arguments: List, returnType: Type, irCallSite: IrCall?) : VirtualCall(callee, arguments, receiverType, returnType, irCallSite) - class ItableCall(callee: FunctionSymbol, receiverType: Type, val calleeHash: Long, + class ItableCall(callee: FunctionSymbol, receiverType: Type, val interfaceId: Int, val calleeItableIndex: Int, arguments: List, returnType: Type, irCallSite: IrCall?) : VirtualCall(callee, arguments, receiverType, returnType, irCallSite) @@ -342,7 +343,7 @@ internal object DataFlowIR { is Node.ItableCall -> buildString { appendLine(" INTERFACE CALL ${node.callee}. Return type = ${node.returnType}") appendLine(" RECEIVER: ${node.receiverType}") - append(" METHOD HASH: ${node.calleeHash}") + append(" INTERFACE ID: ${node.interfaceId}. ITABLE INDEX: ${node.calleeItableIndex}") appendList(node.arguments) { append(" ARG #${ids[it.node]!!}") appendCastTo(it.castToType) @@ -505,12 +506,13 @@ internal object DataFlowIR { type.vtable += layoutBuilder.vtableEntries.map { mapFunction(it.getImplementation(context)!!) } - layoutBuilder.methodTableEntries.forEach { - type.itable[it.overriddenFunction.computeFunctionName().localHash.value] = mapFunction(it.getImplementation(context)!!) + val interfaces = irClass.implementedInterfaces.map { context.getLayoutBuilder(it) } + for (iface in interfaces) { + type.itable[iface.classId] = iface.interfaceVTableEntries.map { mapFunction(layoutBuilder.overridingOf(it)!!) } } } else if (irClass.isInterface) { // Warmup interface table so it is computed before DCE. - context.getLayoutBuilder(irClass).interfaceTableEntries + context.getLayoutBuilder(irClass).interfaceVTableEntries } return type } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt index c8c4958c416..76a003c9ef7 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt @@ -79,7 +79,7 @@ internal object Devirtualization { } + moduleDFG.symbolTable.classMap.values .filterIsInstance() - .flatMap { it.vtable + it.itable.values } + .flatMap { it.vtable + it.itable.values.flatten() } .filterIsInstance() .filter { moduleDFG.functions.containsKey(it) } } @@ -263,7 +263,7 @@ internal object Devirtualization { vtable[callSite.calleeVtableIndex] is DataFlowIR.Node.ItableCall -> - itable[callSite.calleeHash]!! + itable[callSite.interfaceId]!![callSite.calleeItableIndex] else -> error("Unreachable") } diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 1bfebe47acd..5ab71a6c38d 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -2616,6 +2616,10 @@ task interfaceCallsNCasts_conservativeItable(type: KonanLocalTest) { source = "codegen/interfaceCallsNCasts/conservativeItable.kt" } +task interfaceCallsNCasts_functionNameClash(type: KonanLocalTest) { + source = "codegen/interfaceCallsNCasts/functionNameClash.kt" +} + standaloneTest("multiargs") { arguments = ["AAA", "BB", "C"] multiRuns = true diff --git a/kotlin-native/backend.native/tests/codegen/interfaceCallsNCasts/functionNameClash.kt b/kotlin-native/backend.native/tests/codegen/interfaceCallsNCasts/functionNameClash.kt new file mode 100644 index 00000000000..1fd467f63d8 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/interfaceCallsNCasts/functionNameClash.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package codegen.interfaceCallsNCasts.functionNameClash + +import kotlin.test.* + +interface I1 { + fun foo(x: T): String +} +interface I2 { + fun foo(x: T): String +} +class C : I1, I2 { + override fun foo(x: String) = "I1.foo($x)" + override fun foo(x: Int) = "I2.foo($x)" +} + +@Test +fun runTest() { + val c = C() + val i1: I1 = c + assertEquals("I1.foo(str)", i1.foo("str")) + val i2: I2 = c + assertEquals("I2.foo(42)", i2.foo(42)) +} \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/cpp/ObjCExport.mm b/kotlin-native/runtime/src/main/cpp/ObjCExport.mm index aca91e2a5ac..58f0ecafe44 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCExport.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCExport.mm @@ -52,7 +52,6 @@ struct ObjCToKotlinMethodAdapter { struct KotlinToObjCMethodAdapter { const char* selector; - MethodNameHash nameSignature; ClassId interfaceId; int itableSize; int itableIndex; @@ -66,9 +65,6 @@ struct ObjCTypeAdapter { const void * const * kotlinVtable; int kotlinVtableSize; - const MethodTableRecord* kotlinMethodTable; - int kotlinMethodTableSize; - const InterfaceTableRecord* kotlinItable; int kotlinItableSize; @@ -652,7 +648,6 @@ static const TypeInfo* createTypeInfo( const TypeInfo* superType, const KStdVector& superInterfaces, const KStdVector& vtable, - const KStdVector& methodTable, const KStdOrderedMap>& interfaceVTables, const InterfaceTableRecord* superItable, int superItableSize, @@ -707,12 +702,6 @@ static const TypeInfo* createTypeInfo( } } - MethodTableRecord* openMethods_ = konanAllocArray(methodTable.size()); - for (size_t i = 0; i < methodTable.size(); ++i) openMethods_[i] = methodTable[i]; - - result->openMethods_ = openMethods_; - result->openMethodsCount_ = methodTable.size(); - result->packageName_ = nullptr; result->relativeName_ = nullptr; // TODO: add some info. result->writableInfo_ = (WritableTypeInfo*)konanAllocMemory(sizeof(WritableTypeInfo)); @@ -777,22 +766,6 @@ static int getVtableSize(const TypeInfo* typeInfo) { return -1; } -static void insertOrReplace(KStdVector& methodTable, MethodNameHash nameSignature, void* entryPoint) { - MethodTableRecord record = {nameSignature, entryPoint}; - - for (int i = methodTable.size() - 1; i >= 0; --i) { - if (methodTable[i].nameSignature_ == nameSignature) { - methodTable[i].methodEntryPoint_ = entryPoint; - return; - } else if (methodTable[i].nameSignature_ < nameSignature) { - methodTable.insert(methodTable.begin() + (i + 1), record); - return; - } - } - - methodTable.insert(methodTable.begin(), record); -} - static void throwIfCantBeOverridden(Class clazz, const KotlinToObjCMethodAdapter* adapter) { if (adapter->kotlinImpl == nullptr) { NSString* reason; @@ -816,9 +789,6 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType, co const void * const * superVtable = nullptr; int superVtableSize = getVtableSize(superType); - const MethodTableRecord* superMethodTable = nullptr; - int superMethodTableSize = 0; - InterfaceTableRecord const* superITable = nullptr; int superITableSize = 0; @@ -828,27 +798,17 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType, co // And if it is abstract, then vtable and method table are not available from TypeInfo, // but present in type adapter instead: superVtable = superTypeAdapter->kotlinVtable; - superMethodTable = superTypeAdapter->kotlinMethodTable; - superMethodTableSize = superTypeAdapter->kotlinMethodTableSize; superITable = superTypeAdapter->kotlinItable; superITableSize = superTypeAdapter->kotlinItableSize; } if (superVtable == nullptr) superVtable = superType->vtable(); - if (superMethodTable == nullptr) { - superMethodTable = superType->openMethods_; - superMethodTableSize = superType->openMethodsCount_; - } KStdVector vtable( superVtable, superVtable + superVtableSize ); - KStdVector methodTable( - superMethodTable, superMethodTable + superMethodTableSize - ); - if (superITable == nullptr) { superITable = superType->interfaceTable_; superITableSize = superType->interfaceTableSize_; @@ -915,7 +875,6 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType, co throwIfCantBeOverridden(clazz, adapter); itableEqualsSuper = false; - insertOrReplace(methodTable, adapter->nameSignature, const_cast(adapter->kotlinImpl)); if (adapter->vtableIndex != -1) vtable[adapter->vtableIndex] = adapter->kotlinImpl; if (adapter->itableIndex != -1 && superITable != nullptr) @@ -940,7 +899,6 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType, co const KotlinToObjCMethodAdapter* adapter = &typeAdapter->reverseAdapters[i]; throwIfCantBeOverridden(clazz, adapter); - insertOrReplace(methodTable, adapter->nameSignature, const_cast(adapter->kotlinImpl)); RuntimeAssert(adapter->vtableIndex == -1, ""); if (adapter->itableIndex != -1 && superITable != nullptr) { @@ -954,9 +912,8 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType, co // TODO: consider forbidding the class being abstract. - const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, methodTable, - interfaceVTables, superITable, superITableSize, itableEqualsSuper, - fieldsInfo); + const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, interfaceVTables, + superITable, superITableSize, itableEqualsSuper, fieldsInfo); // TODO: it will probably never be requested, since such a class can't be instantiated in Kotlin. result->writableInfo_->objCExport.objCClass = clazz; diff --git a/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp b/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp index 3e226a0add6..a46f3dc5c53 100644 --- a/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp +++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp @@ -17,44 +17,7 @@ #include "KAssert.h" #include "TypeInfo.h" -// 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 1 - extern "C" { -#if USE_BINARY_SEARCH - -void* LookupOpenMethod(const TypeInfo* info, MethodNameHash nameSignature) { - int bottom = 0; - int top = info->openMethodsCount_ - 1; - - while (bottom <= top) { - int middle = (bottom + top) / 2; - if (info->openMethods_[middle].nameSignature_ < nameSignature) - bottom = middle + 1; - else if (info->openMethods_[middle].nameSignature_ == nameSignature) - return info->openMethods_[middle].methodEntryPoint_; - else - top = middle - 1; - } - - RuntimeAssert(false, "Unknown open method"); - return nullptr; -} - -#else - -void* LookupOpenMethod(const TypeInfo* info, MethodNameHash nameSignature) { - for (int i = 0; i < info->openMethodsCount_; ++i) { - if (info->openMethods_[i].nameSignature_ == nameSignature) { - return info->openMethods_[i].methodEntryPoint_; - } - } - RuntimeAssert(false, "Unknown open method"); - return nullptr; -} - -#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. diff --git a/kotlin-native/runtime/src/main/cpp/TypeInfo.h b/kotlin-native/runtime/src/main/cpp/TypeInfo.h index 118c23c669c..a974e6bd133 100644 --- a/kotlin-native/runtime/src/main/cpp/TypeInfo.h +++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.h @@ -29,17 +29,6 @@ struct WritableTypeInfo; struct ObjHeader; struct AssociatedObjectTableRecord; -// Hash of open method name. Must be unique per class/scope (CityHash64 is being used). -typedef int64_t MethodNameHash; - -// An element of sorted by hash in-place array representing methods. -// For systems where introspection is not needed - only open methods are in -// this table. -struct MethodTableRecord { - MethodNameHash nameSignature_; - void* methodEntryPoint_; -}; - // Type for runtime representation of Konan object. // Keep in sync with runtimeTypeMap in RTTIGenerator. enum Konan_RuntimeType { @@ -123,9 +112,6 @@ struct TypeInfo { int32_t objOffsetsCount_; const TypeInfo* const* implementedInterfaces_; int32_t implementedInterfacesCount_; - // Null for abstract classes and interfaces. - const MethodTableRecord* openMethods_; - uint32_t openMethodsCount_; int32_t interfaceTableSize_; InterfaceTableRecord const* interfaceTable_; @@ -181,14 +167,6 @@ struct TypeInfo { #ifdef __cplusplus extern "C" { #endif -// Find open method by its hash. Other methods are resolved in compile-time. -// 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). -void* LookupOpenMethod(const TypeInfo* info, MethodNameHash nameSignature) RUNTIME_CONST; - InterfaceTableRecord const* LookupInterfaceTableRecord(InterfaceTableRecord const* interfaceTable, int interfaceTableSize, ClassId interfaceId) RUNTIME_CONST;