[K/N][codegen] Trampoline to call virtual functions to reduce dependencies coupling

This commit is contained in:
Igor Chevdar
2023-03-25 15:58:00 +02:00
committed by Space Team
parent 281296fc2a
commit 74864ba1d0
12 changed files with 192 additions and 128 deletions
@@ -99,6 +99,8 @@ internal class NativeGenerationState(
val cStubsManager = CStubsManager(config.target, this)
lateinit var llvmDeclarations: LlvmDeclarations
val virtualFunctionTrampolines = mutableMapOf<IrSimpleFunction, LlvmCallable>()
val coverage by lazy { CoverageManager(this) }
lateinit var objCExport: ObjCExport
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrEnumEntry
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.util.isOverridable
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -49,11 +50,8 @@ internal class CAdapterCodegen(
val bridgeFunctionProto = signature.toProto(cname, null, LLVMLinkage.LLVMExternalLinkage)
// If function is virtual, we need to resolve receiver properly.
generateFunction(codegen, bridgeFunctionProto) {
val callee = if (!DescriptorUtils.isTopLevelDeclaration(function) &&
irFunction.isOverridable
) {
val receiver = param(0)
lookupVirtualImpl(receiver, irFunction)
val callee = if (!DescriptorUtils.isTopLevelDeclaration(function) && irFunction.isOverridable) {
codegen.getVirtualFunctionTrampoline(irFunction as IrSimpleFunction)
} else {
// KT-45468: Alias insertion may not be handled by LLVM properly, in case callee is in the cache.
// Hence, insert not an alias but a wrapper, hoping it will be optimized out later.
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cgen.CBridgeOrigin
import org.jetbrains.kotlin.backend.konan.descriptors.ClassGlobalHierarchyInfo
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.KonanBinaryInterface.symbolName
import org.jetbrains.kotlin.backend.konan.llvm.ThreadState.Native
import org.jetbrains.kotlin.backend.konan.llvm.ThreadState.Runnable
import org.jetbrains.kotlin.backend.konan.llvm.objc.*
@@ -116,6 +117,7 @@ internal inline fun generateFunction(
startLocation,
endLocation,
switchToRunnable = isCToKotlinBridge,
needSafePoint = true,
function)
if (isCToKotlinBridge) {
@@ -147,6 +149,7 @@ internal inline fun generateFunction(
startLocation: LocationInfo? = null,
endLocation: LocationInfo? = null,
switchToRunnable: Boolean = false,
needSafePoint: Boolean = true,
code: FunctionGenerationContext.() -> Unit
) : LlvmCallable {
val function = codegen.addFunction(functionProto)
@@ -155,7 +158,8 @@ internal inline fun generateFunction(
codegen,
startLocation,
endLocation,
switchToRunnable = switchToRunnable
switchToRunnable = switchToRunnable,
needSafePoint = needSafePoint
)
try {
generateFunctionBody(functionGenerationContext, code)
@@ -172,7 +176,8 @@ internal inline fun generateFunctionNoRuntime(
code: FunctionGenerationContext.() -> Unit,
) : LlvmCallable {
val function = codegen.addFunction(functionProto)
val functionGenerationContext = DefaultFunctionGenerationContext(function, codegen, null, null, switchToRunnable = false)
val functionGenerationContext = DefaultFunctionGenerationContext(function, codegen, null, null,
switchToRunnable = false, needSafePoint = true)
try {
functionGenerationContext.forbidRuntime = true
require(!functionGenerationContext.isObjectType(functionGenerationContext.returnType!!)) {
@@ -197,6 +202,134 @@ private inline fun <T : FunctionGenerationContext> generateFunctionBody(
functionGenerationContext.resetDebugLocation()
}
private fun IrSimpleFunction.findOverriddenMethodOfAny(): IrSimpleFunction? {
if (modality == Modality.ABSTRACT) return null
val resolved = resolveFakeOverride()!!
if ((resolved.parent as IrClass).isAny()) {
return resolved
}
return null
}
internal object VirtualTablesLookup {
fun FunctionGenerationContext.getInterfaceTableRecord(typeInfo: LLVMValueRef, interfaceId: Int): LLVMValueRef {
val interfaceTableSize = load(structGep(typeInfo, 9 /* interfaceTableSize_ */))
val interfaceTable = load(structGep(typeInfo, 10 /* interfaceTable_ */))
fun fastPath(): LLVMValueRef {
// The fastest optimistic version.
val interfaceTableIndex = and(interfaceTableSize, llvm.int32(interfaceId))
return gep(interfaceTable, interfaceTableIndex)
}
// See details in ClassLayoutBuilder.
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()
} else {
val startLocationInfo = position()?.start
val fastPathBB = basicBlock("fast_path", startLocationInfo)
val slowPathBB = basicBlock("slow_path", startLocationInfo)
val takeResBB = basicBlock("take_res", startLocationInfo)
condBr(icmpGe(interfaceTableSize, llvm.kImmInt32Zero), fastPathBB, slowPathBB)
positionAtEnd(takeResBB)
val resultPhi = phi(pointerType(runtime.interfaceTableRecordType))
appendingTo(fastPathBB) {
val fastValue = fastPath()
br(takeResBB)
addPhiIncoming(resultPhi, currentBlock to fastValue)
}
appendingTo(slowPathBB) {
val actualInterfaceTableSize = sub(llvm.kImmInt32Zero, interfaceTableSize) // -interfaceTableSize
val slowValue = call(llvm.lookupInterfaceTableRecord,
listOf(interfaceTable, actualInterfaceTableSize, llvm.int32(interfaceId)))
br(takeResBB)
addPhiIncoming(resultPhi, currentBlock to slowValue)
}
resultPhi
}
}
fun FunctionGenerationContext.getVirtualImpl(receiver: LLVMValueRef, irFunction: IrSimpleFunction): LlvmCallable {
assert(LLVMTypeOf(receiver) == codegen.kObjHeaderPtr)
val typeInfoPtr: LLVMValueRef = if (irFunction.getObjCMethodInfo() != null)
call(llvm.getObjCKotlinTypeInfo, listOf(receiver))
else
loadTypeInfo(receiver)
assert(typeInfoPtr.type == codegen.kTypeInfoPtr) { llvmtype2string(typeInfoPtr.type) }
val owner = irFunction.parentAsClass
val canCallViaVtable = !owner.isInterface
val layoutBuilder = generationState.context.getLayoutBuilder(owner)
val llvmMethod = when {
canCallViaVtable -> {
val index = layoutBuilder.vtableIndex(irFunction)
val vtablePlace = gep(typeInfoPtr, llvm.int32(1)) // typeInfoPtr + 1
val vtable = bitcast(llvm.int8PtrPtrType, vtablePlace)
val slot = gep(vtable, llvm.int32(index))
load(slot)
}
else -> {
// Essentially: typeInfo.itable[place(interfaceId)].vtable[method]
val itablePlace = layoutBuilder.itablePlace(irFunction)
val interfaceTableRecord = getInterfaceTableRecord(typeInfoPtr, itablePlace.interfaceId)
load(gep(load(structGep(interfaceTableRecord, 2 /* vtable */)), llvm.int32(itablePlace.methodIndex)))
}
}
val functionPtrType = pointerType(codegen.getLlvmFunctionType(irFunction))
return LlvmCallable(
bitcast(functionPtrType, llvmMethod),
LlvmFunctionSignature(irFunction, this)
)
}
}
/*
* Special trampoline function to call actual virtual implementation. This helps with reducing
* dependence between klibs/files (if vtable/itable of some class has changed, the call sites
* would be the same and wouldn't need recompiling).
*/
internal fun CodeGenerator.getVirtualFunctionTrampoline(irFunction: IrSimpleFunction): LlvmCallable {
/*
* Resolve owner of the call with special handling of Any methods:
* if toString/eq/hc is invoked on an interface instance, we resolve
* owner as Any and dispatch it via vtable.
*/
val anyMethod = irFunction.findOverriddenMethodOfAny()
return getVirtualFunctionTrampolineImpl(anyMethod ?: irFunction)
}
private fun CodeGenerator.getVirtualFunctionTrampolineImpl(irFunction: IrSimpleFunction) =
generationState.virtualFunctionTrampolines.getOrPut(irFunction) {
val targetName = if (irFunction.isExported())
irFunction.symbolName
else
irFunction.computePrivateSymbolName(irFunction.parentAsClass.fqNameForIrSerialization.asString())
val proto = LlvmFunctionProto(
name = "$targetName-trampoline",
signature = LlvmFunctionSignature(irFunction, this),
origin = null,
linkage = linkageOf(irFunction)
)
if (isExternal(irFunction))
llvm.externalFunction(proto)
else generateFunction(this, proto, needSafePoint = false) {
val args = proto.signature.parameterTypes.indices.map { param(it) }
val receiver = param(0)
val callee = with(VirtualTablesLookup) { getVirtualImpl(receiver, irFunction) }
val result = call(callee, args, exceptionHandler = ExceptionHandler.Caller, verbatim = true)
ret(result)
}
}
/**
* There're cases when we don't need end position or it is meaningless.
*/
@@ -390,6 +523,7 @@ internal abstract class FunctionGenerationContextBuilder<T : FunctionGenerationC
var startLocation: LocationInfo? = null
var endLocation: LocationInfo? = null
var switchToRunnable = false
var needSafePoint = true
var irFunction: IrFunction? = null
abstract fun build(): T
@@ -401,6 +535,7 @@ internal abstract class FunctionGenerationContext(
private val startLocation: LocationInfo?,
protected val endLocation: LocationInfo?,
switchToRunnable: Boolean,
needSafePoint: Boolean,
internal val irFunction: IrFunction? = null
) : ContextUtils {
@@ -410,6 +545,7 @@ internal abstract class FunctionGenerationContext(
startLocation = builder.startLocation,
endLocation = builder.endLocation,
switchToRunnable = builder.switchToRunnable,
needSafePoint = builder.needSafePoint,
irFunction = builder.irFunction
)
@@ -455,6 +591,9 @@ internal abstract class FunctionGenerationContext(
private val switchToRunnable: Boolean =
context.memoryModel == MemoryModel.EXPERIMENTAL && switchToRunnable
private val needSafePoint: Boolean =
context.memoryModel == MemoryModel.EXPERIMENTAL && needSafePoint
val stackLocalsManager = StackLocalsManagerImpl(this, stackLocalsInitBb)
data class FunctionInvokeInformation(
@@ -1112,98 +1251,6 @@ internal abstract class FunctionGenerationContext(
return load(typeInfoPtrPtr, memoryOrder = LLVMAtomicOrdering.LLVMAtomicOrderingMonotonic)
}
fun lookupInterfaceTableRecord(typeInfo: LLVMValueRef, interfaceId: Int): LLVMValueRef {
val interfaceTableSize = load(structGep(typeInfo, 9 /* interfaceTableSize_ */))
val interfaceTable = load(structGep(typeInfo, 10 /* interfaceTable_ */))
fun fastPath(): LLVMValueRef {
// The fastest optimistic version.
val interfaceTableIndex = and(interfaceTableSize, llvm.int32(interfaceId))
return gep(interfaceTable, interfaceTableIndex)
}
// See details in ClassLayoutBuilder.
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()
} else {
val startLocationInfo = position()?.start
val fastPathBB = basicBlock("fast_path", startLocationInfo)
val slowPathBB = basicBlock("slow_path", startLocationInfo)
val takeResBB = basicBlock("take_res", startLocationInfo)
condBr(icmpGe(interfaceTableSize, llvm.kImmInt32Zero), fastPathBB, slowPathBB)
positionAtEnd(takeResBB)
val resultPhi = phi(pointerType(runtime.interfaceTableRecordType))
appendingTo(fastPathBB) {
val fastValue = fastPath()
br(takeResBB)
addPhiIncoming(resultPhi, currentBlock to fastValue)
}
appendingTo(slowPathBB) {
val actualInterfaceTableSize = sub(llvm.kImmInt32Zero, interfaceTableSize) // -interfaceTableSize
val slowValue = call(llvm.lookupInterfaceTableRecord,
listOf(interfaceTable, actualInterfaceTableSize, llvm.int32(interfaceId)))
br(takeResBB)
addPhiIncoming(resultPhi, currentBlock to slowValue)
}
resultPhi
}
}
fun lookupVirtualImpl(receiver: LLVMValueRef, irFunction: IrFunction): LlvmCallable {
assert(LLVMTypeOf(receiver) == codegen.kObjHeaderPtr)
val typeInfoPtr: LLVMValueRef = if (irFunction.getObjCMethodInfo() != null)
call(llvm.getObjCKotlinTypeInfo, listOf(receiver))
else
loadTypeInfo(receiver)
assert(typeInfoPtr.type == codegen.kTypeInfoPtr) { LLVMPrintTypeToString(typeInfoPtr.type)!!.toKString() }
/*
* Resolve owner of the call with special handling of Any methods:
* if toString/eq/hc is invoked on an interface instance, we resolve
* owner as Any and dispatch it via vtable.
*/
val anyMethod = (irFunction as IrSimpleFunction).findOverriddenMethodOfAny()
val owner = (anyMethod ?: irFunction).parentAsClass
val llvmMethod = when {
!owner.isInterface -> {
// If this is a virtual method of the class - we can call via vtable.
val index = context.getLayoutBuilder(owner).vtableIndex(anyMethod ?: irFunction)
val vtablePlace = gep(typeInfoPtr, llvm.int32(1)) // typeInfoPtr + 1
val vtable = bitcast(llvm.int8PtrPtrType, vtablePlace)
val slot = gep(vtable, llvm.int32(index))
load(slot)
}
else -> {
// Essentially: typeInfo.itable[place(interfaceId)].vtable[method]
val itablePlace = context.getLayoutBuilder(owner).itablePlace(irFunction)
val interfaceTableRecord = lookupInterfaceTableRecord(typeInfoPtr, itablePlace.interfaceId)
load(gep(load(structGep(interfaceTableRecord, 2 /* vtable */)), llvm.int32(itablePlace.methodIndex)))
}
}
val functionPtrType = pointerType(codegen.getLlvmFunctionType(irFunction))
return LlvmCallable(
bitcast(functionPtrType, llvmMethod),
LlvmFunctionSignature(irFunction, this)
)
}
private fun IrSimpleFunction.findOverriddenMethodOfAny(): IrSimpleFunction? {
if (modality == Modality.ABSTRACT) return null
val resolved = resolveFakeOverride()!!
if ((resolved.parent as IrClass).isAny()) {
return resolved
}
return null
}
@Suppress("UNUSED_PARAMETER")
fun getObjectValue(irClass: IrClass, exceptionHandler: ExceptionHandler,
startLocationInfo: LocationInfo?, endLocationInfo: LocationInfo? = null,
@@ -1279,7 +1326,7 @@ internal abstract class FunctionGenerationContext(
currentPositionHolder.resetBuilderDebugLocation()
}
private fun position() = basicBlockToLastLocation[currentBlock]
fun position() = basicBlockToLastLocation[currentBlock]
internal fun mapParameterForDebug(index: Int, value: LLVMValueRef) {
appendingTo(localsInitBb) {
@@ -1366,7 +1413,7 @@ internal abstract class FunctionGenerationContext(
} else {
check(!setCurrentFrameIsCalled)
}
if (context.memoryModel == MemoryModel.EXPERIMENTAL && !forbidRuntime) {
if (context.memoryModel == MemoryModel.EXPERIMENTAL && !forbidRuntime && needSafePoint) {
call(llvm.Kotlin_mm_safePointFunctionPrologue, emptyList())
}
resetDebugLocation()
@@ -1572,6 +1619,7 @@ internal class DefaultFunctionGenerationContext(
startLocation: LocationInfo?,
endLocation: LocationInfo?,
switchToRunnable: Boolean,
needSafePoint: Boolean,
irFunction: IrFunction? = null
) : FunctionGenerationContext(
function,
@@ -1579,6 +1627,7 @@ internal class DefaultFunctionGenerationContext(
startLocation,
endLocation,
switchToRunnable,
needSafePoint,
irFunction
) {
// Note: return handling can be extracted to a separate class.
@@ -162,6 +162,13 @@ internal interface ContextUtils : RuntimeAware {
return !generationState.llvmModuleSpecification.containsDeclaration(declaration)
}
fun linkageOf(irFunction: IrFunction) = when {
isExternal(irFunction) -> LLVMLinkage.LLVMExternalLinkage
irFunction.isExported() -> LLVMLinkage.LLVMExternalLinkage
context.config.producePerFileCache && irFunction in generationState.calledFromExportedInlineFunctions -> LLVMLinkage.LLVMExternalLinkage
else -> LLVMLinkage.LLVMInternalLinkage
}
/**
* LLVM function generated from the Kotlin function.
* It may be declared as external function prototype.
@@ -784,9 +784,16 @@ internal class CodeGeneratorVisitor(
}
}
private fun buildVirtualFunctionTrampoline(irFunction: IrSimpleFunction) {
codegen.getVirtualFunctionTrampoline(irFunction)
}
override fun visitFunction(declaration: IrFunction) {
context.log{"visitFunction : ${ir2string(declaration)}"}
if (declaration is IrSimpleFunction && declaration.isOverridable && declaration.origin !is DECLARATION_ORIGIN_BRIDGE_METHOD)
buildVirtualFunctionTrampoline(declaration)
val scopeState = llvm.initializersGenerationState.scopeState
if (declaration.origin == DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER) {
require(scopeState.globalInitFunction == null) { "There can only be at most one global file initializer" }
@@ -1597,7 +1604,8 @@ internal class CodeGeneratorVisitor(
val interfaceId = dstHierarchyInfo.interfaceId
val typeInfo = functionGenerationContext.loadTypeInfo(srcObjInfoPtr)
with(functionGenerationContext) {
val interfaceTableRecord = lookupInterfaceTableRecord(typeInfo, interfaceId)
val interfaceTableRecord = with(VirtualTablesLookup) { getInterfaceTableRecord(typeInfo, interfaceId) }
icmpEq(load(structGep(interfaceTableRecord, 0 /* id */)), llvm.int32(interfaceId))
}
}
@@ -2617,8 +2625,8 @@ internal class CodeGeneratorVisitor(
//-------------------------------------------------------------------------//
fun callVirtual(function: IrFunction, args: List<LLVMValueRef>, resultLifetime: Lifetime, resultSlot: LLVMValueRef?): LLVMValueRef {
val functionDeclarations = functionGenerationContext.lookupVirtualImpl(args.first(), function)
fun callVirtual(function: IrSimpleFunction, args: List<LLVMValueRef>, resultLifetime: Lifetime, resultSlot: LLVMValueRef?): LLVMValueRef {
val functionDeclarations = codegen.getVirtualFunctionTrampoline(function)
return call(function, functionDeclarations, args, resultLifetime, resultSlot)
}
@@ -392,12 +392,7 @@ private class DeclarationsGeneratorVisitor(override val generationState: NativeG
}
}
val linkage = when {
declaration.isExported() -> LLVMLinkage.LLVMExternalLinkage
context.config.producePerFileCache && declaration in generationState.calledFromExportedInlineFunctions -> LLVMLinkage.LLVMExternalLinkage
else -> LLVMLinkage.LLVMInternalLinkage
}
val proto = LlvmFunctionProto(declaration, symbolName, this, linkage)
val proto = LlvmFunctionProto(declaration, symbolName, this, linkageOf(declaration))
context.log {
"Creating llvm function ${symbolName} for ${declaration.render()}"
}
@@ -294,7 +294,7 @@ internal class BlockGenerator(private val codegen: CodeGenerator) {
val invokeMethod = context.ir.symbols.functionN(numberOfParameters).owner.simpleFunctions()
.single { it.name == OperatorNameConventions.INVOKE }
val llvmDeclarations = lookupVirtualImpl(kotlinFunction, invokeMethod)
val llvmDeclarations = codegen.getVirtualFunctionTrampoline(invokeMethod)
val result = callFromBridge(llvmDeclarations, listOf(kotlinFunction) + kotlinArguments, Lifetime.ARGUMENT)
if (bridge.returnsVoid) {
ret(null)
@@ -945,10 +945,10 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
listOf(param(0), codegen.typeInfoValue(target.parent as IrClass))
)
}
val llvmCallable = if (!isVirtual) {
codegen.llvmFunction(target)
val llvmCallable = if (isVirtual) {
codegen.getVirtualFunctionTrampoline(target as IrSimpleFunction)
} else {
lookupVirtualImpl(args.first(), target)
codegen.llvmFunction(target)
}
call(llvmCallable, args, resultLifetime, exceptionHandler)
}
@@ -14,7 +14,7 @@ func testDirectObjc2Kotlin() throws {
func testVirtualObjc2Kotlin() throws {
let trace = StacktraceBridgesKt.createBar().foo()
try assertTrue(trace[5].contains("objc2kotlin_virtual_kfun:Foo#foo(){}kotlin.collections.List<kotlin.String>"))
try assertTrue(trace[6].contains("objc2kotlin_virtual_kfun:Foo#foo(){}kotlin.collections.List<kotlin.String>"))
}
func testKotlin2Objc() throws {
+4 -3
View File
@@ -6,9 +6,10 @@
frame #1: [..]`kfun:[..]main$lambda$0[..] at kt42208-2.kt:10:18
frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0[..]invoke[..](_this=[..])[..] at kt42208-1.kt:7:60
frame #3: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.[..]$<bridge-UNN>invoke(_this=[..]){}kotlin.Nothing[..] at kt42208-1.kt:7:60
frame #4: [..]`kfun:#main(){} at kt42208-1.kt:5:5
frame #5: [..]`Konan_start(args=[..]) at [..]
frame #6: [..]
frame #4: [..]`kfun:kotlin.Function0#invoke(){}1:0-trampoline + 73
frame #5: [..]`kfun:#main(){} at kt42208-1.kt:5:5
frame #6: [..]`Konan_start(args=[..]) at [..]
frame #7: [..]
frame #8: [..]
> q
@@ -5,8 +5,9 @@
* frame #0: [..]`kfun:[..]main$lambda$0[..] at kt42208-2.kt:14:5
frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.[..]invoke[..](_this=[..])[..] at kt42208-1.kt:9:82
frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.[..]$<bridge-BNN>invoke(_this=[..]){}kotlin.Boolean[..] at kt42208-1.kt:9:82
frame #3: [..]`kfun:#bar(v=[..]){} at kt42208-3.kt:18:5
frame #4: [..]`kfun:#main(){} at kt42208-1.kt:7:5
frame #5: [..]`Konan_start(args=[..]) at [..]
frame #3: [..]`kfun:kotlin.Function0#invoke(){}1:0-trampoline + 73
frame #4: [..]`kfun:#bar(v=[..]){} at kt42208-3.kt:18:5
frame #5: [..]`kfun:#main(){} at kt42208-1.kt:7:5
frame #6: [..]`Konan_start(args=[..]) at [..]
> c
> q
+10 -7
View File
@@ -5,23 +5,26 @@
* frame #0: [..]`kfun:[..]main$lambda$0[..] at kt42208-2.kt:15:5
frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0[..]invoke[..](_this=[..])[..] at kt42208-1.kt:10:71
frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.[..]$<bridge-BNN>invoke(_this=[..]){}kotlin.Boolean[..] at kt42208-1.kt:10:71
frame #3: [..]`kfun:#main(){} at kt42208-1.kt:6:5
frame #4: [..]`Konan_start(args=[..]) at [..]
frame #5: [..]
frame #3: [..]`kfun:kotlin.Function0#invoke(){}1:0-trampoline + 73
frame #4: [..]`kfun:#main(){} at kt42208-1.kt:6:5
frame #5: [..]`Konan_start(args=[..]) at [..]
frame #6: [..]
> c
> bt
* thread #1, [..] stop reason = breakpoint 1.1
* frame #0: [..]`kfun:[..]main$lambda$0[..] at kt42208-2.kt:15:5
frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0[..]invoke[..](_this=[..])[..] at kt42208-1.kt:10:71
frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.[..]$<bridge-BNN>invoke(_this=[..]){}kotlin.Boolean[..] at kt42208-1.kt:10:71
frame #3: [..]`kfun:#main(){} at kt42208-1.kt:7:5
frame #4: [..]`Konan_start(args=[..]) at [..]
frame #3: [..]`kfun:kotlin.Function0#invoke(){}1:0-trampoline + 73
frame #4: [..]`kfun:#main(){} at kt42208-1.kt:7:5
frame #5: [..]`Konan_start(args=[..]) at [..]
> c
> bt
* thread #1, [..] stop reason = breakpoint 1.1
* frame #0: [..]`kfun:[..]main$lambda$0[..] at kt42208-2.kt:15:5
frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0[..]invoke[..](_this=[..])[..] at kt42208-1.kt:10:71
frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.[..]$<bridge-BNN>invoke(_this=[..]){}kotlin.Boolean[..] at kt42208-1.kt:10:71
frame #3: [..]`kfun:#main(){} at kt42208-1.kt:8:5
frame #4: [..]`Konan_start(args=[..]) at [..]
frame #3: [..]`kfun:kotlin.Function0#invoke(){}1:0-trampoline + 73
frame #4: [..]`kfun:#main(){} at kt42208-1.kt:8:5
frame #5: [..]`Konan_start(args=[..]) at [..]
> q