Revert "[K/N][codegen] Refactored interface calls"

This reverts commit 3b3318ab06e8c55b1c49da5734decd59524c3d9f.
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 50fb765..4dae07c4 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,9 +85,10 @@
     }
 }
 
-internal class ClassGlobalHierarchyInfo(val classIdLo: Int, val classIdHi: Int, val interfaceId: Int) {
+internal class ClassGlobalHierarchyInfo(val classIdLo: Int, val classIdHi: Int,
+                                        val interfaceId: Int, val interfaceColor: Int) {
     companion object {
-        val DUMMY = ClassGlobalHierarchyInfo(0, 0, 0)
+        val DUMMY = ClassGlobalHierarchyInfo(0, 0, 0, 0)
 
         // 32-items table seems like a good threshold.
         val MAX_BITS_PER_COLOR = 5
@@ -159,7 +160,8 @@
                         "Unable to assign interface id to ${declaration.name}"
                     }
                     context.getLayoutBuilder(declaration).hierarchyInfo =
-                            ClassGlobalHierarchyInfo(0, 0, color or (interfaceId shl bitsPerColor))
+                            ClassGlobalHierarchyInfo(0, 0,
+                                    color or (interfaceId shl bitsPerColor), color)
                 } else {
                     allClasses += declaration
                     if (declaration != root) {
@@ -179,7 +181,7 @@
             val enterTime = if (irClass == root) -1 else time
             immediateInheritors[irClass]?.forEach { dfs(it) }
             val exitTime = time
-            context.getLayoutBuilder(irClass).hierarchyInfo = ClassGlobalHierarchyInfo(enterTime, exitTime, 0)
+            context.getLayoutBuilder(irClass).hierarchyInfo = ClassGlobalHierarchyInfo(enterTime, exitTime, 0, 0)
         }
 
         dfs(root)
@@ -261,7 +263,7 @@
 
 internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context, val isLowered: Boolean) {
     val vtableEntries: List<OverriddenFunctionInfo> by lazy {
-        require(!irClass.isInterface)
+        assert(!irClass.isInterface)
 
         context.logMultiple {
             +""
@@ -275,7 +277,7 @@
             context.getLayoutBuilder(superClass).vtableEntries
         }
 
-        val methods = irClass.overridableOrOverridingMethods
+        val methods = irClass.sortedOverridableOrOverridingMethods
         val newVtableSlots = mutableListOf<OverriddenFunctionInfo>()
         val overridenVtableSlots = mutableMapOf<IrSimpleFunction, OverriddenFunctionInfo>()
 
@@ -334,7 +336,7 @@
             +"DONE vTable for ${irClass.render()}"
         }
 
-        inheritedVtableSlots + filteredNewVtableSlots.sortedBy { it.overriddenFunction.uniqueName }
+        inheritedVtableSlots + filteredNewVtableSlots.sortedBy { it.overriddenFunction.uniqueId }
     }
 
     fun vtableIndex(function: IrSimpleFunction): Int {
@@ -344,16 +346,21 @@
         return index
     }
 
-    fun overridingOf(function: IrSimpleFunction) =
-            irClass.overridableOrOverridingMethods.firstOrNull { function in it.allOverriddenFunctions }?.let {
-                OverriddenFunctionInfo(it, function).getImplementation(context)
-            }
+    val methodTableEntries: List<OverriddenFunctionInfo> 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
+    }
 
-    val interfaceVTableEntries: List<IrSimpleFunction> by lazy {
-        require(irClass.isInterface)
-        irClass.overridableOrOverridingMethods
-                .filter { f -> f.isReal || f.overriddenSymbols.any { OverriddenFunctionInfo(f, it.owner).needBridge } }
-                .sortedBy { it.uniqueName }
+    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 itableSize: Int, val methodIndex: Int) {
@@ -362,30 +369,12 @@
         }
     }
 
-    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 {
-        require(irClass.isInterface) { "An interface expected but was ${irClass.name}" }
-        val interfaceVTable = interfaceVTableEntries
-        val index = interfaceVTable.indexOf(function)
+        assert (irClass.isInterface) { "An interface expected but was ${irClass.name}" }
+        val itable = interfaceTableEntries
+        val index = itable.indexOf(function)
         if (index >= 0)
-            return InterfaceTablePlace(classId, interfaceVTable.size, index)
+            return InterfaceTablePlace(hierarchyInfo.interfaceId, itable.size, index)
         val superFunction = function.overriddenSymbols.first().owner
         return context.getLayoutBuilder(superFunction.parentAsClass).itablePlace(superFunction)
     }
@@ -465,8 +454,13 @@
         return fields.sortedByDescending{ LLVMStoreSizeOfType(context.llvm.runtime.targetData, it.type.llvmType(context)) }
     }
 
-    private val IrClass.overridableOrOverridingMethods: List<IrSimpleFunction>
-        get() = this.simpleFunctions().filter { it.isOverridableOrOverrides && it.bridgeTarget == null }
+    private val IrClass.sortedOverridableOrOverridingMethods: List<IrSimpleFunction>
+        get() =
+            this.simpleFunctions()
+                    .filter { it.isOverridableOrOverrides && it.bridgeTarget == null }
+                    .sortedBy { it.uniqueId }
 
-    private val IrFunction.uniqueName get() = computeFunctionName()
+    private val functionIds = mutableMapOf<IrFunction, Long>()
+
+    private val IrFunction.uniqueId get() = functionIds.getOrPut(this) { computeFunctionName().localHash.value }
 }
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 a779f10..54cc5dc 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
@@ -1038,8 +1038,8 @@
     }
 
     fun lookupInterfaceTableRecord(typeInfo: LLVMValueRef, interfaceId: Int): LLVMValueRef {
-        val interfaceTableSize = load(structGep(typeInfo, 9 /* interfaceTableSize_ */))
-        val interfaceTable = load(structGep(typeInfo, 10 /* interfaceTable_ */))
+        val interfaceTableSize = load(structGep(typeInfo, 11 /* interfaceTableSize_ */))
+        val interfaceTable = load(structGep(typeInfo, 12 /* interfaceTable_ */))
 
         fun fastPath(): LLVMValueRef {
             // The fastest optimistic version.
@@ -1048,8 +1048,7 @@
         }
 
         // See details in ClassLayoutBuilder.
-        return if (context.ghaEnabled()
-                && context.globalHierarchyAnalysisResult.bitsPerColor <= ClassGlobalHierarchyInfo.MAX_BITS_PER_COLOR
+        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()
@@ -1094,6 +1093,7 @@
          */
         val anyMethod = (irFunction as IrSimpleFunction).findOverriddenMethodOfAny()
         val owner = (anyMethod ?: irFunction).parentAsClass
+        val methodHash = codegen.functionHash(irFunction)
 
         val llvmMethod = when {
             !owner.isInterface -> {
@@ -1105,6 +1105,8 @@
                 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 9ff4a8d..e2e055d 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,6 +463,7 @@
     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 4d86dac..fc4b3cc 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,6 +82,9 @@
         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)
 
@@ -94,6 +97,8 @@
             objOffsetsCount: Int,
             interfaces: ConstValue,
             interfacesCount: Int,
+            methods: ConstValue,
+            methodsCount: Int,
             interfaceTableSize: Int,
             interfaceTable: ConstValue,
             packageName: String?,
@@ -125,6 +130,9 @@
                     interfaces,
                     Int32(interfacesCount),
 
+                    methods,
+                    Int32(methodsCount),
+
                     Int32(interfaceTableSize),
                     interfaceTable,
 
@@ -197,6 +205,20 @@
         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
@@ -232,7 +254,16 @@
             objOffsets.size
         }
 
-        val needInterfaceTable = !irClass.isInterface && !irClass.isAbstract() && !irClass.isObjCClass()
+        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 (interfaceTable, interfaceTableSize) = if (needInterfaceTable) {
             interfaceTableRecords(irClass)
         } else {
@@ -250,11 +281,12 @@
                 superType,
                 objOffsetsPtr, objOffsetsCount,
                 interfacesPtr, interfaces.size,
+                methodsPtr, methods.size,
                 interfaceTableSize, interfaceTablePtr,
                 reflectionInfo.packageName,
                 reflectionInfo.relativeName,
                 flagsFromClass(irClass),
-                context.getLayoutBuilder(irClass).classId,
+                getClassId(irClass),
                 llvmDeclarations.writableTypeInfoGlobal?.pointer,
                 associatedObjects = genAssociatedObjects(irClass)
         )
@@ -294,6 +326,25 @@
         return ConstArray(int8TypePtr, vtableEntries)
     }
 
+    fun methodTableRecords(irClass: IrClass): List<MethodTableRecord> {
+        val functionNames = mutableMapOf<Long, OverriddenFunctionInfo>()
+        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<List<InterfaceTableRecord>, Int> {
         // The details are in ClassLayoutBuilder.
         val interfaces = irClass.implementedInterfaces
@@ -305,7 +356,7 @@
 
     private fun interfaceTableSkeleton(interfaces: List<IrClass>): Pair<Array<out ClassLayoutBuilder?>, Int> {
         val interfaceLayouts = interfaces.map { context.getLayoutBuilder(it) }
-        val interfaceIds = interfaceLayouts.map { it.classId }
+        val interfaceColors = interfaceLayouts.map { it.hierarchyInfo.interfaceColor }
 
         // Find the optimal size. It must be a power of 2.
         var size = 1
@@ -316,8 +367,8 @@
                 used[i] = false
             // Check for collisions.
             var ok = true
-            for (id in interfaceIds) {
-                val index = id and (size - 1) // This is not an optimization but rather for not to bother with negative numbers.
+            for (color in interfaceColors) {
+                val index = color % size
                 if (used[index]) {
                     ok = false
                     break
@@ -327,25 +378,17 @@
             if (ok) break
             size *= 2
         }
-        val useFastITable = size <= maxSize
+        val conservative = size > maxSize
 
-        val interfaceTableSkeleton = if (useFastITable) {
-            arrayOfNulls<ClassLayoutBuilder?>(size).also {
-                for (interfaceLayout in interfaceLayouts)
-                    it[interfaceLayout.classId and (size - 1)] = interfaceLayout
-            }
-        } else {
+        val interfaceTableSkeleton = if (conservative) {
             size = interfaceLayouts.size
-            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
+            interfaceLayouts.sortedBy { it.hierarchyInfo.interfaceId }.toTypedArray()
+        } else arrayOfNulls<ClassLayoutBuilder?>(size).also {
+            for (interfaceLayout in interfaceLayouts)
+                it[interfaceLayout.hierarchyInfo.interfaceId % size] = interfaceLayout
         }
 
-        val interfaceTableSize = if (useFastITable) (size - 1) else -size
+        val interfaceTableSize = if (conservative) -size else (size - 1)
         return Pair(interfaceTableSkeleton, interfaceTableSize)
     }
 
@@ -353,19 +396,22 @@
             irClass: IrClass,
             interfaceTableSkeleton: Array<out ClassLayoutBuilder?>
     ): List<InterfaceTableRecord> {
-        val layoutBuilder = context.getLayoutBuilder(irClass)
+        val methodTableEntries = context.getLayoutBuilder(irClass).methodTableEntries
         val className = irClass.fqNameForIrSerialization
 
         return interfaceTableSkeleton.map { iface ->
-            val interfaceId = iface?.classId ?: 0
+            val interfaceId = iface?.hierarchyInfo?.interfaceId ?: 0
             InterfaceTableRecord(
                     Int32(interfaceId),
-                    Int32(iface?.interfaceVTableEntries?.size ?: 0),
+                    Int32(iface?.interfaceTableEntries?.size ?: 0),
                     if (iface == null)
                         NullPointer(kInt8Ptr)
                     else {
-                        val vtableEntries = iface.interfaceVTableEntries.map { ifaceFunction ->
-                            val impl = layoutBuilder.overridingOf(ifaceFunction)
+                        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
@@ -496,6 +542,15 @@
         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
@@ -513,20 +568,21 @@
         val typeHierarchyInfo = if (!context.ghaEnabled())
             ClassGlobalHierarchyInfo.DUMMY
         else
-            ClassGlobalHierarchyInfo(-1, -1, 0)
+            ClassGlobalHierarchyInfo(-1, -1, 0, 0)
 
         // TODO: interfaces (e.g. FunctionN and Function) should have different colors.
-        val (interfaceTableSkeleton, interfaceTableSize) = interfaceTableSkeleton(interfaces)
+        val (interfaceTableSkeleton, interfaceTableSize) =
+                if (context.ghaEnabled()) interfaceTableSkeleton(interfaces) else Pair(emptyArray(), -1)
 
         val interfaceTable = interfaceTableSkeleton.map { layoutBuilder ->
             if (layoutBuilder == null) {
                 InterfaceTableRecord(Int32(0), Int32(0), null)
             } else {
-                val vtableEntries = layoutBuilder.interfaceVTableEntries.map { methodImpls[it]!!.bitcast(int8TypePtr) }
+                val vtableEntries = layoutBuilder.interfaceTableEntries.map { methodImpls[it]!!.bitcast(int8TypePtr) }
                 val interfaceVTable = staticData.placeGlobalArray("", kInt8Ptr, vtableEntries)
                 InterfaceTableRecord(
-                        Int32(layoutBuilder.classId),
-                        Int32(layoutBuilder.interfaceVTableEntries.size),
+                        Int32(layoutBuilder.hierarchyInfo.interfaceId),
+                        Int32(layoutBuilder.interfaceTableEntries.size),
                         interfaceVTable.pointer.getElementPtr(0)
                 )
             }
@@ -540,6 +596,7 @@
                 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 5a04d3f..5d2024c 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,6 +26,7 @@
     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 dd3e4d6..6416569 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
@@ -458,12 +458,14 @@
 
     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),
@@ -476,6 +478,7 @@
             typeInfo: ConstPointer?,
             vtable: ConstPointer?,
             vtableSize: Int,
+            methodTable: List<RTTIGenerator.MethodTableRecord>,
             itable: List<RTTIGenerator.InterfaceTableRecord>,
             itableSize: Int,
             val objCName: String,
@@ -490,6 +493,9 @@
             vtable,
             Int32(vtableSize),
 
+            staticData.placeGlobalConstArray("", runtime.methodTableRecordType, methodTable),
+            Int32(methodTable.size),
+
             staticData.placeGlobalConstArray("", runtime.interfaceTableRecordType, itable),
             Int32(itableSize),
 
@@ -1162,10 +1168,12 @@
 private fun ObjCExportCodeGenerator.createReverseAdapter(
         irFunction: IrFunction,
         baseMethod: ObjCMethodSpec.BaseMethod<IrSimpleFunctionSymbol>,
+        functionName: String,
         vtableIndex: Int?,
         itablePlace: ClassLayoutBuilder.InterfaceTablePlace?
 ): ObjCExportCodeGenerator.KotlinToObjCMethodAdapter {
 
+    val nameSignature = functionName.localHash.value
     val selector = baseMethod.selector
 
     val kotlinToObjC = generateKotlinToObjCBridge(
@@ -1173,7 +1181,7 @@
             baseMethod
     ).bitcast(int8TypePtr)
 
-    return KotlinToObjCMethodAdapter(selector,
+    return KotlinToObjCMethodAdapter(selector, nameSignature,
             itablePlace ?: ClassLayoutBuilder.InterfaceTablePlace.INVALID,
             vtableIndex ?: -1,
             kotlinToObjC)
@@ -1243,9 +1251,8 @@
 private fun ObjCExportCodeGenerator.itablePlace(irFunction: IrSimpleFunction): ClassLayoutBuilder.InterfaceTablePlace? {
     assert(irFunction.isOverridable)
     val irClass = irFunction.parentAsClass
-    return if (irClass.isInterface
-            && (irFunction.isReal || irFunction.resolveFakeOverrideMaybeAbstract().parent != context.irBuiltIns.anyClass.owner)
-    ) {
+    return if (irClass.isInterface && context.ghaEnabled()
+            && (irFunction.isReal || irFunction.resolveFakeOverrideMaybeAbstract().parent != context.irBuiltIns.anyClass.owner)) {
         context.getLayoutBuilder(irClass).itablePlace(irFunction)
     } else {
         null
@@ -1264,6 +1271,7 @@
             typeInfo = null,
             vtable = null,
             vtableSize = -1,
+            methodTable = emptyList(),
             itable = emptyList(),
             itableSize = -1,
             objCName = name,
@@ -1346,9 +1354,15 @@
         null
     }
 
+    val methodTable = if (!irClass.isInterface && irClass.isAbstract()) {
+        rttiGenerator.methodTableRecords(irClass)
+    } else {
+        emptyList()
+    }
+
     val (itable, itableSize) = when {
-        irClass.isInterface -> Pair(emptyList(), context.getLayoutBuilder(irClass).interfaceVTableEntries.size)
-        irClass.isAbstract() -> rttiGenerator.interfaceTableRecords(irClass)
+        irClass.isInterface -> Pair(emptyList(), context.getLayoutBuilder(irClass).interfaceTableEntries.size)
+        irClass.isAbstract() && context.ghaEnabled() -> rttiGenerator.interfaceTableRecords(irClass)
         else -> Pair(emptyList(), -1)
     }
 
@@ -1357,6 +1371,7 @@
             typeInfo,
             vtable,
             vtableSize,
+            methodTable,
             itable,
             itableSize,
             objCName,
@@ -1451,7 +1466,7 @@
                     presentVtableBridges += vtableIndex
                     presentMethodTableBridges += functionName
                     presentItableBridges += itablePlace
-                    result += createReverseAdapter(it, baseMethod, vtableIndex, itablePlace)
+                    result += createReverseAdapter(it, baseMethod, functionName, vtableIndex, itablePlace)
                     coveredMethods += it
                 }
             }
@@ -1472,6 +1487,7 @@
         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 219c99d..2c8485f 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 @@
                                 receiverType.vtable[call.calleeVtableIndex]
 
                             is DataFlowIR.Node.ItableCall ->
-                                receiverType.itable[call.interfaceId]!![call.calleeItableIndex]
+                                receiverType.itable[call.calleeHash]!!
 
                             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 b43e75f..abe1a7f 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,24 +785,18 @@
                                                             symbolTable.mapClassReferenceType(owner)
                                                         }
                                                     }
-
-                                            val isAnyMethod = callee.target.parentAsClass.isAny()
-                                            if (owner.isInterface && !isAnyMethod) {
-                                                val itablePlace = context.getLayoutBuilder(owner).itablePlace(callee)
+                                            if (owner.isInterface) {
+                                                val calleeHash = callee.computeFunctionName().localHash.value
                                                 DataFlowIR.Node.ItableCall(
                                                         symbolTable.mapFunction(callee.target),
                                                         receiverType,
-                                                        itablePlace.interfaceId,
-                                                        itablePlace.methodIndex,
+                                                        calleeHash,
                                                         arguments,
                                                         mapReturnType(value.type, callee.target.returnType),
                                                         value
                                                 )
                                             } else {
-                                                val vtableIndex = if (isAnyMethod)
-                                                    context.getLayoutBuilder(context.irBuiltIns.anyClass.owner).vtableIndex(callee.target)
-                                                else
-                                                    context.getLayoutBuilder(owner).vtableIndex(callee)
+                                                val vtableIndex = 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 4a82d86..ac6f9c5 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,10 +6,9 @@
 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
@@ -65,7 +64,7 @@
             : Type(isFinal, isAbstract, primitiveBinaryType, name) {
             val superTypes = mutableListOf<Type>()
             val vtable = mutableListOf<FunctionSymbol>()
-            val itable = mutableMapOf<Int, List<FunctionSymbol>>()
+            val itable = mutableMapOf<Long, FunctionSymbol>()
         }
 
         class Public(val hash: Long, index: Int, isFinal: Boolean, isAbstract: Boolean, primitiveBinaryType: PrimitiveBinaryType?,
@@ -245,7 +244,7 @@
                          arguments: List<Edge>, returnType: Type, irCallSite: IrCall?)
             : VirtualCall(callee, arguments, receiverType, returnType, irCallSite)
 
-        class ItableCall(callee: FunctionSymbol, receiverType: Type, val interfaceId: Int, val calleeItableIndex: Int,
+        class ItableCall(callee: FunctionSymbol, receiverType: Type, val calleeHash: Long,
                          arguments: List<Edge>, returnType: Type, irCallSite: IrCall?)
             : VirtualCall(callee, arguments, receiverType, returnType, irCallSite)
 
@@ -343,7 +342,7 @@
                 is Node.ItableCall -> buildString {
                     appendLine("        INTERFACE CALL ${node.callee}. Return type = ${node.returnType}")
                     appendLine("            RECEIVER: ${node.receiverType}")
-                    append("            INTERFACE ID: ${node.interfaceId}. ITABLE INDEX: ${node.calleeItableIndex}")
+                    append("            METHOD HASH: ${node.calleeHash}")
                     appendList(node.arguments) {
                         append("            ARG #${ids[it.node]!!}")
                         appendCastTo(it.castToType)
@@ -506,13 +505,12 @@
                 type.vtable += layoutBuilder.vtableEntries.map {
                     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)!!) }
+                layoutBuilder.methodTableEntries.forEach {
+                    type.itable[it.overriddenFunction.computeFunctionName().localHash.value] = mapFunction(it.getImplementation(context)!!)
                 }
             } else if (irClass.isInterface) {
                 // Warmup interface table so it is computed before DCE.
-                context.getLayoutBuilder(irClass).interfaceVTableEntries
+                context.getLayoutBuilder(irClass).interfaceTableEntries
             }
             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 76a003c..c8c4958 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 @@
                     } +
                             moduleDFG.symbolTable.classMap.values
                                     .filterIsInstance<DataFlowIR.Type.Declared>()
-                                    .flatMap { it.vtable + it.itable.values.flatten() }
+                                    .flatMap { it.vtable + it.itable.values }
                                     .filterIsInstance<DataFlowIR.FunctionSymbol.Declared>()
                                     .filter { moduleDFG.functions.containsKey(it) }
                 }
@@ -263,7 +263,7 @@
                 vtable[callSite.calleeVtableIndex]
 
             is DataFlowIR.Node.ItableCall ->
-                itable[callSite.interfaceId]!![callSite.calleeItableIndex]
+                itable[callSite.calleeHash]!!
 
             else -> error("Unreachable")
         }
diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle
index d0a31c2..5f3ffbe 100644
--- a/kotlin-native/backend.native/tests/build.gradle
+++ b/kotlin-native/backend.native/tests/build.gradle
@@ -2646,10 +2646,6 @@
     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
deleted file mode 100644
index 1fd467f..0000000
--- a/kotlin-native/backend.native/tests/codegen/interfaceCallsNCasts/functionNameClash.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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<T> {
-    fun foo(x: T): String
-}
-interface I2<T> {
-    fun foo(x: T): String
-}
-class C : I1<String>, I2<Int> {
-    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<String> = c
-    assertEquals("I1.foo(str)", i1.foo("str"))
-    val i2: I2<Int> = 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 64aaecd..5e88ada 100644
--- a/kotlin-native/runtime/src/main/cpp/ObjCExport.mm
+++ b/kotlin-native/runtime/src/main/cpp/ObjCExport.mm
@@ -52,6 +52,7 @@
 
 struct KotlinToObjCMethodAdapter {
   const char* selector;
+  MethodNameHash nameSignature;
   ClassId interfaceId;
   int itableSize;
   int itableIndex;
@@ -65,6 +66,9 @@
   const void * const * kotlinVtable;
   int kotlinVtableSize;
 
+  const MethodTableRecord* kotlinMethodTable;
+  int kotlinMethodTableSize;
+
   const InterfaceTableRecord* kotlinItable;
   int kotlinItableSize;
 
@@ -678,6 +682,7 @@
   const TypeInfo* superType,
   const KStdVector<const TypeInfo*>& superInterfaces,
   const KStdVector<VTableElement>& vtable,
+  const KStdVector<MethodTableRecord>& methodTable,
   const KStdOrderedMap<ClassId, KStdVector<VTableElement>>& interfaceVTables,
   const InterfaceTableRecord* superItable,
   int superItableSize,
@@ -732,6 +737,12 @@
     }
   }
 
+  MethodTableRecord* openMethods_ = konanAllocArray<MethodTableRecord>(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));
@@ -796,6 +807,22 @@
   return -1;
 }
 
+static void insertOrReplace(KStdVector<MethodTableRecord>& 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;
@@ -819,6 +846,9 @@
   const void * const * superVtable = nullptr;
   int superVtableSize = getVtableSize(superType);
 
+  const MethodTableRecord* superMethodTable = nullptr;
+  int superMethodTableSize = 0;
+
   InterfaceTableRecord const* superITable = nullptr;
   int superITableSize = 0;
 
@@ -828,17 +858,27 @@
     // 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<const void*> vtable(
         superVtable,
         superVtable + superVtableSize
   );
 
+  KStdVector<MethodTableRecord> methodTable(
+        superMethodTable, superMethodTable + superMethodTableSize
+  );
+
   if (superITable == nullptr) {
     superITable = superType->interfaceTable_;
     superITableSize = superType->interfaceTableSize_;
@@ -905,6 +945,7 @@
       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)
@@ -929,6 +970,7 @@
       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) {
@@ -942,8 +984,9 @@
 
   // TODO: consider forbidding the class being abstract.
 
-  const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, interfaceVTables,
-                                          superITable, superITableSize, itableEqualsSuper, fieldsInfo);
+  const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, methodTable,
+                                          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 a46f3dc..3e226a0 100644
--- a/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp
+++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp
@@ -17,7 +17,44 @@
 #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 a974e6b..118c23c 100644
--- a/kotlin-native/runtime/src/main/cpp/TypeInfo.h
+++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.h
@@ -29,6 +29,17 @@
 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 {
@@ -112,6 +123,9 @@
     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_;
 
@@ -167,6 +181,14 @@
 #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;