All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.codegen.inline.MethodInliner.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.codegen.inline

import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmType
import org.jetbrains.kotlin.codegen.inline.FieldRemapper.Companion.foldName
import org.jetbrains.kotlin.codegen.inline.coroutines.CoroutineTransformer
import org.jetbrains.kotlin.codegen.inline.coroutines.isSuspendLambdaCapturedByOuterObjectOrLambda
import org.jetbrains.kotlin.codegen.inline.coroutines.markNoinlineLambdaIfSuspend
import org.jetbrains.kotlin.codegen.inline.coroutines.surroundInvokesWithSuspendMarkersIfNeeded
import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.ControlFlowGraph
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.commons.LocalVariablesSorter
import org.jetbrains.org.objectweb.asm.commons.MethodRemapper
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.util.Printer
import java.util.*
import kotlin.math.max

class MethodInliner(
    private val node: MethodNode,
    private val parameters: Parameters,
    private val inliningContext: InliningContext,
    private val nodeRemapper: FieldRemapper,
    private val isSameModule: Boolean,
    private val errorPrefix: String,
    private val sourceMapper: SourceMapCopier,
    private val inlineCallSiteInfo: InlineCallSiteInfo,
    private val inlineOnlySmapSkipper: InlineOnlySmapSkipper?, //non null only for root
    private val shouldPreprocessApiVersionCalls: Boolean = false
) {
    private val languageVersionSettings = inliningContext.state.languageVersionSettings
    private val invokeCalls = ArrayList()
    //keeps order
    private val transformations = ArrayList()
    //current state
    private val currentTypeMapping = HashMap()
    private val result = InlineResult.create()
    private var lambdasFinallyBlocks: Int = 0

    fun doInline(
        adapter: MethodVisitor,
        remapper: LocalVarRemapper,
        remapReturn: Boolean,
        returnLabelOwner: ReturnLabelOwner
    ): InlineResult {
        return doInline(adapter, remapper, remapReturn, returnLabelOwner, 0)
    }

    private fun recordTransformation(info: TransformationInfo) {
        if (!inliningContext.isInliningLambda) {
            inliningContext.root.state.globalInlineContext.recordTypeFromInlineFunction(info.oldClassName)
        }
        if (info.shouldRegenerate(isSameModule)) {
            inliningContext.recordRegeneratedAnonymousObject(info.oldClassName)
        }
        transformations.add(info)
    }

    private fun doInline(
        adapter: MethodVisitor,
        remapper: LocalVarRemapper,
        remapReturn: Boolean,
        returnLabelOwner: ReturnLabelOwner,
        finallyDeepShift: Int
    ): InlineResult {
        //analyze body
        var transformedNode = markPlacesForInlineAndRemoveInlinable(node, returnLabelOwner, finallyDeepShift)
        if (inliningContext.isInliningLambda && isDefaultLambdaWithReification(inliningContext.lambdaInfo!!)) {
            //TODO maybe move reification in one place
            inliningContext.root.inlineMethodReifier.reifyInstructions(transformedNode)
        }

        //substitute returns with "goto end" instruction to keep non local returns in lambdas
        val end = linkedLabel()
        val isTransformingAnonymousObject = nodeRemapper is RegeneratedLambdaFieldRemapper
        transformedNode = doInline(transformedNode)
        if (!isTransformingAnonymousObject) {
            //don't remove assertion in transformed anonymous object
            removeClosureAssertions(transformedNode)
        }
        transformedNode.instructions.resetLabels()

        val resultNode = MethodNode(
            Opcodes.API_VERSION, transformedNode.access, transformedNode.name, transformedNode.desc,
            transformedNode.signature, transformedNode.exceptions?.toTypedArray()
        )

        val visitor = RemapVisitor(resultNode, remapper, nodeRemapper)

        try {
            transformedNode.accept(
                if (isTransformingAnonymousObject) {
                    /*keep annotations and attributes during anonymous object transformations*/
                    visitor
                } else MethodBodyVisitor(visitor)
            )
        } catch (e: Throwable) {
            throw wrapException(e, transformedNode, "couldn't inline method call")
        }

        resultNode.visitLabel(end)

        if (inliningContext.isRoot) {
            val remapValue = remapper.remap(parameters.argsSizeOnStack + 1).value
            InternalFinallyBlockInliner.processInlineFunFinallyBlocks(
                resultNode, lambdasFinallyBlocks, (remapValue as StackValue.Local).index,
                languageVersionSettings.supportsFeature(LanguageFeature.ProperFinally)
            )
        }

        processReturns(resultNode, returnLabelOwner, remapReturn, end)
        //flush transformed node to output
        resultNode.accept(SkipMaxAndEndVisitor(adapter))
        return result
    }

    private fun doInline(node: MethodNode): MethodNode {
        val currentInvokes = LinkedList(invokeCalls)

        val resultNode = MethodNode(node.access, node.name, node.desc, node.signature, null)

        val iterator = transformations.iterator()

        val remapper = TypeRemapper.createFrom(currentTypeMapping)

        // MethodRemapper doesn't extends LocalVariablesSorter, but RemappingMethodAdapter does.
        // So wrapping with LocalVariablesSorter to keep old behavior
        // TODO: investigate LocalVariablesSorter removing (see also same code in RemappingClassBuilder.java)
        val remappingMethodAdapter = MethodRemapper(
            LocalVariablesSorter(
                resultNode.access,
                resultNode.desc,
                wrapWithMaxLocalCalc(resultNode)
            ), AsmTypeRemapper(remapper, result)
        )

        val fakeContinuationName = CoroutineTransformer.findFakeContinuationConstructorClassName(node)
        val markerShift = calcMarkerShift(parameters, node)
        val lambdaInliner = object : InlineAdapter(remappingMethodAdapter, parameters.argsSizeOnStack, sourceMapper) {
            private var transformationInfo: TransformationInfo? = null

            private fun handleAnonymousObjectRegeneration() {
                transformationInfo = iterator.next()

                val oldClassName = transformationInfo!!.oldClassName
                if (transformationInfo!!.shouldRegenerate(isSameModule)) {
                    //TODO: need poping of type but what to do with local funs???
                    val newClassName = transformationInfo!!.newClassName
                    remapper.addMapping(oldClassName, newClassName)

                    val childInliningContext = inliningContext.subInlineWithClassRegeneration(
                        inliningContext.nameGenerator,
                        currentTypeMapping,
                        inlineCallSiteInfo,
                        transformationInfo!!
                    )
                    val transformer = transformationInfo!!.createTransformer(
                        childInliningContext, isSameModule, fakeContinuationName
                    )

                    val transformResult = transformer.doTransform(nodeRemapper)
                    result.merge(transformResult)
                    result.addChangedType(oldClassName, newClassName)

                    if (inliningContext.isInliningLambda &&
                        inliningContext.lambdaInfo !is DefaultLambda && //never delete default lambda classes
                        transformationInfo!!.canRemoveAfterTransformation() &&
                        !inliningContext.root.state.globalInlineContext.isTypeFromInlineFunction(oldClassName)
                    ) {
                        // this class is transformed and original not used so we should remove original one after inlining
                        result.addClassToRemove(oldClassName)
                    }

                    if (transformResult.reifiedTypeParametersUsages.wereUsedReifiedParameters()) {
                        ReifiedTypeInliner.putNeedClassReificationMarker(mv)
                        result.reifiedTypeParametersUsages.mergeAll(transformResult.reifiedTypeParametersUsages)
                    }
                } else if (!transformationInfo!!.wasAlreadyRegenerated) {
                    result.addNotChangedClass(oldClassName)
                }
            }

            override fun anew(type: Type) {
                if (isSamWrapper(type.internalName) || isAnonymousClass(type.internalName)) {
                    handleAnonymousObjectRegeneration()
                }

                //in case of regenerated transformationInfo type would be remapped to new one via remappingMethodAdapter
                super.anew(type)
            }

            override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
                if (/*INLINE_RUNTIME.equals(owner) &&*/ isInvokeOnLambda(owner, name)) { //TODO add method
                    assert(!currentInvokes.isEmpty())
                    val invokeCall = currentInvokes.remove()
                    val info = invokeCall.functionalArgument

                    if (info !is LambdaInfo) {
                        //noninlinable lambda
                        markNoinlineLambdaIfSuspend(mv, info)
                        super.visitMethodInsn(opcode, owner, name, desc, itf)
                        return
                    }

                    // in case of inlining suspend lambda reference as ordinary parameter of inline function:
                    //   suspend fun foo (...) ...
                    //   inline fun inlineMe(c: (...) -> ...) ...
                    //   builder {
                    //     inlineMe(::foo)
                    //   }
                    // we should create additional parameter for continuation.
                    var coroutineDesc = desc
                    val actualInvokeDescriptor: FunctionDescriptor
                    if (info.isSuspend) {
                        actualInvokeDescriptor = (info as ExpressionLambda).getInlineSuspendLambdaViewDescriptor()
                        val parametersSize = actualInvokeDescriptor.valueParameters.size +
                                (if (actualInvokeDescriptor.extensionReceiverParameter != null) 1 else 0)
                        // And here we expect invoke(...Ljava/lang/Object;) be replaced with invoke(...Lkotlin/coroutines/Continuation;)
                        // if this does not happen, insert fake continuation, since we could not have one yet.
                        val argumentTypes = Type.getArgumentTypes(desc)
                        if (argumentTypes.size != parametersSize &&
                            // But do not add it in IR. In IR we already have lowered lambdas with additional parameter, while in Old BE we don't.
                            !inliningContext.root.state.isIrBackend
                        ) {
                            addFakeContinuationMarker(this)
                            coroutineDesc = Type.getMethodDescriptor(Type.getReturnType(desc), *argumentTypes, AsmTypes.OBJECT_TYPE)
                        }
                    } else {
                        actualInvokeDescriptor = info.invokeMethodDescriptor
                    }

                    val valueParameters =
                        listOfNotNull(actualInvokeDescriptor.extensionReceiverParameter) + actualInvokeDescriptor.valueParameters

                    val erasedInvokeFunction = ClosureCodegen.getErasedInvokeFunction(actualInvokeDescriptor)
                    val invokeParameters = erasedInvokeFunction.valueParameters

                    val valueParamShift = max(nextLocalIndex, markerShift)//NB: don't inline cause it changes
                    val parameterTypesFromDesc = info.invokeMethod.argumentTypes
                    putStackValuesIntoLocalsForLambdaOnInvoke(
                        listOf(*parameterTypesFromDesc), valueParameters, invokeParameters, valueParamShift, this, coroutineDesc
                    )

                    if (parameterTypesFromDesc.isEmpty()) {
                        // There won't be no parameters processing and line call can be left without actual instructions.
                        // Note: if function is called on the line with other instructions like 1 + foo(), 'nop' will still be generated.
                        visitInsn(Opcodes.NOP)
                    }

                    inlineOnlySmapSkipper?.onInlineLambdaStart(remappingMethodAdapter, info, sourceMapper.parent)
                    addInlineMarker(this, true)
                    val lambdaParameters = info.addAllParameters(nodeRemapper)

                    val newCapturedRemapper = InlinedLambdaRemapper(
                        info.lambdaClassType.internalName, nodeRemapper, lambdaParameters,
                        info is DefaultLambda && info.isBoundCallableReference
                    )

                    setLambdaInlining(true)

                    val callSite = sourceMapper.callSite.takeIf { info is DefaultLambda }
                    val inliner = MethodInliner(
                        info.node.node, lambdaParameters, inliningContext.subInlineLambda(info),
                        newCapturedRemapper,
                        if (info is DefaultLambda) isSameModule else true /*cause all nested objects in same module as lambda*/,
                        "Lambda inlining " + info.lambdaClassType.internalName,
                        SourceMapCopier(sourceMapper.parent, info.node.classSMAP, callSite), inlineCallSiteInfo, null
                    )

                    val varRemapper = LocalVarRemapper(lambdaParameters, valueParamShift)
                    //TODO add skipped this and receiver
                    val lambdaResult = inliner.doInline(this.mv, varRemapper, true, info, invokeCall.finallyDepthShift)
                    result.mergeWithNotChangeInfo(lambdaResult)
                    result.reifiedTypeParametersUsages.mergeAll(lambdaResult.reifiedTypeParametersUsages)

                    StackValue
                        .onStack(info.invokeMethod.returnType, info.invokeMethodDescriptor.returnType)
                        .put(OBJECT_TYPE, erasedInvokeFunction.returnType, this)
                    setLambdaInlining(false)
                    addInlineMarker(this, false)
                    inlineOnlySmapSkipper?.onInlineLambdaEnd(remappingMethodAdapter)
                } else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
                    //TODO add proper message
                    var info = transformationInfo as? AnonymousObjectTransformationInfo ?: throw AssertionError(
                        " call doesn't correspond to object transformation info for '$owner.$name': $transformationInfo"
                    )
                    val objectConstructsItself = inlineCallSiteInfo.ownerClassName == info.oldClassName
                    if (objectConstructsItself) {
                        // inline fun f() -> new f$1 -> fun something() in class f$1 -> new f$1
                        //                   ^-- fetch the info that was created for this instruction
                        info = inliningContext.parent?.transformationInfo as? AnonymousObjectTransformationInfo
                            ?: throw AssertionError("anonymous object $owner constructs itself, but we have no info on in")
                    }
                    if (info.shouldRegenerate(isSameModule)) {
                        for (capturedParamDesc in info.allRecapturedParameters) {
                            val realDesc = if (objectConstructsItself && capturedParamDesc.fieldName == AsmUtil.THIS) {
                                // The captures in `info` are relative to the parent context, so a normal `this` there
                                // is a captured outer `this` here.
                                CapturedParamDesc(Type.getObjectType(owner), AsmUtil.CAPTURED_THIS_FIELD, capturedParamDesc.type)
                            } else capturedParamDesc
                            visitFieldInsn(
                                Opcodes.GETSTATIC, realDesc.containingLambdaName,
                                FieldRemapper.foldName(realDesc.fieldName), realDesc.type.descriptor
                            )
                        }
                        super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf)

                        //TODO: add new inner class also for other contexts
                        if (inliningContext.parent is RegeneratedClassContext) {
                            inliningContext.parent.typeRemapper.addAdditionalMappings(info.oldClassName, info.newClassName)
                        }

                        transformationInfo = null
                    } else {
                        super.visitMethodInsn(opcode, owner, name, desc, itf)
                    }
                } else if ((!inliningContext.isInliningLambda || isDefaultLambdaWithReification(inliningContext.lambdaInfo!!)) &&
                    ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))
                ) {
                    //we shouldn't process here content of inlining lambda it should be reified at external level except default lambdas
                } else {
                    super.visitMethodInsn(opcode, owner, name, desc, itf)
                }
            }

            override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
                if (opcode == Opcodes.GETSTATIC && (isAnonymousSingletonLoad(owner, name) || isWhenMappingAccess(owner, name))) {
                    handleAnonymousObjectRegeneration()
                }
                super.visitFieldInsn(opcode, owner, name, desc)
            }

            override fun visitMaxs(stack: Int, locals: Int) {
                lambdasFinallyBlocks = resultNode.tryCatchBlocks.size
                super.visitMaxs(stack, locals)
            }
        }

        node.accept(lambdaInliner)

        surroundInvokesWithSuspendMarkersIfNeeded(resultNode)
        return resultNode
    }

    private fun isDefaultLambdaWithReification(lambdaInfo: LambdaInfo) =
        lambdaInfo is DefaultLambda && lambdaInfo.needReification

    private fun prepareNode(node: MethodNode, finallyDeepShift: Int): MethodNode {
        node.instructions.resetLabels()

        val capturedParamsSize = parameters.capturedParametersSizeOnStack
        val realParametersSize = parameters.realParametersSizeOnStack
        val reorderIrLambdaParameters = inliningContext.isInliningLambda &&
                inliningContext.parent?.isInliningLambda == false &&
                inliningContext.lambdaInfo is IrExpressionLambda
        val newArgumentList = if (reorderIrLambdaParameters) {
            // In IR lambdas, captured variables come before real parameters, but after the extension receiver.
            // Move them to the end of the descriptor instead.
            Type.getArgumentTypes(inliningContext.lambdaInfo!!.invokeMethod.descriptor) + parameters.capturedTypes
        } else {
            Type.getArgumentTypes(node.desc) + parameters.capturedTypes
        }
        val transformedNode = MethodNode(
            Opcodes.API_VERSION, node.access, node.name,
            Type.getMethodDescriptor(Type.getReturnType(node.desc), *newArgumentList),
            node.signature, node.exceptions?.toTypedArray()
        )

        val transformationVisitor = object : InlineMethodInstructionAdapter(transformedNode) {
            private val GENERATE_DEBUG_INFO = GENERATE_SMAP && inlineOnlySmapSkipper == null

            private val isInliningLambda = nodeRemapper.isInsideInliningLambda

            private fun getNewIndex(`var`: Int): Int {
                val lambdaInfo = inliningContext.lambdaInfo
                if (reorderIrLambdaParameters && lambdaInfo is IrExpressionLambda) {
                    val extensionSize =
                        if (lambdaInfo.isExtensionLambda && !lambdaInfo.isBoundCallableReference)
                            lambdaInfo.invokeMethod.argumentTypes[0].size
                        else 0
                    return when {
                        //                v-- extensionSize     v-- argsSizeOnStack
                        // |- extension -|- captured -|- real -|- locals -|    old descriptor
                        // |- extension -|- real -|- captured -|- locals -|    new descriptor
                        //                         ^-- realParametersSize
                        `var` >= parameters.argsSizeOnStack -> `var`
                        `var` >= extensionSize + capturedParamsSize -> `var` - capturedParamsSize
                        `var` >= extensionSize -> `var` + realParametersSize - extensionSize
                        else -> `var`
                    }
                }
                // |- extension -|- real -|- locals -|               old descriptor
                // |- extension -|- real -|- captured -|- locals -|  new descriptor
                return `var` + if (`var` < realParametersSize) 0 else capturedParamsSize
            }

            override fun visitVarInsn(opcode: Int, `var`: Int) {
                super.visitVarInsn(opcode, getNewIndex(`var`))
            }

            override fun visitIincInsn(`var`: Int, increment: Int) {
                super.visitIincInsn(getNewIndex(`var`), increment)
            }

            override fun visitMaxs(maxStack: Int, maxLocals: Int) {
                super.visitMaxs(maxStack, maxLocals + capturedParamsSize)
            }

            override fun visitLineNumber(line: Int, start: Label) {
                if (isInliningLambda || GENERATE_DEBUG_INFO) {
                    super.visitLineNumber(line, start)
                }
            }

            override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
                if (DEFAULT_LAMBDA_FAKE_CALL == owner) {
                    val index = name.substringAfter(DEFAULT_LAMBDA_FAKE_CALL).toInt()
                    val lambda = getFunctionalArgumentIfExists(index) as DefaultLambda
                    lambda.parameterOffsetsInDefault.zip(lambda.capturedVars).asReversed().forEach { (_, captured) ->
                        val originalBoundReceiverType = lambda.originalBoundReceiverType
                        if (lambda.isBoundCallableReference && AsmUtil.isPrimitive(originalBoundReceiverType)) {
                            StackValue.onStack(originalBoundReceiverType!!).put(captured.type, InstructionAdapter(this))
                        }
                        super.visitFieldInsn(
                            Opcodes.PUTSTATIC,
                            captured.containingLambdaName,
                            CAPTURED_FIELD_FOLD_PREFIX + captured.fieldName,
                            captured.type.descriptor
                        )
                    }
                } else {
                    super.visitMethodInsn(opcode, owner, name, desc, itf)
                }
            }

            override fun visitLocalVariable(
                name: String, desc: String, signature: String?, start: Label, end: Label, index: Int
            ) {
                if (isInliningLambda || GENERATE_DEBUG_INFO) {
                    val isInlineFunctionMarker = name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
                    val varSuffix = when {
                        inliningContext.isRoot && !isInlineFunctionMarker -> INLINE_FUN_VAR_SUFFIX
                        else -> ""
                    }

                    val varName = if (!varSuffix.isEmpty() && name == AsmUtil.THIS) AsmUtil.INLINE_DECLARATION_SITE_THIS else name
                    super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index))
                }
            }
        }

        node.accept(transformationVisitor)

        transformCaptured(transformedNode)
        transformFinallyDeepIndex(transformedNode, finallyDeepShift)

        return transformedNode
    }

    private fun markPlacesForInlineAndRemoveInlinable(
        node: MethodNode, returnLabelOwner: ReturnLabelOwner, finallyDeepShift: Int
    ): MethodNode {
        val processingNode = prepareNode(node, finallyDeepShift)

        preprocessNodeBeforeInline(processingNode, returnLabelOwner)

        replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode)

        val toDelete = SmartSet.create()

        val sources = analyzeMethodNodeWithInterpreter(processingNode, FunctionalArgumentInterpreter(this, toDelete))
        val instructions = processingNode.instructions

        var awaitClassReification = false
        var currentFinallyDeep = 0

        InsnSequence(instructions).forEach { cur ->
            val frame = sources[instructions.indexOf(cur)]

            if (frame != null) {
                when {
                    ReifiedTypeInliner.isNeedClassReificationMarker(cur) -> awaitClassReification = true

                    cur is MethodInsnNode -> {
                        if (isFinallyStart(cur)) {
                            //TODO deep index calc could be more precise
                            currentFinallyDeep = getConstant(cur.previous)
                        }

                        val owner = cur.owner
                        val name = cur.name
                        //TODO check closure
                        val argTypes = Type.getArgumentTypes(cur.desc)
                        val paramCount = argTypes.size + 1//non static
                        val firstParameterIndex = frame.stackSize - paramCount
                        if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
                            val sourceValue = frame.getStack(firstParameterIndex)
                            invokeCalls.add(InvokeCall(sourceValue.functionalArgument, currentFinallyDeep))
                        } else if (isSamWrapperConstructorCall(owner, name)) {
                            recordTransformation(SamWrapperTransformationInfo(owner, inliningContext, isAlreadyRegenerated(owner)))
                        } else if (isAnonymousConstructorCall(owner, name)) {
                            val functionalArgumentMapping = HashMap()

                            var offset = 0
                            var capturesAnonymousObjectThatMustBeRegenerated = false
                            for (i in 0 until paramCount) {
                                val sourceValue = frame.getStack(firstParameterIndex + i)
                                val functionalArgument = sourceValue.functionalArgument
                                if (functionalArgument != null) {
                                    functionalArgumentMapping[offset] = functionalArgument
                                } else if (i < argTypes.size && isAnonymousClassThatMustBeRegenerated(argTypes[i])) {
                                    capturesAnonymousObjectThatMustBeRegenerated = true
                                }

                                offset += if (i == 0) 1 else argTypes[i - 1].size
                            }

                            recordTransformation(
                                buildConstructorInvocation(
                                    owner,
                                    cur.desc,
                                    functionalArgumentMapping,
                                    awaitClassReification,
                                    capturesAnonymousObjectThatMustBeRegenerated
                                )
                            )
                            awaitClassReification = false
                        } else if (inliningContext.isInliningLambda && ReifiedTypeInliner.isOperationReifiedMarker(cur)) {
                            val reificationArgument = cur.reificationArgument
                            val parameterName = reificationArgument!!.parameterName
                            result.reifiedTypeParametersUsages.addUsedReifiedParameter(parameterName)
                        }
                    }

                    cur.opcode == Opcodes.GETSTATIC -> {
                        val fieldInsnNode = cur as FieldInsnNode?
                        val className = fieldInsnNode!!.owner
                        if (isAnonymousSingletonLoad(className, fieldInsnNode.name)) {
                            recordTransformation(
                                AnonymousObjectTransformationInfo(
                                    className, awaitClassReification, isAlreadyRegenerated(className), true,
                                    inliningContext.nameGenerator
                                )
                            )
                            awaitClassReification = false
                        } else if (isWhenMappingAccess(className, fieldInsnNode.name)) {
                            recordTransformation(
                                WhenMappingTransformationInfo(
                                    className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode
                                )
                            )
                        } else if (fieldInsnNode.isCheckAssertionsStatus()) {
                            fieldInsnNode.owner = inlineCallSiteInfo.ownerClassName
                            if (inliningContext.isInliningLambda) {
                                if (inliningContext.lambdaInfo!!.isCrossInline) {
                                    assert(inliningContext.parent?.parent is RegeneratedClassContext) {
                                        "$inliningContext grandparent shall be RegeneratedClassContext but got ${inliningContext.parent?.parent}"
                                    }
                                    inliningContext.parent!!.parent!!.generateAssertField = true
                                } else {
                                    assert(inliningContext.parent != null) {
                                        "$inliningContext parent shall not be null"
                                    }
                                    inliningContext.parent!!.generateAssertField = true
                                }
                            } else {
                                inliningContext.generateAssertField = true
                            }
                        }
                    }

                    cur.opcode == Opcodes.POP -> {
                        if (frame.top().functionalArgument is LambdaInfo) {
                            toDelete.add(cur)
                        }
                    }

                    cur.opcode == Opcodes.PUTFIELD -> {
                        //Recognize next contract's pattern in inline lambda
                        //  ALOAD 0
                        //  SOME_VALUE
                        //  PUTFIELD $capturedField
                        // and transform it to
                        //  SOME_VALUE
                        //  PUTSTATIC $$$$capturedField
                        val fieldInsn = cur as FieldInsnNode
                        if (isCapturedFieldName(fieldInsn.name) &&
                            nodeRemapper is InlinedLambdaRemapper &&
                            nodeRemapper.originalLambdaInternalName == fieldInsn.owner
                        ) {
                            val stackTransformations = mutableSetOf()
                            val lambdaInfo = frame.peek(1)?.functionalArgument
                            if (lambdaInfo is LambdaInfo && stackTransformations.all { it is VarInsnNode }) {
                                assert(lambdaInfo.lambdaClassType.internalName == nodeRemapper.originalLambdaInternalName) {
                                    "Wrong bytecode template for contract template: ${lambdaInfo.lambdaClassType.internalName} != ${nodeRemapper.originalLambdaInternalName}"
                                }
                                fieldInsn.name = FieldRemapper.foldName(fieldInsn.name)
                                fieldInsn.opcode = Opcodes.PUTSTATIC
                                toDelete.addAll(stackTransformations)
                            }
                        }
                    }
                }
            } else {
                //given frame is null if and only if the corresponding instruction cannot be reached (dead code).
                //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
                if (cur.type == AbstractInsnNode.LABEL) {
                    //NB: Cause we generate exception table for default handler using gaps (see ExpressionCodegen.visitTryExpression)
                    //it may occurs that interval for default handler starts before catch start label, so this label seems as dead,
                    //but as result all this labels will be merged into one (see KT-5863)
                } else {
                    toDelete.add(cur)
                }
            }
        }

        processingNode.remove(toDelete)

        //clean dead try/catch blocks
        processingNode.tryCatchBlocks.removeIf { it.isMeaningless() }

        return processingNode
    }

    // Replace ALOAD 0
    // with
    //   ICONST fakeContinuationMarker
    //   INVOKESTATIC InlineMarker.mark
    //   ACONST_NULL
    // iff this ALOAD 0 is continuation and one of the following conditions is met
    //   1) it is passed as the last parameter to suspending function
    //   2) it is ASTORE'd right after
    //   3) it is passed to invoke of lambda
    private fun replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode: MethodNode) {
        // in ir backend inline suspend lambdas do not use ALOAD 0 to get continuation, since they are generated as static functions
        // instead they get continuation from parameter.
        if (inliningContext.state.isIrBackend) return
        val lambdaInfo = inliningContext.lambdaInfo ?: return
        if (!lambdaInfo.isSuspend) return
        val sources = analyzeMethodNodeWithInterpreter(processingNode, Aload0Interpreter(processingNode))
        val cfg = ControlFlowGraph.build(processingNode)
        val aload0s = processingNode.instructions.asSequence().filter { it.opcode == Opcodes.ALOAD && it.safeAs()?.`var` == 0 }

        val visited = hashSetOf()
        fun findMeaningfulSuccs(insn: AbstractInsnNode): Collection {
            if (!visited.add(insn)) return emptySet()
            val res = hashSetOf()
            for (succIndex in cfg.getSuccessorsIndices(insn)) {
                val succ = processingNode.instructions[succIndex]
                if (succ.isMeaningful) res.add(succ)
                else res.addAll(findMeaningfulSuccs(succ))
            }
            return res
        }

        // After inlining suspendCoroutineUninterceptedOrReturn there will be suspension point, which is not a MethodInsnNode.
        // So, it is incorrect to expect MethodInsnNodes only
        val suspensionPoints = processingNode.instructions.asSequence()
            .filter { isBeforeSuspendMarker(it) }
            .flatMap { findMeaningfulSuccs(it).asSequence() }
            .filter { it is MethodInsnNode }

        val toReplace = hashSetOf()
        for (suspensionPoint in suspensionPoints) {
            assert(suspensionPoint is MethodInsnNode) {
                "suspensionPoint shall be MethodInsnNode, but instead $suspensionPoint"
            }
            suspensionPoint as MethodInsnNode
            assert(Type.getReturnType(suspensionPoint.desc) == OBJECT_TYPE) {
                "suspensionPoint shall return $OBJECT_TYPE, but returns ${Type.getReturnType(suspensionPoint.desc)}"
            }
            val frame = sources[processingNode.instructions.indexOf(suspensionPoint)] ?: continue
            val paramTypes = Type.getArgumentTypes(suspensionPoint.desc)
            if (suspensionPoint.name.endsWith(JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX)) {
                // Expected pattern here:
                //     ALOAD 0
                //     (ICONST or other integers creating instruction)
                //     (ACONST_NULL or ALOAD)
                //     ICONST_0
                //     INVOKESTATIC InlineMarker.mark
                //     INVOKE* suspendingFunction$default(..., Continuation;ILjava/lang/Object)Ljava/lang/Object;
                assert(paramTypes.size >= 3) {
                    "${suspensionPoint.name}${suspensionPoint.desc} shall have 3+ parameters"
                }
            } else {
                // Expected pattern here:
                //     ALOAD 0
                //     ICONST_0
                //     INVOKESTATIC InlineMarker.mark
                //     INVOKE* suspendingFunction(..., Continuation;)Ljava/lang/Object;
                assert(paramTypes.isNotEmpty()) {
                    "${suspensionPoint.name}${suspensionPoint.desc} shall have 1+ parameters"
                }
            }

            for ((index, param) in paramTypes.reversed().withIndex()) {
                if (param != languageVersionSettings.continuationAsmType() && param != OBJECT_TYPE) continue
                val sourceIndices = (frame.getStack(frame.stackSize - index - 1) as? Aload0BasicValue)?.indices ?: continue
                for (sourceIndex in sourceIndices) {
                    val src = processingNode.instructions[sourceIndex]
                    if (src in aload0s) {
                        toReplace.add(src)
                    }
                }
            }
        }

        // Expected pattern here:
        //     ALOAD 0
        //     ASTORE N
        // This pattern may occur after multiple inlines
        // Note, that this is not a suspension point, thus we check it separately
        toReplace.addAll(aload0s.filter { it.next?.opcode == Opcodes.ASTORE })
        // Expected pattern here:
        //     ALOAD 0
        //     INVOKEINTERFACE kotlin/jvm/functions/FunctionN.invoke (...,Ljava/lang/Object;)Ljava/lang/Object;
        toReplace.addAll(aload0s.filter { isLambdaCall(it.next) })
        replaceContinuationsWithFakeOnes(toReplace, processingNode)
    }

    private fun isLambdaCall(invoke: AbstractInsnNode?): Boolean {
        if (invoke?.opcode != Opcodes.INVOKEINTERFACE) return false
        invoke as MethodInsnNode
        if (!invoke.owner.startsWith("kotlin/jvm/functions/Function")) return false
        if (invoke.name != "invoke") return false
        if (Type.getReturnType(invoke.desc) != OBJECT_TYPE) return false
        return Type.getArgumentTypes(invoke.desc).let { it.isNotEmpty() && it.last() == OBJECT_TYPE }
    }

    private fun replaceContinuationsWithFakeOnes(
        continuations: Collection,
        node: MethodNode
    ) {
        for (toReplace in continuations) {
            insertNodeBefore(createFakeContinuationMethodNodeForInline(), node, toReplace)
            node.instructions.remove(toReplace)
        }
    }

    private fun preprocessNodeBeforeInline(node: MethodNode, returnLabelOwner: ReturnLabelOwner) {
        try {
            FixStackWithLabelNormalizationMethodTransformer().transform("fake", node)
        } catch (e: Throwable) {
            throw wrapException(e, node, "couldn't inline method call")
        }

        if (shouldPreprocessApiVersionCalls) {
            val targetApiVersion = inliningContext.state.languageVersionSettings.apiVersion
            ApiVersionCallsPreprocessingMethodTransformer(targetApiVersion).transform("fake", node)
        }

        val frames = analyzeMethodNodeWithInterpreter(node, BasicInterpreter())

        val localReturnsNormalizer = LocalReturnsNormalizer()

        for ((index, insnNode) in node.instructions.toArray().withIndex()) {
            val frame = frames[index] ?: continue
            // Don't care about dead code, it will be eliminated

            if (!isReturnOpcode(insnNode.opcode)) continue

            var insertBeforeInsn = insnNode

            // TODO extract isLocalReturn / isNonLocalReturn, see processReturns
            val labelName = getMarkedReturnLabelOrNull(insnNode)
            if (labelName != null) {
                if (!returnLabelOwner.isReturnFromMe(labelName)) continue
                insertBeforeInsn = insnNode.previous
            }

            localReturnsNormalizer.addLocalReturnToTransform(insnNode, insertBeforeInsn, frame)
        }

        localReturnsNormalizer.transform(node)
    }

    private fun isAnonymousClassThatMustBeRegenerated(type: Type?): Boolean {
        if (type == null || type.sort != Type.OBJECT) return false
        return inliningContext.isRegeneratedAnonymousObject(type.internalName)
    }

    private fun buildConstructorInvocation(
        anonymousType: String,
        desc: String,
        lambdaMapping: Map,
        needReification: Boolean,
        capturesAnonymousObjectThatMustBeRegenerated: Boolean
    ): AnonymousObjectTransformationInfo {
        // In objects inside non-default inline lambdas, all reified type parameters are free (not from the function
        // we're inlining into) so there's nothing to reify:
        //
        //     inline fun  f(x: () -> KClass = { { T::class }() }) = x()
        //     fun a() = f()
        //     fun b() = f { { Int::class }() } // non-default lambda
        //     inline fun  c() = f { { V::class }() }
        //
        // -- in a(), the default inline lambda captures T so a regeneration is needed; but in b() and c(), the non-default
        // inline lambda cannot possibly reference it, while V is not yet bound so regenerating the object while inlining
        // the lambda into f() is pointless.
        val inNonDefaultLambda = inliningContext.isInliningLambda && inliningContext.lambdaInfo !is DefaultLambda
        return AnonymousObjectTransformationInfo(
            anonymousType, needReification && !inNonDefaultLambda, lambdaMapping,
            inliningContext.classRegeneration,
            isAlreadyRegenerated(anonymousType),
            desc,
            false,
            inliningContext.nameGenerator,
            capturesAnonymousObjectThatMustBeRegenerated
        )
    }

    private fun isAlreadyRegenerated(owner: String): Boolean {
        return inliningContext.typeRemapper.hasNoAdditionalMapping(owner)
    }

    internal fun getFunctionalArgumentIfExists(insnNode: AbstractInsnNode): FunctionalArgument? {
        return when {
            insnNode.opcode == Opcodes.ALOAD ->
                getFunctionalArgumentIfExists((insnNode as VarInsnNode).`var`)
            insnNode is FieldInsnNode && insnNode.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX) ->
                findCapturedField(insnNode, nodeRemapper).functionalArgument
            insnNode is FieldInsnNode && insnNode.isSuspendLambdaCapturedByOuterObjectOrLambda(inliningContext) ->
                NonInlineableArgumentForInlineableParameterCalledInSuspend
            else ->
                null
        }
    }

    private fun getFunctionalArgumentIfExists(varIndex: Int): FunctionalArgument? {
        if (varIndex < parameters.argsSizeOnStack) {
            return parameters.getParameterByDeclarationSlot(varIndex).functionalArgument
        }
        return null
    }

    private fun transformCaptured(node: MethodNode) {
        if (nodeRemapper.isRoot) {
            return
        }

        if (inliningContext.isInliningLambda && inliningContext.lambdaInfo is IrExpressionLambda && !inliningContext.parent!!.isInliningLambda) {
            val capturedVars = inliningContext.lambdaInfo.capturedVars
            var offset = parameters.realParametersSizeOnStack
            val map = capturedVars.map {
                offset to it.also { offset += it.type.size }
            }.toMap()

            var cur: AbstractInsnNode? = node.instructions.first
            while (cur != null) {
                if (cur is VarInsnNode && cur.opcode in Opcodes.ILOAD..Opcodes.ALOAD && map.contains(cur.`var`)) {
                    val varIndex = cur.`var`
                    val capturedParamDesc = map[varIndex]!!

                    val newIns = FieldInsnNode(
                        Opcodes.GETSTATIC,
                        capturedParamDesc.containingLambdaName,
                        foldName(capturedParamDesc.fieldName),
                        capturedParamDesc.type.descriptor
                    )
                    node.instructions.insertBefore(cur, newIns)
                    node.instructions.remove(cur)
                    cur = newIns
                }
                cur = cur.next
            }
        }

        // Fold all captured variables access chains
        //          ALOAD 0
        //          [ALOAD this$0]*
        //          GETFIELD $captured
        //  to GETFIELD $$$$captured
        // On future decoding this field could be inlined or unfolded to another field access chain
        // (this chain could differ cause some of this$0 could be inlined)
        var cur: AbstractInsnNode? = node.instructions.first
        while (cur != null) {
            if (cur is VarInsnNode && cur.opcode == Opcodes.ALOAD) {
                val varIndex = cur.`var`
                if (varIndex == 0 || nodeRemapper.shouldProcessNonAload0FieldAccessChains()) {
                    val accessChain = getCapturedFieldAccessChain((cur as VarInsnNode?)!!)
                    val insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node)
                    if (insnNode != null) {
                        cur = insnNode
                    }
                }
            }
            cur = cur.next
        }
    }

    private fun wrapException(originalException: Throwable, node: MethodNode, errorSuffix: String): RuntimeException {
        return if (originalException is InlineException) {
            InlineException("$errorPrefix: $errorSuffix", originalException)
        } else {
            InlineException("$errorPrefix: $errorSuffix\nCause: ${node.nodeText}", originalException)
        }
    }

    private class LocalReturnsNormalizer {
        private class LocalReturn(
            private val returnInsn: AbstractInsnNode,
            private val insertBeforeInsn: AbstractInsnNode,
            private val frame: Frame
        ) {

            fun transform(insnList: InsnList, returnVariableIndex: Int) {
                val isReturnWithValue = returnInsn.opcode != Opcodes.RETURN

                val expectedStackSize = if (isReturnWithValue) 1 else 0
                val actualStackSize = frame.stackSize
                if (expectedStackSize == actualStackSize) return

                var stackSize = actualStackSize
                if (isReturnWithValue) {
                    val storeOpcode = Opcodes.ISTORE + returnInsn.opcode - Opcodes.IRETURN
                    insnList.insertBefore(insertBeforeInsn, VarInsnNode(storeOpcode, returnVariableIndex))
                    stackSize--
                }

                while (stackSize > 0) {
                    val stackElementSize = frame.getStack(stackSize - 1).size
                    val popOpcode = if (stackElementSize == 1) Opcodes.POP else Opcodes.POP2
                    insnList.insertBefore(insertBeforeInsn, InsnNode(popOpcode))
                    stackSize--
                }

                if (isReturnWithValue) {
                    val loadOpcode = Opcodes.ILOAD + returnInsn.opcode - Opcodes.IRETURN
                    insnList.insertBefore(insertBeforeInsn, VarInsnNode(loadOpcode, returnVariableIndex))
                }
            }
        }

        private val localReturns = SmartList()

        private var returnVariableSize = 0
        private var returnOpcode = -1

        internal fun addLocalReturnToTransform(
            returnInsn: AbstractInsnNode,
            insertBeforeInsn: AbstractInsnNode,
            sourceValueFrame: Frame
        ) {
            assert(isReturnOpcode(returnInsn.opcode)) { "return instruction expected" }
            assert(returnOpcode < 0 || returnOpcode == returnInsn.opcode) { "Return op should be " + Printer.OPCODES[returnOpcode] + ", got " + Printer.OPCODES[returnInsn.opcode] }
            returnOpcode = returnInsn.opcode

            localReturns.add(LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame))

            if (returnInsn.opcode != Opcodes.RETURN) {
                returnVariableSize = if (returnInsn.opcode == Opcodes.LRETURN || returnInsn.opcode == Opcodes.DRETURN) {
                    2
                } else {
                    1
                }
            }
        }

        fun transform(methodNode: MethodNode) {
            var returnVariableIndex = -1
            if (returnVariableSize > 0) {
                returnVariableIndex = methodNode.maxLocals
                methodNode.maxLocals += returnVariableSize
            }

            for (localReturn in localReturns) {
                localReturn.transform(methodNode.instructions, returnVariableIndex)
            }
        }
    }

    //Place to insert finally blocks from try blocks that wraps inline fun call
    class PointForExternalFinallyBlocks(
        @JvmField val beforeIns: AbstractInsnNode,
        @JvmField val returnType: Type,
        @JvmField val finallyIntervalEnd: LabelNode
    )

    companion object {

        @JvmStatic
        fun findCapturedField(node: FieldInsnNode, fieldRemapper: FieldRemapper): CapturedParamInfo {
            assert(node.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX)) {
                "Captured field template should start with $CAPTURED_FIELD_FOLD_PREFIX prefix"
            }
            val fin = FieldInsnNode(node.opcode, node.owner, node.name.substring(3), node.desc)
            return fieldRemapper.findField(fin) ?: throw IllegalStateException(
                "Couldn't find captured field ${node.owner}.${node.name} in ${fieldRemapper.originalLambdaInternalName}"
            )
        }

        //remove next template:
        //      aload x
        //      LDC paramName
        //      INTRINSICS_CLASS_NAME.checkParameterIsNotNull/checkNotNullParameter(...)
        private fun removeClosureAssertions(node: MethodNode) {
            val toDelete = arrayListOf()
            InsnSequence(node.instructions).filterIsInstance().forEach { methodInsnNode ->
                if (methodInsnNode.isCheckParameterIsNotNull()) {
                    val prev = methodInsnNode.previous
                    assert(Opcodes.LDC == prev?.opcode) { "'${methodInsnNode.name}' should go after LDC but $prev" }
                    val prevPev = methodInsnNode.previous.previous
                    assert(Opcodes.ALOAD == prevPev?.opcode) { "'${methodInsnNode.name}' should be invoked on local var, but $prev" }

                    toDelete.add(prevPev)
                    toDelete.add(prev)
                    toDelete.add(methodInsnNode)
                }
            }

            node.remove(toDelete)
        }

        private fun transformFinallyDeepIndex(node: MethodNode, finallyDeepShift: Int) {
            if (finallyDeepShift == 0) {
                return
            }

            var cur: AbstractInsnNode? = node.instructions.first
            while (cur != null) {
                if (cur is MethodInsnNode && isFinallyMarker(cur)) {
                    val constant = cur.previous
                    val curDeep = getConstant(constant)
                    node.instructions.insert(constant, LdcInsnNode(curDeep + finallyDeepShift))
                    node.instructions.remove(constant)
                }
                cur = cur.next
            }
        }

        private fun getCapturedFieldAccessChain(aload0: VarInsnNode): List {
            val lambdaAccessChain = mutableListOf(aload0).apply {
                addAll(InsnSequence(aload0.next, null).filter { it.isMeaningful }.takeWhile { insnNode ->
                    insnNode is FieldInsnNode && AsmUtil.CAPTURED_THIS_FIELD == insnNode.name
                }.toList())
            }

            return lambdaAccessChain.apply {
                last().getNextMeaningful().takeIf { insn -> insn is FieldInsnNode }?.also {
                    //captured field access
                        insn ->
                    add(insn)
                }
            }
        }

        private fun putStackValuesIntoLocalsForLambdaOnInvoke(
            directOrder: List,
            directOrderOfArguments: List,
            directOrderOfInvokeParameters: List,
            shift: Int,
            iv: InstructionAdapter,
            descriptor: String
        ) {
            val actualParams = Type.getArgumentTypes(descriptor)
            assert(actualParams.size == directOrder.size) {
                "Number of expected and actual parameters should be equal, but ${actualParams.size} != ${directOrder.size}!"
            }

            var currentShift = shift + directOrder.sumBy { it.size }

            val safeToUseArgumentKotlinType =
                directOrder.size == directOrderOfArguments.size && directOrderOfArguments.size == directOrderOfInvokeParameters.size

            for (index in directOrder.lastIndex downTo 0) {
                val type = directOrder[index]
                currentShift -= type.size
                val typeOnStack = actualParams[index]

                val argumentKotlinType: KotlinType?
                val invokeParameterKotlinType: KotlinType?
                if (safeToUseArgumentKotlinType) {
                    argumentKotlinType = directOrderOfArguments[index].type
                    invokeParameterKotlinType = directOrderOfInvokeParameters[index].type
                } else {
                    argumentKotlinType = null
                    invokeParameterKotlinType = null
                }

                if (typeOnStack != type || invokeParameterKotlinType != argumentKotlinType) {
                    StackValue.onStack(typeOnStack, invokeParameterKotlinType).put(type, argumentKotlinType, iv)
                }
                iv.store(currentShift, type)
            }
        }

        //process local and global returns (local substituted with goto end-label global kept unchanged)
        @JvmStatic
        fun processReturns(
            node: MethodNode, returnLabelOwner: ReturnLabelOwner, remapReturn: Boolean, endLabel: Label?
        ): List {
            if (!remapReturn) {
                return emptyList()
            }
            val result = ArrayList()
            val instructions = node.instructions
            var insnNode: AbstractInsnNode? = instructions.first
            while (insnNode != null) {
                if (isReturnOpcode(insnNode.opcode)) {
                    var isLocalReturn = true
                    val labelName = getMarkedReturnLabelOrNull(insnNode)

                    if (labelName != null) {
                        isLocalReturn = returnLabelOwner.isReturnFromMe(labelName)
                        //remove global return flag
                        if (isLocalReturn) {
                            instructions.remove(insnNode.previous)
                        }
                    }

                    if (isLocalReturn && endLabel != null) {
                        val nop = InsnNode(Opcodes.NOP)
                        instructions.insert(insnNode, nop)

                        val labelNode = endLabel.info as LabelNode
                        val jumpInsnNode = JumpInsnNode(Opcodes.GOTO, labelNode)
                        instructions.insert(nop, jumpInsnNode)

                        instructions.remove(insnNode)
                        insnNode = jumpInsnNode
                    }

                    //generate finally block before nonLocalReturn flag/return/goto
                    val label = LabelNode()
                    instructions.insert(insnNode, label)
                    result.add(
                        PointForExternalFinallyBlocks(
                            getInstructionToInsertFinallyBefore(insnNode, isLocalReturn), getReturnType(insnNode.opcode), label
                        )
                    )
                }
                insnNode = insnNode.next
            }
            return result
        }

        private fun getInstructionToInsertFinallyBefore(nonLocalReturnOrJump: AbstractInsnNode, isLocal: Boolean): AbstractInsnNode {
            return if (isLocal) nonLocalReturnOrJump else nonLocalReturnOrJump.previous
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy