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.1.0-RC
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.CONTINUATION_ASM_TYPE
import org.jetbrains.kotlin.codegen.inline.FieldRemapper.Companion.foldName
import org.jetbrains.kotlin.codegen.inline.coroutines.CoroutineTransformer
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.*
import org.jetbrains.kotlin.codegen.optimization.fixStack.*
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckParameterIsNotNull
import org.jetbrains.kotlin.codegen.optimization.temporaryVals.TemporaryVariablesEliminationTransformer
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
import org.jetbrains.kotlin.config.LanguageFeature
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.utils.SmartList
import org.jetbrains.kotlin.utils.SmartSet
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.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 errorPrefixSupplier: () -> String,
    private val sourceMapper: SourceMapCopier,
    private val inlineCallSiteInfo: InlineCallSiteInfo,
    private val isInlineOnlyMethod: Boolean = false,
    private val shouldPreprocessApiVersionCalls: Boolean = false,
    private val defaultMaskStart: Int = -1,
    private val defaultMaskEnd: Int = -1
) {
    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,
        returnLabels: Map
    ): InlineResult {
        return doInline(adapter, remapper, remapReturn, returnLabels, 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,
        returnLabels: Map,
        finallyDeepShift: Int
    ): InlineResult {
        //analyze body
        var transformedNode = markPlacesForInlineAndRemoveInlinable(node, returnLabels, finallyDeepShift)

        //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)
            )
        }

        if (remapReturn) {
            processReturns(resultNode, returnLabels, 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 localVariablesSorter = LocalVariablesSorter(resultNode.access, resultNode.desc, wrapWithMaxLocalCalc(resultNode))
        val remappingMethodAdapter = MethodRemapper(localVariablesSorter, AsmTypeRemapper(remapper, result))

        val fakeContinuationName = CoroutineTransformer.findFakeContinuationConstructorClassName(node)
        val markerShift = calcMarkerShift(parameters, node)
        var currentLineNumber = if (isInlineOnlyMethod) sourceMapper.callSite!!.line else -1
        val lambdaInliner = object : InlineAdapter(remappingMethodAdapter, parameters.argsSizeOnStack, sourceMapper) {
            private var transformationInfo: TransformationInfo? = null
            private var currentLabel: Label? = null

            override fun visitLabel(label: Label?) {
                currentLabel = label
                super.visitLabel(label)
            }

            override fun visitLineNumber(line: Int, start: Label) {
                if (!isInlineOnlyMethod) {
                    currentLineNumber = line
                }
                super.visitLineNumber(line, start)
            }

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

                val oldClassName = transformationInfo!!.oldClassName
                if (inliningContext.parent?.transformationInfo?.oldClassName == oldClassName) {
                    // Object constructs itself, don't enter an infinite recursion of regeneration.
                } else 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)
                    transformResult.getChangedTypes().forEach { (oldType, newType) ->
                        // KT-65503 For all changed types, if oldType is a lambda or an anonymous object,
                        // and the newType is a name for an inline call,
                        // it should be added to the remapper to ensure correct inline conversion of nested anonymous objects or lambdas.
                        if (newType.contains(INLINE_CALL_TRANSFORMATION_SUFFIX) &&
                            !oldType.contains(INLINE_CALL_TRANSFORMATION_SUFFIX) &&
                            isAnonymousClass(oldType) &&
                            !remapper.hasNoAdditionalMapping(oldType)
                        ) {
                            remapper.addMapping(oldType, newType)
                        }
                    }
                    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)
                    }

                    for (classBuilder in childInliningContext.continuationBuilders.values) {
                        classBuilder.done(inliningContext.state.config.generateSmapCopyToAnnotation)
                    }
                } else {
                    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
                    }

                    val nullableAnyType = inliningContext.state.module.builtIns.nullableAnyType
                    val expectedParameters = info.invokeMethod.argumentTypes
                    val expectedKotlinParameters = info.invokeMethodParameters
                    val argumentCount = Type.getArgumentTypes(desc).size.let {
                        if (info is PsiExpressionLambda && info.invokeMethodDescriptor.isSuspend && it < expectedParameters.size) {
                            // Inlining suspend lambda into a function that takes a non-suspend lambda.
                            // In the IR backend, this cannot happen as inline lambdas are not lowered.
                            addFakeContinuationMarker(this)
                            it + 1
                        } else it
                    }
                    assert(argumentCount == expectedParameters.size && argumentCount == expectedKotlinParameters.size) {
                        "inconsistent lambda arguments: $argumentCount on stack, ${expectedParameters.size} expected, " +
                                "${expectedKotlinParameters.size} Kotlin types"
                    }

                    var valueParamShift = max(nextLocalIndex, markerShift) + expectedParameters.sumOf { it.size }
                    for (index in argumentCount - 1 downTo 0) {
                        val type = expectedParameters[index]
                        StackValue.coerce(AsmTypes.OBJECT_TYPE, nullableAnyType, type, expectedKotlinParameters[index], this)
                        valueParamShift -= type.size
                        store(valueParamShift, type)
                    }
                    if (expectedParameters.isEmpty()) {
                        nop() // add something for a line number to bind onto (TODO what line number?)
                    }

                    val firstLine = info.node.node.instructions.asSequence().mapNotNull { it as? LineNumberNode }.firstOrNull()?.line ?: -1
                    if ((info is DefaultLambda != isInlineOnlyMethod) && currentLineNumber >= 0 && firstLine == currentLineNumber) {
                        // This can happen in two cases:
                        //   1. `someInlineOnlyFunction { singleLineLambda }`: in this case line numbers are removed
                        //      from the inline function, so the entirety of its bytecode has the line number of
                        //      the call site;
                        //   2. `inline fun someFunction(defaultLambda: ... = { ... }) = something(defaultLambda())`:
                        //      all of `someFunction`, including `defaultLambda` if no value is provided at call site,
                        //      has the line number of the declaration.
                        // In those cases the debugger is unable to observe the boundary between the body of the function
                        // and the inline lambda call, as they have the exact same line number. So to force a JDI
                        // event we insert a fake line number separating those two real stretches. The event corresponding
                        // to the fake line number itself should be ignored by the debugger though.
                        val label = Label()
                        val fakeLineNumber =
                            sourceMapper.parent.mapSyntheticLineNumber(SourceMapper.LOCAL_VARIABLE_INLINE_ARGUMENT_SYNTHETIC_LINE_NUMBER)
                        mv.visitLabel(label)
                        mv.visitLineNumber(fakeLineNumber, label)
                    }

                    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,
                        isInlineOnlyMethod = false
                    )

                    val varRemapper = LocalVarRemapper(lambdaParameters, valueParamShift)

                    val inlineScopesGenerator = inliningContext.inlineScopesGenerator

                    val label = currentLabel

                    // When regenerating anonymous objects we may inline a crossinline lambda before some
                    // already inlined functions. For these functions their scope numbers should be incremented.
                    // We also need to temporarily increment the already inlined scopes number by the number of
                    // inline marker variables that we have found before the crossinline lambda call to assign
                    // the scope number for this lambda correctly.
                    val inlineScopeNumberIncrement =
                        if (inlineScopesGenerator != null && label != null && isRegeneratingAnonymousObject()) {
                            incrementScopeNumbersOfVariables(node, label)
                        } else {
                            0
                        }

                    inlineScopesGenerator?.apply {
                        inlinedScopes += inlineScopeNumberIncrement
                        currentCallSiteLineNumber =
                            if (isInlineOnlyMethod) {
                                currentLineNumber
                            } else {
                                sourceMapper.mapLineNumber(currentLineNumber)
                            }
                    }

                    //TODO add skipped this and receiver
                    val lambdaResult =
                        inliner.doInline(localVariablesSorter, varRemapper, true, info.returnLabels, invokeCall.finallyDepthShift)

                    inlineScopesGenerator?.apply { inlinedScopes -= inlineScopeNumberIncrement }
                    result.mergeWithNotChangeInfo(lambdaResult)
                    result.reifiedTypeParametersUsages.mergeAll(lambdaResult.reifiedTypeParametersUsages)
                    result.reifiedTypeParametersUsages.mergeAll(info.reifiedTypeParametersUsages)

                    StackValue.coerce(info.invokeMethod.returnType, info.invokeMethodReturnType, OBJECT_TYPE, nullableAnyType, this)
                    setLambdaInlining(false)
                    addInlineMarker(this, false)

                    if (currentLineNumber != -1) {
                        val endLabel = Label()
                        mv.visitLabel(endLabel)
                        if (isInlineOnlyMethod) {
                            // This is from the function we're inlining into, so no need to remap.
                            mv.visitLineNumber(currentLineNumber, endLabel)
                        } else {
                            // Need to go through the superclass here to properly remap the line number via `sourceMapper`.
                            super.visitLineNumber(currentLineNumber, endLabel)
                        }
                    }
                } else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
                    //TODO add proper message
                    val newInfo = transformationInfo as? AnonymousObjectTransformationInfo ?: throw AssertionError(
                        " call doesn't correspond to object transformation info for '$owner.$name': $transformationInfo"
                    )
                    // inline fun f() -> new f$1 -> fun something() in class f$1 -> new f$1
                    //                   ^-- fetch the info that was created for this instruction
                    // Currently, this self-reference pattern only happens in coroutine objects, which construct
                    // copies of themselves in `create` or `invoke` (not `invokeSuspend`).
                    val existingInfo = inliningContext.parent?.transformationInfo?.takeIf { it.oldClassName == newInfo.oldClassName }
                            as AnonymousObjectTransformationInfo?
                    val info = existingInfo ?: newInfo
                    if (info.shouldRegenerate(isSameModule)) {
                        for (capturedParamDesc in info.allRecapturedParameters) {
                            val realDesc = if (existingInfo != null && 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,
                                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 (ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))) {
                    // If objects are reified, the marker will be recreated by `handleAnonymousObjectRegeneration` above.
                    if (!inliningContext.shouldReifyTypeParametersInObjects) {
                        super.visitMethodInsn(opcode, owner, name, desc, itf)
                    }
                } 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)

        if (inliningContext.inlineScopesGenerator != null && GENERATE_SMAP) {
            updateCallSiteLineNumbers(resultNode, node)
        }

        return resultNode
    }

    private fun updateCallSiteLineNumbers(resultNode: MethodNode, inlinedNode: MethodNode) {
        val inlinedNodeLocalVariables = inlinedNode.localVariables ?: return
        val resultNodeLocalVariables = resultNode.localVariables ?: return
        if (inlinedNodeLocalVariables.isEmpty() || resultNodeLocalVariables.isEmpty()) {
            return
        }

        val markerVariablesFromInlinedNode = inlinedNodeLocalVariables.filter { isFakeLocalVariableForInline(it.name) }
        if (markerVariablesFromInlinedNode.isEmpty()) {
            return
        }

        val markerVariableNamesFromInlinedNode = markerVariablesFromInlinedNode.map { it.name }.toMutableSet()

        // When updating the call site line numbers, we need to skip the marker variable of the inlined node - it has
        // already been assigned a correct call site line number during inlining. However, when regenerating anonymous objects,
        // the inliner copies the bodies of the regenerated methods and no marker variables are introduced during this process.
        // So in case with anonymous object regeneration we don't have to skip anything.
        if (!isRegeneratingAnonymousObject()) {
            val labelToIndex = inlinedNode.getLabelToIndexMap()
            val markerVariableOfInlinedNode = markerVariablesFromInlinedNode.sortedBy { labelToIndex[it.start.label] }.first()
            markerVariableNamesFromInlinedNode.remove(markerVariableOfInlinedNode.name)
        }

        for (variable in resultNodeLocalVariables) {
            val name = variable.name
            if (isFakeLocalVariableForInline(name) && name in markerVariableNamesFromInlinedNode) {
                variable.name = updateCallSiteLineNumber(name) { sourceMapper.mapLineNumber(it) }
            }
        }
    }

    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 oldArgumentTypes = 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)
        } else {
            Type.getArgumentTypes(node.desc)
        }
        val oldArgumentOffsets = oldArgumentTypes.runningFold(0) { acc, type -> acc + type.size }
        val newArgumentTypes = oldArgumentTypes.filterIndexed { index, _ ->
            oldArgumentOffsets[index] !in defaultMaskStart..defaultMaskEnd
        }.toTypedArray() + parameters.capturedTypes
        val transformedNode = MethodNode(
            Opcodes.API_VERSION, node.access, node.name,
            Type.getMethodDescriptor(Type.getReturnType(node.desc), *newArgumentTypes),
            node.signature, node.exceptions?.toTypedArray()
        )

        inliningContext.inlineScopesGenerator?.addInlineScopesInfo(node, isRegeneratingAnonymousObject())

        val transformationVisitor = object : InlineMethodInstructionAdapter(transformedNode) {
            private val GENERATE_DEBUG_INFO = GENERATE_SMAP && !isInlineOnlyMethod

            private val isInliningLambda = nodeRemapper.isInsideInliningLambda

            private fun getNewIndex(`var`: Int): Int {
                val lambdaInfo = inliningContext.lambdaInfo
                @Suppress("USELESS_IS_CHECK") // K2 warning suppression, TODO: KT-62472
                if (reorderIrLambdaParameters && lambdaInfo is IrExpressionLambda) {
                    val extensionSize = if (lambdaInfo.isExtensionLambda) 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
                    for (captured in lambda.capturedVars.asReversed()) {
                        lambda.originalBoundReceiverType?.let {
                            // The receiver is the only captured value; it needs to be boxed.
                            StackValue.onStack(it).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) return

                val isInlineFunctionMarker = name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
                val newName = when {
                    inliningContext.isRoot && !isInlineFunctionMarker -> {
                        if (inliningContext.inlineScopesGenerator != null) {
                            calculateNewNameUsingScopeNumbers(name)
                        } else {
                            calculateNewNameUsingTheOldScheme(name)
                        }
                    }
                    else -> name
                }
                super.visitLocalVariable(newName, desc, signature, start, end, getNewIndex(index))
            }

            private fun calculateNewNameUsingScopeNumbers(name: String): String {
                if (name.startsWith(AsmUtil.THIS)) {
                    val scopeNumber = name.getInlineScopeInfo()?.scopeNumber ?: return AsmUtil.INLINE_DECLARATION_SITE_THIS
                    return "${AsmUtil.INLINE_DECLARATION_SITE_THIS}$INLINE_SCOPE_NUMBER_SEPARATOR$scopeNumber"
                }
                return name
            }

            private fun calculateNewNameUsingTheOldScheme(name: String): String {
                val namePrefix = if (name == AsmUtil.THIS) AsmUtil.INLINE_DECLARATION_SITE_THIS else name
                return namePrefix + INLINE_FUN_VAR_SUFFIX
            }
        }

        node.accept(transformationVisitor)

        transformCaptured(transformedNode)
        transformFinallyDeepIndex(transformedNode, finallyDeepShift)

        return transformedNode
    }

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

        preprocessNodeBeforeInline(processingNode, returnLabels)

        replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode)

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

        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
                        when {
                            isAnonymousSingletonLoad(className, fieldInsnNode.name) -> {
                                recordTransformation(
                                    AnonymousObjectTransformationInfo(
                                        className, awaitClassReification, isAlreadyRegenerated(className), true,
                                        inliningContext.nameGenerator
                                    )
                                )
                                awaitClassReification = false
                            }
                            isWhenMappingAccess(className, fieldInsnNode.name) -> {
                                recordTransformation(
                                    WhenMappingTransformationInfo(
                                        className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode
                                    )
                                )
                            }
                            fieldInsnNode.isCheckAssertionsStatus() -> {
                                fieldInsnNode.owner = inlineCallSiteInfo.ownerClassName
                                when {
                                    // In inline function itself:
                                    inliningContext.parent == null -> inliningContext
                                    // In method of regenerated object - field should already exist:
                                    inliningContext.parent is RegeneratedClassContext -> inliningContext.parent
                                    // In lambda inlined into the root function:
                                    inliningContext.parent.parent == null -> inliningContext.parent
                                    // In lambda inlined into a method of a regenerated object:
                                    else -> inliningContext.parent.parent as? RegeneratedClassContext
                                        ?: throw AssertionError("couldn't find class for \$assertionsDisabled (context = $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 = 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.nodeType == 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
    }

    private fun markObsoleteInstruction(instructions: InsnList, sources: Array?>): SmartSet {
        return instructions.filterIndexedTo(SmartSet.create()) { index, insn ->
            // Parameter checks are processed separately
            !insn.isAloadBeforeCheckParameterIsNotNull() && when (insn.opcode) {
                Opcodes.GETFIELD, Opcodes.GETSTATIC, Opcodes.ALOAD ->
                    sources[index + 1]?.top().functionalArgument is LambdaInfo
                Opcodes.PUTFIELD, Opcodes.PUTSTATIC, Opcodes.ASTORE ->
                    sources[index]?.top().functionalArgument is LambdaInfo
                Opcodes.SWAP ->
                    sources[index]?.peek(0).functionalArgument is LambdaInfo || sources[index]?.peek(1).functionalArgument is LambdaInfo
                else -> false
            }
        }
    }

    // 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.
        val lambdaInfo = inliningContext.lambdaInfo ?: return
        if (lambdaInfo !is PsiExpressionLambda || !lambdaInfo.invokeMethodDescriptor.isSuspend) return
        val sources = analyzeMethodNodeWithInterpreter(processingNode, Aload0Interpreter(processingNode))
        val cfg = ControlFlowGraph.build(processingNode)
        val aload0s = processingNode.instructions.asSequence().filter { it.opcode == Opcodes.ALOAD && (it as? VarInsnNode)?.`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 != CONTINUATION_ASM_TYPE && 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, returnLabels: Map) {
        try {
            InplaceArgumentsMethodTransformer().transform("fake", node)
            FixStackWithLabelNormalizationMethodTransformer().transform("fake", node)
            TemporaryVariablesEliminationTransformer(inliningContext.state).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)
        }

        removeFakeVariablesInitializationIfPresent(node)

        val analyzer = FastStackAnalyzer("", node, FixStackInterpreter()) { nLocals, nStack -> Frame(nLocals, nStack) }
        val frames = analyzer.analyze()

        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

            // TODO extract isLocalReturn / isNonLocalReturn, see processReturns
            val labelName = getMarkedReturnLabelOrNull(insnNode)
            if (labelName == null) {
                localReturnsNormalizer.addLocalReturnToTransform(insnNode, insnNode, frame)
            } else if (labelName in returnLabels) {
                localReturnsNormalizer.addLocalReturnToTransform(insnNode, insnNode.previous, frame)
            }
        }

        localReturnsNormalizer.transform(node)
    }

    private fun removeFakeVariablesInitializationIfPresent(node: MethodNode) {
        // Before 1.6, we generated fake variable initialization instructions
        //      ICONST_0
        //      ISTORE x
        // for all inline functions. Original intent was to mark inline function body for the debugger with corresponding LVT entry.
        // However, for @InlineOnly functions corresponding LVT entries were not copied (assuming that nobody is actually debugging
        // @InlineOnly functions).
        // Since 1.6, we no longer generate fake variables for @InlineOnly functions
        // Here we erase fake variable initialization for @InlineOnly functions inlined into existing bytecode (e.g., inline function
        // inside third-party library).
        // We consider a sequence of instructions 'ICONST_0; ISTORE x' a fake variable initialization if the corresponding variable 'x'
        // is not used in the bytecode (see below).

        val insnArray = node.instructions.toArray()

        // Very conservative variable usage check.
        // Here we look at integer variables only (this includes integral primitive types: byte, char, short, boolean).
        // Variable is considered "used" if:
        //  - it's loaded with ILOAD instruction
        //  - it's incremented with IINC instruction
        //  - there's a local variable table entry for this variable
        val usedIntegerVar = BooleanArray(node.maxLocals)
        for (insn in insnArray) {
            if (insn.nodeType == AbstractInsnNode.VAR_INSN && insn.opcode == Opcodes.ILOAD) {
                usedIntegerVar[(insn as VarInsnNode).`var`] = true
            } else if (insn.nodeType == AbstractInsnNode.IINC_INSN) {
                usedIntegerVar[(insn as IincInsnNode).`var`] = true
            }
        }
        for (localVariable in node.localVariables) {
            val d0 = localVariable.desc[0]
            // byte || char || short || int || boolean
            if (d0 == 'B' || d0 == 'C' || d0 == 'S' || d0 == 'I' || d0 == 'Z') {
                usedIntegerVar[localVariable.index] = true
            }
        }

        // Looking for sequences of instructions:
        //  p0: ICONST_0
        //  p1: ISTORE x
        //  p2: 




© 2015 - 2024 Weber Informatics LLC | Privacy Policy