diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt index c810c7930b5..589449e22d5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt @@ -14,634 +14,531 @@ * limitations under the License. */ -package org.jetbrains.kotlin.codegen.inline; +package org.jetbrains.kotlin.codegen.inline -import com.google.common.collect.Lists; -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.codegen.ClosureCodegen; -import org.jetbrains.kotlin.codegen.StackValue; -import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods; -import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer; -import org.jetbrains.kotlin.codegen.optimization.fixStack.StackTransformationUtilsKt; -import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; -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.Method; -import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter; -import org.jetbrains.org.objectweb.asm.tree.*; -import org.jetbrains.org.objectweb.asm.tree.analysis.*; -import org.jetbrains.org.objectweb.asm.util.Printer; +import com.google.common.collect.Lists +import com.intellij.util.ArrayUtil +import org.jetbrains.kotlin.codegen.ClosureCodegen +import org.jetbrains.kotlin.codegen.StackValue +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.* +import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods +import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.fixStack.top +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.RemappingMethodAdapter +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.* +import org.jetbrains.org.objectweb.asm.util.Printer +import java.util.* -import java.util.*; - -import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.*; - -public class MethodInliner { - private final MethodNode node; - private final Parameters parameters; - private final InliningContext inliningContext; - private final FieldRemapper nodeRemapper; - private final boolean isSameModule; - private final String errorPrefix; - private final SourceMapper sourceMapper; - private final InlineCallSiteInfo inlineCallSiteInfo; - private final KotlinTypeMapper typeMapper; - private final List invokeCalls = new ArrayList(); +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: SourceMapper, + private val inlineCallSiteInfo: InlineCallSiteInfo, + private val inlineOnlySmapSkipper: InlineOnlySmapSkipper? //non null only for root +) { + private val typeMapper = inliningContext.state.typeMapper + private val invokeCalls = ArrayList() //keeps order - private final List transformations = new ArrayList(); + private val transformations = ArrayList() //current state - private final Map currentTypeMapping = new HashMap(); - private final InlineResult result; - private int lambdasFinallyBlocks; - private final InlineOnlySmapSkipper inlineOnlySmapSkipper; + private val currentTypeMapping = HashMap() + private val result = InlineResult.create() + private var lambdasFinallyBlocks: Int = 0 - public MethodInliner( - @NotNull MethodNode node, - @NotNull Parameters parameters, - @NotNull InliningContext inliningContext, - @NotNull FieldRemapper nodeRemapper, - boolean isSameModule, - @NotNull String errorPrefix, - @NotNull SourceMapper sourceMapper, - @NotNull InlineCallSiteInfo inlineCallSiteInfo, - @Nullable InlineOnlySmapSkipper smapSkipper //non null only for root - ) { - this.node = node; - this.parameters = parameters; - this.inliningContext = inliningContext; - this.nodeRemapper = nodeRemapper; - this.isSameModule = isSameModule; - this.errorPrefix = errorPrefix; - this.sourceMapper = sourceMapper; - this.inlineCallSiteInfo = inlineCallSiteInfo; - this.typeMapper = inliningContext.state.getTypeMapper(); - this.result = InlineResult.create(); - this.inlineOnlySmapSkipper = smapSkipper; + fun doInline( + adapter: MethodVisitor, + remapper: LocalVarRemapper, + remapReturn: Boolean, + labelOwner: LabelOwner + ): InlineResult { + return doInline(adapter, remapper, remapReturn, labelOwner, 0) } - @NotNull - public InlineResult doInline( - @NotNull MethodVisitor adapter, - @NotNull LocalVarRemapper remapper, - boolean remapReturn, - @NotNull LabelOwner labelOwner - ) { - return doInline(adapter, remapper, remapReturn, labelOwner, 0); - } - - @NotNull - private InlineResult doInline( - @NotNull MethodVisitor adapter, - @NotNull LocalVarRemapper remapper, - boolean remapReturn, - @NotNull LabelOwner labelOwner, - int finallyDeepShift - ) { + private fun doInline( + adapter: MethodVisitor, + remapper: LocalVarRemapper, + remapReturn: Boolean, + labelOwner: LabelOwner, + finallyDeepShift: Int + ): InlineResult { //analyze body - MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node, labelOwner, finallyDeepShift); + var transformedNode = markPlacesForInlineAndRemoveInlinable(node, labelOwner, finallyDeepShift) //substitute returns with "goto end" instruction to keep non local returns in lambdas - Label end = new Label(); - transformedNode = doInline(transformedNode); - removeClosureAssertions(transformedNode); - transformedNode.instructions.resetLabels(); + val end = Label() + transformedNode = doInline(transformedNode) + removeClosureAssertions(transformedNode) + transformedNode.instructions.resetLabels() - MethodNode resultNode = new MethodNode( + val resultNode = MethodNode( InlineCodegenUtil.API, transformedNode.access, transformedNode.name, transformedNode.desc, transformedNode.signature, ArrayUtil.toStringArray(transformedNode.exceptions) - ); - RemapVisitor visitor = new RemapVisitor(resultNode, remapper, nodeRemapper); + ) + val visitor = RemapVisitor(resultNode, remapper, nodeRemapper) try { - transformedNode.accept(visitor); + transformedNode.accept(visitor) } - catch (Throwable e) { - throw wrapException(e, transformedNode, "couldn't inline method call"); + catch (e: Throwable) { + throw wrapException(e, transformedNode, "couldn't inline method call") } - resultNode.visitLabel(end); + resultNode.visitLabel(end) - if (inliningContext.isRoot()) { - StackValue remapValue = remapper.remap(parameters.getArgsSizeOnStack() + 1).value; + if (inliningContext.isRoot) { + val remapValue = remapper.remap(parameters.argsSizeOnStack + 1).value InternalFinallyBlockInliner.processInlineFunFinallyBlocks( - resultNode, lambdasFinallyBlocks, ((StackValue.Local) remapValue).index - ); + resultNode, lambdasFinallyBlocks, (remapValue as StackValue.Local).index + ) } - processReturns(resultNode, labelOwner, remapReturn, end); + processReturns(resultNode, labelOwner, remapReturn, end) //flush transformed node to output - resultNode.accept(new MethodBodyVisitor(adapter)); + resultNode.accept(MethodBodyVisitor(adapter)) - sourceMapper.endMapping(); - return result; + sourceMapper.endMapping() + return result } - @NotNull - private MethodNode doInline(@NotNull MethodNode node) { - final Deque currentInvokes = new LinkedList(invokeCalls); + private fun doInline(node: MethodNode): MethodNode { + val currentInvokes = LinkedList(invokeCalls) - final MethodNode resultNode = new MethodNode(node.access, node.name, node.desc, node.signature, null); + val resultNode = MethodNode(node.access, node.name, node.desc, node.signature, null) - final Iterator iterator = transformations.iterator(); + val iterator = transformations.iterator() - final TypeRemapper remapper = TypeRemapper.createFrom(currentTypeMapping); - final RemappingMethodAdapter remappingMethodAdapter = new RemappingMethodAdapter( + val remapper = TypeRemapper.createFrom(currentTypeMapping) + val remappingMethodAdapter = RemappingMethodAdapter( resultNode.access, resultNode.desc, resultNode, - new AsmTypeRemapper(remapper, inliningContext.getRoot().typeParameterMappings == null, result) - ); + AsmTypeRemapper(remapper, inliningContext.root.typeParameterMappings == null, result) + ) - final int markerShift = InlineCodegenUtil.calcMarkerShift(parameters, node); - InlineAdapter lambdaInliner = new InlineAdapter(remappingMethodAdapter, parameters.getArgsSizeOnStack(), sourceMapper) { - private TransformationInfo transformationInfo; + val markerShift = InlineCodegenUtil.calcMarkerShift(parameters, node) + val lambdaInliner = object : InlineAdapter(remappingMethodAdapter, parameters.argsSizeOnStack, sourceMapper) { + private var transformationInfo: TransformationInfo? = null - private void handleAnonymousObjectRegeneration() { - transformationInfo = iterator.next(); + private fun handleAnonymousObjectRegeneration() { + transformationInfo = iterator.next() - String oldClassName = transformationInfo.getOldClassName(); - if (transformationInfo.shouldRegenerate(isSameModule)) { + val oldClassName = transformationInfo!!.oldClassName + if (transformationInfo!!.shouldRegenerate(isSameModule)) { //TODO: need poping of type but what to do with local funs??? - String newClassName = transformationInfo.getNewClassName(); - remapper.addMapping(oldClassName, newClassName); + val newClassName = transformationInfo!!.newClassName + remapper.addMapping(oldClassName, newClassName) - InliningContext childInliningContext = inliningContext.subInlineWithClassRegeneration( + val childInliningContext = inliningContext.subInlineWithClassRegeneration( inliningContext.nameGenerator, currentTypeMapping, inlineCallSiteInfo - ); - ObjectTransformer transformer = transformationInfo.createTransformer(childInliningContext, isSameModule); + ) + val transformer = transformationInfo!!.createTransformer(childInliningContext, isSameModule) - InlineResult transformResult = transformer.doTransform(nodeRemapper); - result.merge(transformResult); - result.addChangedType(oldClassName, newClassName); + val transformResult = transformer.doTransform(nodeRemapper) + result.merge(transformResult) + result.addChangedType(oldClassName, newClassName) - if (inliningContext.isInliningLambda && transformationInfo.canRemoveAfterTransformation()) { + if (inliningContext.isInliningLambda && transformationInfo!!.canRemoveAfterTransformation()) { // this class is transformed and original not used so we should remove original one after inlining - result.addClassToRemove(oldClassName); + result.addClassToRemove(oldClassName) } - if (transformResult.getReifiedTypeParametersUsages().wereUsedReifiedParameters()) { - ReifiedTypeInliner.putNeedClassReificationMarker(mv); - result.getReifiedTypeParametersUsages().mergeAll(transformResult.getReifiedTypeParametersUsages()); + if (transformResult.reifiedTypeParametersUsages.wereUsedReifiedParameters()) { + ReifiedTypeInliner.putNeedClassReificationMarker(mv) + result.reifiedTypeParametersUsages.mergeAll(transformResult.reifiedTypeParametersUsages) } } - else if (!transformationInfo.getWasAlreadyRegenerated()) { - result.addNotChangedClass(oldClassName); + else if (!transformationInfo!!.wasAlreadyRegenerated) { + result.addNotChangedClass(oldClassName) } } - @Override - public void anew(@NotNull Type type) { - if (isAnonymousClass(type.getInternalName())) { - handleAnonymousObjectRegeneration(); + override fun anew(type: Type) { + if (isAnonymousClass(type.internalName)) { + handleAnonymousObjectRegeneration() } //in case of regenerated transformationInfo type would be remapped to new one via remappingMethodAdapter - super.anew(type); + super.anew(type) } - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + 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(); - InvokeCall invokeCall = currentInvokes.remove(); - LambdaInfo info = invokeCall.lambdaInfo; + assert(!currentInvokes.isEmpty()) + val invokeCall = currentInvokes.remove() + val info = invokeCall.lambdaInfo if (info == null) { //noninlinable lambda - super.visitMethodInsn(opcode, owner, name, desc, itf); - return; + super.visitMethodInsn(opcode, owner, name, desc, itf) + return } - int valueParamShift = Math.max(getNextLocalIndex(), markerShift);//NB: don't inline cause it changes - putStackValuesIntoLocals(Arrays.asList(info.getInvokeMethod().getArgumentTypes()), valueParamShift, this, desc); + val valueParamShift = Math.max(nextLocalIndex, markerShift)//NB: don't inline cause it changes + putStackValuesIntoLocals(Arrays.asList(*info.invokeMethod.argumentTypes), valueParamShift, this, desc) - if (invokeCall.lambdaInfo.getInvokeMethodDescriptor().getValueParameters().isEmpty()) { + + if (invokeCall.lambdaInfo.invokeMethodDescriptor.getValueParameters().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); + visitInsn(Opcodes.NOP) } - addInlineMarker(this, true); - Parameters lambdaParameters = info.addAllParameters(nodeRemapper); + addInlineMarker(this, true) + val lambdaParameters = info.addAllParameters(nodeRemapper) - InlinedLambdaRemapper newCapturedRemapper = - new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), nodeRemapper, lambdaParameters); + val newCapturedRemapper = InlinedLambdaRemapper(info.lambdaClassType.internalName, nodeRemapper, lambdaParameters) - setLambdaInlining(true); - SMAP lambdaSMAP = info.getNode().getClassSMAP(); - //noinspection ConstantConditions - SourceMapper mapper = - inliningContext.classRegeneration && !inliningContext.isInliningLambda - ? new NestedSourceMapper(sourceMapper, lambdaSMAP.getIntervals(), lambdaSMAP.getSourceInfo()) - : new InlineLambdaSourceMapper(sourceMapper.getParent(), info.getNode()); - MethodInliner inliner = new MethodInliner( - info.getNode().getNode(), lambdaParameters, inliningContext.subInlineLambda(info), + setLambdaInlining(true) + val lambdaSMAP = info.node.classSMAP + + val mapper = if (inliningContext.classRegeneration && !inliningContext.isInliningLambda) + NestedSourceMapper(sourceMapper, lambdaSMAP.intervals, lambdaSMAP.sourceInfo) + else + InlineLambdaSourceMapper(sourceMapper.parent!!, info.node) + val inliner = MethodInliner( + info.node.node, lambdaParameters, inliningContext.subInlineLambda(info), newCapturedRemapper, true /*cause all calls in same module as lambda*/, - "Lambda inlining " + info.getLambdaClassType().getInternalName(), + "Lambda inlining " + info.lambdaClassType.internalName, mapper, inlineCallSiteInfo, null - ); + ) - LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift); + val remapper = LocalVarRemapper(lambdaParameters, valueParamShift) //TODO add skipped this and receiver - InlineResult lambdaResult = inliner.doInline(this.mv, remapper, true, info, invokeCall.finallyDepthShift); - result.mergeWithNotChangeInfo(lambdaResult); - result.getReifiedTypeParametersUsages().mergeAll(lambdaResult.getReifiedTypeParametersUsages()); + val lambdaResult = inliner.doInline(this.mv, remapper, true, info, invokeCall.finallyDepthShift) + result.mergeWithNotChangeInfo(lambdaResult) + result.reifiedTypeParametersUsages.mergeAll(lambdaResult.reifiedTypeParametersUsages) //return value boxing/unboxing - Method bridge = typeMapper.mapAsmMethod(ClosureCodegen.getErasedInvokeFunction(info.getInvokeMethodDescriptor())); - StackValue.onStack(info.getInvokeMethod().getReturnType()).put(bridge.getReturnType(), this); - setLambdaInlining(false); - addInlineMarker(this, false); - mapper.endMapping(); - if (inlineOnlySmapSkipper != null) { - inlineOnlySmapSkipper.markCallSiteLineNumber(remappingMethodAdapter); - } + val bridge = typeMapper.mapAsmMethod(ClosureCodegen.getErasedInvokeFunction(info.invokeMethodDescriptor)) + StackValue.onStack(info.invokeMethod.returnType).put(bridge.returnType, this) + setLambdaInlining(false) + addInlineMarker(this, false) + mapper.endMapping() + inlineOnlySmapSkipper?.markCallSiteLineNumber(remappingMethodAdapter) } else if (isAnonymousConstructorCall(owner, name)) { //TODO add method //TODO add proper message - assert transformationInfo instanceof AnonymousObjectTransformationInfo : - " call doesn't correspond to object transformation info: " + - owner + "." + name + ", info " + transformationInfo; - InliningContext parent = inliningContext.getParent(); - boolean shouldRegenerate = transformationInfo.shouldRegenerate(isSameModule); - boolean isContinuation = parent != null && parent.isContinuation(); + assert(transformationInfo is AnonymousObjectTransformationInfo) { + " call doesn't correspond to object transformation info: " + + owner + "." + name + ", info " + transformationInfo + } + val parent = inliningContext.parent + val shouldRegenerate = transformationInfo!!.shouldRegenerate(isSameModule) + val isContinuation = parent != null && parent.isContinuation if (shouldRegenerate || isContinuation) { - assert shouldRegenerate || inlineCallSiteInfo.getOwnerClassName().equals(transformationInfo.getOldClassName()) - : "Only coroutines can call their own constructors"; + assert(shouldRegenerate || inlineCallSiteInfo.ownerClassName == transformationInfo!!.oldClassName) { "Only coroutines can call their own constructors" } //put additional captured parameters on stack - AnonymousObjectTransformationInfo info = (AnonymousObjectTransformationInfo) transformationInfo; + var info = transformationInfo as AnonymousObjectTransformationInfo - AnonymousObjectTransformationInfo oldInfo = inliningContext.findAnonymousObjectTransformationInfo(owner); + val oldInfo = inliningContext.findAnonymousObjectTransformationInfo(owner) if (oldInfo != null && isContinuation) { - info = oldInfo; + info = oldInfo } - for (CapturedParamDesc capturedParamDesc : info.getAllRecapturedParameters()) { + for (capturedParamDesc in info.allRecapturedParameters) { visitFieldInsn( - Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(), - "$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor() - ); + Opcodes.GETSTATIC, capturedParamDesc.containingLambdaName, + "$$$" + capturedParamDesc.fieldName, capturedParamDesc.type.descriptor + ) } - super.visitMethodInsn(opcode, info.getNewClassName(), name, info.getNewConstructorDescriptor(), itf); + super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf) //TODO: add new inner class also for other contexts - if (inliningContext.getParent() instanceof RegeneratedClassContext) { - inliningContext.getParent().typeRemapper.addAdditionalMappings( - transformationInfo.getOldClassName(), transformationInfo.getNewClassName() - ); + if (inliningContext.parent is RegeneratedClassContext) { + inliningContext.parent!!.typeRemapper.addAdditionalMappings( + transformationInfo!!.oldClassName, transformationInfo!!.newClassName + ) } - transformationInfo = null; + transformationInfo = null } else { - super.visitMethodInsn(opcode, owner, name, desc, itf); + super.visitMethodInsn(opcode, owner, name, desc, itf) } } - else if (!inliningContext.isInliningLambda && - ReifiedTypeInliner.isNeedClassReificationMarker(new MethodInsnNode(opcode, owner, name, desc, false))) { + else if (!inliningContext.isInliningLambda && ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))) { //we shouldn't process here content of inlining lambda it should be reified at external level } else { - super.visitMethodInsn(opcode, owner, name, desc, itf); + super.visitMethodInsn(opcode, owner, name, desc, itf) } } - @Override - public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) { + override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) { if (opcode == Opcodes.GETSTATIC && (isAnonymousSingletonLoad(owner, name) || isWhenMappingAccess(owner, name))) { - handleAnonymousObjectRegeneration(); + handleAnonymousObjectRegeneration() } - super.visitFieldInsn(opcode, owner, name, desc); + super.visitFieldInsn(opcode, owner, name, desc) } - @Override - public void visitMaxs(int stack, int locals) { - lambdasFinallyBlocks = resultNode.tryCatchBlocks.size(); - super.visitMaxs(stack, locals); + override fun visitMaxs(stack: Int, locals: Int) { + lambdasFinallyBlocks = resultNode.tryCatchBlocks.size + super.visitMaxs(stack, locals) } - }; - - node.accept(lambdaInliner); - - return resultNode; - } - - @NotNull - public static CapturedParamInfo findCapturedField(@NotNull FieldInsnNode node, @NotNull FieldRemapper fieldRemapper) { - assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix"; - FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc); - CapturedParamInfo field = fieldRemapper.findField(fin); - if (field == null) { - throw new IllegalStateException( - "Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName() - ); } - return field; + + node.accept(lambdaInliner) + + return resultNode } - @NotNull - private MethodNode prepareNode(@NotNull MethodNode node, int finallyDeepShift) { - final int capturedParamsSize = parameters.getCapturedParametersSizeOnStack(); - final int realParametersSize = parameters.getRealParametersSizeOnStack(); - Type[] types = Type.getArgumentTypes(node.desc); - Type returnType = Type.getReturnType(node.desc); + private fun prepareNode(node: MethodNode, finallyDeepShift: Int): MethodNode { + val capturedParamsSize = parameters.capturedParametersSizeOnStack + val realParametersSize = parameters.realParametersSizeOnStack + val types = Type.getArgumentTypes(node.desc) + val returnType = Type.getReturnType(node.desc) - List capturedTypes = parameters.getCapturedTypes(); - Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()])); + val capturedTypes = parameters.capturedTypes + val allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toTypedArray()) - node.instructions.resetLabels(); - MethodNode transformedNode = new MethodNode( - InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null + node.instructions.resetLabels() + val transformedNode = object : MethodNode( + InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, *allTypes), node.signature, null ) { - @SuppressWarnings("ConstantConditions") - private final boolean GENERATE_DEBUG_INFO = InlineCodegenUtil.GENERATE_SMAP && inlineOnlySmapSkipper == null; + private val GENERATE_DEBUG_INFO = InlineCodegenUtil.GENERATE_SMAP && inlineOnlySmapSkipper == null - private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda(); + private val isInliningLambda = nodeRemapper.isInsideInliningLambda - private int getNewIndex(int var) { - return var + (var < realParametersSize ? 0 : capturedParamsSize); + private fun getNewIndex(`var`: Int): Int { + return `var` + if (`var` < realParametersSize) 0 else capturedParamsSize } - @Override - public void visitVarInsn(int opcode, int var) { - super.visitVarInsn(opcode, getNewIndex(var)); + override fun visitVarInsn(opcode: Int, `var`: Int) { + super.visitVarInsn(opcode, getNewIndex(`var`)) } - @Override - public void visitIincInsn(int var, int increment) { - super.visitIincInsn(getNewIndex(var), increment); + override fun visitIincInsn(`var`: Int, increment: Int) { + super.visitIincInsn(getNewIndex(`var`), increment) } - @Override - public void visitMaxs(int maxStack, int maxLocals) { - super.visitMaxs(maxStack, maxLocals + capturedParamsSize); + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + super.visitMaxs(maxStack, maxLocals + capturedParamsSize) } - @Override - public void visitLineNumber(int line, @NotNull Label start) { + override fun visitLineNumber(line: Int, start: Label) { if (isInliningLambda || GENERATE_DEBUG_INFO) { - super.visitLineNumber(line, start); + super.visitLineNumber(line, start) } } - @Override - public void visitLocalVariable( - @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index + override fun visitLocalVariable( + name: String, desc: String, signature: String?, start: Label, end: Label, index: Int ) { if (isInliningLambda || GENERATE_DEBUG_INFO) { - String varSuffix = - inliningContext.isRoot() && !InlineCodegenUtil.isFakeLocalVariableForInline(name) ? INLINE_FUN_VAR_SUFFIX : ""; - String varName = !varSuffix.isEmpty() && name.equals("this") ? name + "_" : name; - super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index)); + val varSuffix = if (inliningContext.isRoot && !InlineCodegenUtil.isFakeLocalVariableForInline(name)) INLINE_FUN_VAR_SUFFIX else "" + val varName = if (!varSuffix.isEmpty() && name == "this") name + "_" else name + super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index)) } } - }; + } - node.accept(transformedNode); + node.accept(transformedNode) - transformCaptured(transformedNode); - transformFinallyDeepIndex(transformedNode, finallyDeepShift); + transformCaptured(transformedNode) + transformFinallyDeepIndex(transformedNode, finallyDeepShift) - return transformedNode; + return transformedNode } - @NotNull - private MethodNode markPlacesForInlineAndRemoveInlinable( - @NotNull MethodNode node, @NotNull LabelOwner labelOwner, int finallyDeepShift - ) { - node = prepareNode(node, finallyDeepShift); + private fun markPlacesForInlineAndRemoveInlinable( + node: MethodNode, labelOwner: LabelOwner, finallyDeepShift: Int + ): MethodNode { + val processingNode = prepareNode(node, finallyDeepShift) - normalizeLocalReturns(node, labelOwner); + normalizeLocalReturns(processingNode, labelOwner) - Frame[] sources = analyzeMethodNodeWithoutMandatoryTransformations(node); + val sources = analyzeMethodNodeWithoutMandatoryTransformations(processingNode) - Set toDelete = SmartSet.create(); - InsnList instructions = node.instructions; - AbstractInsnNode cur = instructions.getFirst(); + val toDelete = SmartSet.create() + val instructions = processingNode.instructions + var cur: AbstractInsnNode? = instructions.first - boolean awaitClassReification = false; - int currentFinallyDeep = 0; + var awaitClassReification = false + var currentFinallyDeep = 0 while (cur != null) { - Frame frame = sources[instructions.indexOf(cur)]; + val frame: Frame? = sources[instructions.indexOf(cur)] if (frame != null) { if (ReifiedTypeInliner.isNeedClassReificationMarker(cur)) { - awaitClassReification = true; + awaitClassReification = true } - else if (cur.getType() == AbstractInsnNode.METHOD_INSN) { + else if (cur is MethodInsnNode) { if (InlineCodegenUtil.isFinallyStart(cur)) { //TODO deep index calc could be more precise - currentFinallyDeep = InlineCodegenUtil.getConstant(cur.getPrevious()); + currentFinallyDeep = InlineCodegenUtil.getConstant(cur.previous) } - MethodInsnNode methodInsnNode = (MethodInsnNode) cur; - String owner = methodInsnNode.owner; - String desc = methodInsnNode.desc; - String name = methodInsnNode.name; + val owner = cur.owner + val name = cur.name //TODO check closure - Type[] argTypes = Type.getArgumentTypes(desc); - int paramCount = argTypes.length + 1;//non static - int firstParameterIndex = frame.getStackSize() - paramCount; + 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)*/) { - SourceValue sourceValue = frame.getStack(firstParameterIndex); + val sourceValue = frame.getStack(firstParameterIndex) - LambdaInfo lambdaInfo = MethodInlinerUtilKt.getLambdaIfExistsAndMarkInstructions( - this, sourceValue, true, instructions, sources, toDelete - ); + val lambdaInfo = getLambdaIfExistsAndMarkInstructions(sourceValue, true, instructions, sources, toDelete + ) - invokeCalls.add(new InvokeCall(lambdaInfo, currentFinallyDeep)); + invokeCalls.add(InvokeCall(lambdaInfo, currentFinallyDeep)) } else if (isAnonymousConstructorCall(owner, name)) { - Map lambdaMapping = new HashMap(); + val lambdaMapping = HashMap() - int offset = 0; - boolean capturesAnonymousObjectThatMustBeRegenerated = false; - for (int i = 0; i < paramCount; i++) { - SourceValue sourceValue = frame.getStack(firstParameterIndex + i); - LambdaInfo lambdaInfo = MethodInlinerUtilKt.getLambdaIfExistsAndMarkInstructions( - this, sourceValue, false, instructions, sources, toDelete - ); + var offset = 0 + var capturesAnonymousObjectThatMustBeRegenerated = false + for (i in 0..paramCount - 1) { + val sourceValue = frame.getStack(firstParameterIndex + i) + val lambdaInfo = getLambdaIfExistsAndMarkInstructions(sourceValue, false, instructions, sources, toDelete + ) if (lambdaInfo != null) { - lambdaMapping.put(offset, lambdaInfo); + lambdaMapping.put(offset, lambdaInfo) } - else if (i < argTypes.length && isAnonymousClassThatMustBeRegenerated(argTypes[i])) { - capturesAnonymousObjectThatMustBeRegenerated = true; + else if (i < argTypes.size && isAnonymousClassThatMustBeRegenerated(argTypes[i])) { + capturesAnonymousObjectThatMustBeRegenerated = true } - offset += i == 0 ? 1 : argTypes[i - 1].getSize(); + offset += if (i == 0) 1 else argTypes[i - 1].size } transformations.add( buildConstructorInvocation( - owner, desc, lambdaMapping, awaitClassReification, capturesAnonymousObjectThatMustBeRegenerated + owner, cur.desc, lambdaMapping, awaitClassReification, capturesAnonymousObjectThatMustBeRegenerated ) - ); - awaitClassReification = false; + ) + awaitClassReification = false } - else if (inliningContext.isInliningLambda && ReifiedTypeInliner.Companion.isOperationReifiedMarker(cur)) { - ReificationArgument reificationArgument = ReifiedTypeInlinerKt.getReificationArgument((MethodInsnNode) cur); - String parameterName = reificationArgument.getParameterName(); - result.getReifiedTypeParametersUsages().addUsedReifiedParameter(parameterName); + else if (inliningContext.isInliningLambda && ReifiedTypeInliner.isOperationReifiedMarker(cur)) { + val reificationArgument = cur.reificationArgument + val parameterName = reificationArgument!!.parameterName + result.reifiedTypeParametersUsages.addUsedReifiedParameter(parameterName) } } - else if (cur.getOpcode() == Opcodes.GETSTATIC) { - FieldInsnNode fieldInsnNode = (FieldInsnNode) cur; - String className = fieldInsnNode.owner; + else if (cur.opcode == Opcodes.GETSTATIC) { + val fieldInsnNode = cur as FieldInsnNode? + val className = fieldInsnNode!!.owner if (isAnonymousSingletonLoad(className, fieldInsnNode.name)) { transformations.add( - new AnonymousObjectTransformationInfo( + AnonymousObjectTransformationInfo( className, awaitClassReification, isAlreadyRegenerated(className), true, inliningContext.nameGenerator ) - ); - awaitClassReification = false; + ) + awaitClassReification = false } else if (isWhenMappingAccess(className, fieldInsnNode.name)) { transformations.add( - new WhenMappingTransformationInfo( - className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode - ) - ); + WhenMappingTransformationInfo( + className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode + ) + ) } } - else if (cur.getOpcode() == Opcodes.POP) { - SourceValue top = StackTransformationUtilsKt.top(frame); - LambdaInfo lambdaInfo = MethodInlinerUtilKt.getLambdaIfExistsAndMarkInstructions( - this, top, true, instructions, sources, toDelete - ); + else if (cur.opcode == Opcodes.POP) { + val top = frame.top()!! + val lambdaInfo = getLambdaIfExistsAndMarkInstructions(top, true, instructions, sources, toDelete) if (lambdaInfo != null) { - toDelete.add(cur); + toDelete.add(cur) } } } - AbstractInsnNode prevNode = cur; - cur = cur.getNext(); + val prevNode = cur + cur = cur.next //given frame is null if and only if the corresponding instruction cannot be reached (dead code). if (frame == null) { //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems - if (prevNode.getType() == AbstractInsnNode.LABEL) { + if (prevNode.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(prevNode); + toDelete.add(prevNode) } } } - MethodInlinerUtilKt.remove(node, toDelete); + processingNode.remove(toDelete) //clean dead try/catch blocks - List blocks = node.tryCatchBlocks; - blocks.removeIf(MethodInliner::isEmptyTryInterval); + val blocks = processingNode.tryCatchBlocks + blocks.removeIf{ isEmptyTryInterval(it) } - return node; + return processingNode } - private void normalizeLocalReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner) { - Frame[] frames = analyzeMethodNodeBeforeInline(node); + private fun normalizeLocalReturns(node: MethodNode, labelOwner: LabelOwner) { + val frames = analyzeMethodNodeBeforeInline(node) - LocalReturnsNormalizer localReturnsNormalizer = new LocalReturnsNormalizer(); + val localReturnsNormalizer = LocalReturnsNormalizer() - AbstractInsnNode[] instructions = node.instructions.toArray(); + val instructions = node.instructions.toArray() - for (int i = 0; i < instructions.length; ++i) { - Frame frame = frames[i]; + for (i in instructions.indices) { + val frame = frames[i] ?: continue // Don't care about dead code, it will be eliminated - if (frame == null) continue; - AbstractInsnNode insnNode = instructions[i]; - if (!InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) continue; + val insnNode = instructions[i] + if (!InlineCodegenUtil.isReturnOpcode(insnNode.opcode)) continue - AbstractInsnNode insertBeforeInsn = insnNode; + var insertBeforeInsn = insnNode // TODO extract isLocalReturn / isNonLocalReturn, see processReturns - String labelName = getMarkedReturnLabelOrNull(insnNode); + val labelName = getMarkedReturnLabelOrNull(insnNode) if (labelName != null) { - if (!labelOwner.isMyLabel(labelName)) continue; - insertBeforeInsn = insnNode.getPrevious(); + if (!labelOwner.isMyLabel(labelName)) continue + insertBeforeInsn = insnNode.previous } - localReturnsNormalizer.addLocalReturnToTransform(insnNode, insertBeforeInsn, frame); + localReturnsNormalizer.addLocalReturnToTransform(insnNode, insertBeforeInsn, frame) } - localReturnsNormalizer.transform(node); + localReturnsNormalizer.transform(node) } - private boolean isAnonymousClassThatMustBeRegenerated(@Nullable Type type) { - if (type == null || type.getSort() != Type.OBJECT) return false; - AnonymousObjectTransformationInfo info = inliningContext.findAnonymousObjectTransformationInfo(type.getInternalName()); - return info != null && info.shouldRegenerate(true); + private fun isAnonymousClassThatMustBeRegenerated(type: Type?): Boolean { + if (type == null || type.sort != Type.OBJECT) return false + val info = inliningContext.findAnonymousObjectTransformationInfo(type.internalName) + return info != null && info.shouldRegenerate(true) } - @NotNull - private Frame[] analyzeMethodNodeBeforeInline(@NotNull MethodNode node) { + private fun analyzeMethodNodeBeforeInline(node: MethodNode): Array?> { try { - new FixStackWithLabelNormalizationMethodTransformer().transform("fake", node); + FixStackWithLabelNormalizationMethodTransformer().transform("fake", node) } - catch (Throwable e) { - throw wrapException(e, node, "couldn't inline method call"); + catch (e: Throwable) { + throw wrapException(e, node, "couldn't inline method call") } - return analyzeMethodNodeWithoutMandatoryTransformations(node); + return analyzeMethodNodeWithoutMandatoryTransformations(node) } - private static Frame[] analyzeMethodNodeWithoutMandatoryTransformations(@NotNull MethodNode node) { - Analyzer analyzer = new Analyzer(new SourceInterpreter()) { - @NotNull - @Override - protected Frame newFrame(int nLocals, int nStack) { - return new Frame(nLocals, nStack) { - @Override - public void execute(@NotNull AbstractInsnNode insn, Interpreter interpreter) throws AnalyzerException { - // This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else. - if (insn.getOpcode() == Opcodes.RETURN) return; - super.execute(insn, interpreter); - } - }; - } - }; + private fun buildConstructorInvocation( + anonymousType: String, + desc: String, + lambdaMapping: Map, + needReification: Boolean, + capturesAnonymousObjectThatMustBeRegenerated: Boolean + ): AnonymousObjectTransformationInfo { + val memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType) == null - try { - return analyzer.analyze("fake", node); - } - catch (AnalyzerException e) { - throw new RuntimeException(e); - } - } - - private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) { - LabelNode start = tryCatchBlockNode.start; - AbstractInsnNode end = tryCatchBlockNode.end; - while (end != start && end instanceof LabelNode) { - end = end.getPrevious(); - } - return start == end; - } - - @NotNull - private AnonymousObjectTransformationInfo buildConstructorInvocation( - @NotNull String anonymousType, - @NotNull String desc, - @NotNull Map lambdaMapping, - boolean needReification, - boolean capturesAnonymousObjectThatMustBeRegenerated - ) { - boolean memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType) == null; - - AnonymousObjectTransformationInfo info = new AnonymousObjectTransformationInfo( + val info = AnonymousObjectTransformationInfo( anonymousType, needReification, lambdaMapping, inliningContext.classRegeneration, isAlreadyRegenerated(anonymousType), @@ -649,314 +546,334 @@ public class MethodInliner { false, inliningContext.nameGenerator, capturesAnonymousObjectThatMustBeRegenerated - ); + ) if (memoizeAnonymousObject) { - inliningContext.getRoot().internalNameToAnonymousObjectTransformationInfo.put(anonymousType, info); + inliningContext.root.internalNameToAnonymousObjectTransformationInfo.put(anonymousType, info) } - return info; + return info } - private boolean isAlreadyRegenerated(@NotNull String owner) { - return inliningContext.typeRemapper.hasNoAdditionalMapping(owner); + private fun isAlreadyRegenerated(owner: String): Boolean { + return inliningContext.typeRemapper.hasNoAdditionalMapping(owner) } - @Nullable - LambdaInfo getLambdaIfExists(@Nullable AbstractInsnNode insnNode) { + internal fun getLambdaIfExists(insnNode: AbstractInsnNode?): LambdaInfo? { if (insnNode == null) { - return null; + return null } - if (insnNode.getOpcode() == Opcodes.ALOAD) { - int varIndex = ((VarInsnNode) insnNode).var; - return getLambdaIfExists(varIndex); + if (insnNode.opcode == Opcodes.ALOAD) { + val varIndex = (insnNode as VarInsnNode).`var` + return getLambdaIfExists(varIndex) } - if (insnNode instanceof FieldInsnNode) { - FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode; - if (fieldInsnNode.name.startsWith("$$$")) { - return findCapturedField(fieldInsnNode, nodeRemapper).getLambda(); + if (insnNode is FieldInsnNode) { + val fieldInsnNode = insnNode as FieldInsnNode? + if (fieldInsnNode!!.name.startsWith("$$$")) { + return findCapturedField(fieldInsnNode, nodeRemapper).lambda } } - return null; + return null } - @Nullable - private LambdaInfo getLambdaIfExists(int varIndex) { - if (varIndex < parameters.getArgsSizeOnStack()) { - return parameters.getParameterByDeclarationSlot(varIndex).getLambda(); + private fun getLambdaIfExists(varIndex: Int): LambdaInfo? { + if (varIndex < parameters.argsSizeOnStack) { + return parameters.getParameterByDeclarationSlot(varIndex).lambda } - return null; + return null } - private static void removeClosureAssertions(@NotNull MethodNode node) { - AbstractInsnNode cur = node.instructions.getFirst(); - while (cur != null && cur.getNext() != null) { - AbstractInsnNode next = cur.getNext(); - if (next.getType() == AbstractInsnNode.METHOD_INSN) { - MethodInsnNode methodInsnNode = (MethodInsnNode) next; - if (methodInsnNode.name.equals("checkParameterIsNotNull") && - methodInsnNode.owner.equals(IntrinsicMethods.INTRINSICS_CLASS_NAME)) { - AbstractInsnNode prev = cur.getPrevious(); - - assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur; - assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev; - - node.instructions.remove(prev); - node.instructions.remove(cur); - cur = next.getNext(); - node.instructions.remove(next); - next = cur; - } - } - cur = next; - } - } - - private void transformCaptured(@NotNull MethodNode node) { - if (nodeRemapper.isRoot()) { - return; + private fun transformCaptured(node: MethodNode) { + if (nodeRemapper.isRoot) { + return } //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0) - AbstractInsnNode cur = node.instructions.getFirst(); + var cur: AbstractInsnNode? = node.instructions.first while (cur != null) { - if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) { - int varIndex = ((VarInsnNode) cur).var; + if (cur is VarInsnNode && cur.opcode == Opcodes.ALOAD) { + val varIndex = cur.`var` if (varIndex == 0 || nodeRemapper.processNonAload0FieldAccessChains(getLambdaIfExists(varIndex) != null)) { - List accessChain = getCapturedFieldAccessChain((VarInsnNode) cur); - AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node); + val accessChain = getCapturedFieldAccessChain((cur as VarInsnNode?)!!) + val insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node) if (insnNode != null) { - cur = insnNode; + cur = insnNode } } } - cur = cur.getNext(); + cur = cur.next } } - private static void transformFinallyDeepIndex(@NotNull MethodNode node, int finallyDeepShift) { - if (finallyDeepShift == 0) { - return; - } - - AbstractInsnNode cur = node.instructions.getFirst(); - while (cur != null) { - if (cur instanceof MethodInsnNode && InlineCodegenUtil.isFinallyMarker(cur)) { - AbstractInsnNode constant = cur.getPrevious(); - int curDeep = InlineCodegenUtil.getConstant(constant); - node.instructions.insert(constant, new LdcInsnNode(curDeep + finallyDeepShift)); - node.instructions.remove(constant); - } - cur = cur.getNext(); - } - } - - @NotNull - private static List getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) { - List fieldAccessChain = new ArrayList(); - fieldAccessChain.add(aload0); - AbstractInsnNode next = aload0.getNext(); - while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) { - if (next instanceof LabelNode) { - next = next.getNext(); - continue; //it will be delete on transformation - } - fieldAccessChain.add(next); - if ("this$0".equals(((FieldInsnNode) next).name)) { - next = next.getNext(); - } - else { - break; - } - } - - return fieldAccessChain; - } - - private static void putStackValuesIntoLocals( - @NotNull List directOrder, int shift, @NotNull InstructionAdapter iv, @NotNull String descriptor - ) { - Type[] actualParams = Type.getArgumentTypes(descriptor); - assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!"; - - int size = 0; - for (Type next : directOrder) { - size += next.getSize(); - } - - shift += size; - int index = directOrder.size(); - - for (Type next : Lists.reverse(directOrder)) { - shift -= next.getSize(); - Type typeOnStack = actualParams[--index]; - if (!typeOnStack.equals(next)) { - StackValue.onStack(typeOnStack).put(next, iv); - } - iv.store(shift, next); - } - } - - @NotNull - private RuntimeException wrapException(@NotNull Throwable originalException, @NotNull MethodNode node, @NotNull String errorSuffix) { - if (originalException instanceof InlineException) { - return new InlineException(errorPrefix + ": " + errorSuffix, originalException); + private fun wrapException(originalException: Throwable, node: MethodNode, errorSuffix: String): RuntimeException { + if (originalException is InlineException) { + return InlineException(errorPrefix + ": " + errorSuffix, originalException) } else { - return new InlineException(errorPrefix + ": " + errorSuffix + "\nCause: " + getNodeText(node), originalException); + return InlineException(errorPrefix + ": " + errorSuffix + "\nCause: " + getNodeText(node), originalException) } } - @NotNull - //process local and global returns (local substituted with goto end-label global kept unchanged) - public static List processReturns( - @NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, @Nullable Label endLabel - ) { - if (!remapReturn) { - return Collections.emptyList(); - } - List result = new ArrayList(); - InsnList instructions = node.instructions; - AbstractInsnNode insnNode = instructions.getFirst(); - while (insnNode != null) { - if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) { - boolean isLocalReturn = true; - String labelName = InlineCodegenUtil.getMarkedReturnLabelOrNull(insnNode); + private class LocalReturnsNormalizer { + private class LocalReturn( + private val returnInsn: AbstractInsnNode, + private val insertBeforeInsn: AbstractInsnNode, + private val frame: Frame + ) { - if (labelName != null) { - isLocalReturn = labelOwner.isMyLabel(labelName); - //remove global return flag - if (isLocalReturn) { - instructions.remove(insnNode.getPrevious()); - } - } + fun transform(insnList: InsnList, returnVariableIndex: Int) { + val isReturnWithValue = returnInsn.opcode != Opcodes.RETURN - if (isLocalReturn && endLabel != null) { - LabelNode labelNode = (LabelNode) endLabel.info; - JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode); - instructions.insert(insnNode, jumpInsnNode); - instructions.remove(insnNode); - insnNode = jumpInsnNode; - } + val expectedStackSize = if (isReturnWithValue) 1 else 0 + val actualStackSize = frame.stackSize + if (expectedStackSize == actualStackSize) return - //generate finally block before nonLocalReturn flag/return/goto - LabelNode label = new LabelNode(); - instructions.insert(insnNode, label); - result.add(new PointForExternalFinallyBlocks( - getInstructionToInsertFinallyBefore(insnNode, isLocalReturn), getReturnType(insnNode.getOpcode()), label - )); - } - insnNode = insnNode.getNext(); - } - return result; - } - - private static class LocalReturnsNormalizer { - private static class LocalReturn { - private final AbstractInsnNode returnInsn; - private final AbstractInsnNode insertBeforeInsn; - private final Frame frame; - - public LocalReturn( - @NotNull AbstractInsnNode returnInsn, - @NotNull AbstractInsnNode insertBeforeInsn, - @NotNull Frame frame - ) { - this.returnInsn = returnInsn; - this.insertBeforeInsn = insertBeforeInsn; - this.frame = frame; - } - - public void transform(@NotNull InsnList insnList, int returnVariableIndex) { - boolean isReturnWithValue = returnInsn.getOpcode() != Opcodes.RETURN; - - int expectedStackSize = isReturnWithValue ? 1 : 0; - int actualStackSize = frame.getStackSize(); - if (expectedStackSize == actualStackSize) return; - - int stackSize = actualStackSize; + var stackSize = actualStackSize if (isReturnWithValue) { - int storeOpcode = Opcodes.ISTORE + returnInsn.getOpcode() - Opcodes.IRETURN; - insnList.insertBefore(insertBeforeInsn, new VarInsnNode(storeOpcode, returnVariableIndex)); - stackSize--; + val storeOpcode = Opcodes.ISTORE + returnInsn.opcode - Opcodes.IRETURN + insnList.insertBefore(insertBeforeInsn, VarInsnNode(storeOpcode, returnVariableIndex)) + stackSize-- } while (stackSize > 0) { - int stackElementSize = frame.getStack(stackSize - 1).getSize(); - int popOpcode = stackElementSize == 1 ? Opcodes.POP : Opcodes.POP2; - insnList.insertBefore(insertBeforeInsn, new InsnNode(popOpcode)); - stackSize--; + val stackElementSize = frame.getStack(stackSize - 1).getSize() + val popOpcode = if (stackElementSize == 1) Opcodes.POP else Opcodes.POP2 + insnList.insertBefore(insertBeforeInsn, InsnNode(popOpcode)) + stackSize-- } if (isReturnWithValue) { - int loadOpcode = Opcodes.ILOAD + returnInsn.getOpcode() - Opcodes.IRETURN; - insnList.insertBefore(insertBeforeInsn, new VarInsnNode(loadOpcode, returnVariableIndex)); + val loadOpcode = Opcodes.ILOAD + returnInsn.opcode - Opcodes.IRETURN + insnList.insertBefore(insertBeforeInsn, VarInsnNode(loadOpcode, returnVariableIndex)) } } } - private final List localReturns = new SmartList(); + private val localReturns = SmartList() - private int returnVariableSize = 0; - private int returnOpcode = -1; + private var returnVariableSize = 0 + private var returnOpcode = -1 - private void addLocalReturnToTransform( - @NotNull AbstractInsnNode returnInsn, - @NotNull AbstractInsnNode insertBeforeInsn, - @NotNull Frame sourceValueFrame + internal fun addLocalReturnToTransform( + returnInsn: AbstractInsnNode, + insertBeforeInsn: AbstractInsnNode, + sourceValueFrame: Frame ) { - assert InlineCodegenUtil.isReturnOpcode(returnInsn.getOpcode()) : "return instruction expected"; - assert returnOpcode < 0 || returnOpcode == returnInsn.getOpcode() : - "Return op should be " + Printer.OPCODES[returnOpcode] + ", got " + Printer.OPCODES[returnInsn.getOpcode()]; - returnOpcode = returnInsn.getOpcode(); + assert(InlineCodegenUtil.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(new LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame)); + localReturns.add(LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame)) - if (returnInsn.getOpcode() != Opcodes.RETURN) { - if (returnInsn.getOpcode() == Opcodes.LRETURN || returnInsn.getOpcode() == Opcodes.DRETURN) { - returnVariableSize = 2; + if (returnInsn.opcode != Opcodes.RETURN) { + if (returnInsn.opcode == Opcodes.LRETURN || returnInsn.opcode == Opcodes.DRETURN) { + returnVariableSize = 2 } else { - returnVariableSize = 1; + returnVariableSize = 1 } } } - public void transform(@NotNull MethodNode methodNode) { - int returnVariableIndex = -1; + fun transform(methodNode: MethodNode) { + var returnVariableIndex = -1 if (returnVariableSize > 0) { - returnVariableIndex = methodNode.maxLocals; - methodNode.maxLocals += returnVariableSize; + returnVariableIndex = methodNode.maxLocals + methodNode.maxLocals += returnVariableSize } - for (LocalReturn localReturn : localReturns) { - localReturn.transform(methodNode.instructions, returnVariableIndex); + for (localReturn in localReturns) { + localReturn.transform(methodNode.instructions, returnVariableIndex) } } } - @NotNull - private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal) { - return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious(); - } - //Place to insert finally blocks from try blocks that wraps inline fun call - public static class PointForExternalFinallyBlocks { - public final AbstractInsnNode beforeIns; - public final Type returnType; - public final LabelNode finallyIntervalEnd; + class PointForExternalFinallyBlocks( + @JvmField val beforeIns: AbstractInsnNode, + @JvmField val returnType: Type, + @JvmField val finallyIntervalEnd: LabelNode + ) - public PointForExternalFinallyBlocks( - @NotNull AbstractInsnNode beforeIns, - @NotNull Type returnType, - @NotNull LabelNode finallyIntervalEnd + companion object { + + @JvmStatic + fun findCapturedField(node: FieldInsnNode, fieldRemapper: FieldRemapper): CapturedParamInfo { + assert(node.name.startsWith("$$$")) { "Captured field template should start with $$$ prefix" } + val fin = FieldInsnNode(node.opcode, node.owner, node.name.substring(3), node.desc) + val field = fieldRemapper.findField(fin) ?: throw IllegalStateException( + "Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.lambdaInternalName + ) + return field + } + + private fun analyzeMethodNodeWithoutMandatoryTransformations(node: MethodNode): Array?> { + val analyzer = object : Analyzer(SourceInterpreter()) { + override fun newFrame(nLocals: Int, nStack: Int): Frame { + return object : Frame(nLocals, nStack) { + @Throws(AnalyzerException::class) + override fun execute(insn: AbstractInsnNode, interpreter: Interpreter) { + // This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else. + if (insn.opcode == Opcodes.RETURN) return + super.execute(insn, interpreter) + } + } + } + } + + try { + return analyzer.analyze("fake", node) + } + catch (e: AnalyzerException) { + throw RuntimeException(e) + } + + } + + private fun isEmptyTryInterval(tryCatchBlockNode: TryCatchBlockNode): Boolean { + val start = tryCatchBlockNode.start + var end: AbstractInsnNode = tryCatchBlockNode.end + while (end !== start && end is LabelNode) { + end = end.getPrevious() + } + return start === end + } + + private fun removeClosureAssertions(node: MethodNode) { + var cur: AbstractInsnNode? = node.instructions.first + while (cur != null && cur.next != null) { + var next = cur.next + if (next.type == AbstractInsnNode.METHOD_INSN) { + val methodInsnNode = next as MethodInsnNode + if (methodInsnNode.name == "checkParameterIsNotNull" && methodInsnNode.owner == IntrinsicMethods.INTRINSICS_CLASS_NAME) { + val prev = cur.previous + + assert(cur.opcode == Opcodes.LDC) { "checkParameterIsNotNull should go after LDC but " + cur!! } + assert(prev.opcode == Opcodes.ALOAD) { "checkParameterIsNotNull should be invoked on local var but " + prev } + + node.instructions.remove(prev) + node.instructions.remove(cur) + cur = next.getNext() + node.instructions.remove(next) + next = cur + } + } + cur = next + } + } + + private fun transformFinallyDeepIndex(node: MethodNode, finallyDeepShift: Int) { + if (finallyDeepShift == 0) { + return + } + + var cur: AbstractInsnNode? = node.instructions.first + while (cur != null) { + if (cur is MethodInsnNode && InlineCodegenUtil.isFinallyMarker(cur)) { + val constant = cur.previous + val curDeep = InlineCodegenUtil.getConstant(constant) + node.instructions.insert(constant, LdcInsnNode(curDeep + finallyDeepShift)) + node.instructions.remove(constant) + } + cur = cur.next + } + } + + private fun getCapturedFieldAccessChain(aload0: VarInsnNode): List { + val fieldAccessChain = ArrayList() + fieldAccessChain.add(aload0) + var next: AbstractInsnNode? = aload0.next + while (next != null && next is FieldInsnNode || next is LabelNode) { + if (next is LabelNode) { + next = next.next + continue //it will be delete on transformation + } + fieldAccessChain.add(next) + if ("this$0" == (next as FieldInsnNode).name) { + next = next.next + } + else { + break + } + } + + return fieldAccessChain + } + + private fun putStackValuesIntoLocals( + directOrder: List, shift: Int, iv: InstructionAdapter, descriptor: String ) { - this.beforeIns = beforeIns; - this.returnType = returnType; - this.finallyIntervalEnd = finallyIntervalEnd; + var shift = shift + val actualParams = Type.getArgumentTypes(descriptor) + assert(actualParams.size == directOrder.size) { "Number of expected and actual params should be equals!" } + + var size = 0 + for (next in directOrder) { + size += next.size + } + + shift += size + var index = directOrder.size + + for (next in Lists.reverse(directOrder)) { + shift -= next.size + val typeOnStack = actualParams[--index] + if (typeOnStack != next) { + StackValue.onStack(typeOnStack).put(next, iv) + } + iv.store(shift, next) + } + } + + //process local and global returns (local substituted with goto end-label global kept unchanged) + @JvmStatic + fun processReturns( + node: MethodNode, labelOwner: LabelOwner, 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 (InlineCodegenUtil.isReturnOpcode(insnNode.opcode)) { + var isLocalReturn = true + val labelName = InlineCodegenUtil.getMarkedReturnLabelOrNull(insnNode) + + if (labelName != null) { + isLocalReturn = labelOwner.isMyLabel(labelName) + //remove global return flag + if (isLocalReturn) { + instructions.remove(insnNode.previous) + } + } + + if (isLocalReturn && endLabel != null) { + val labelNode = endLabel.info as LabelNode + val jumpInsnNode = JumpInsnNode(Opcodes.GOTO, labelNode) + instructions.insert(insnNode, 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 } } }