001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.codegen.inline;
018    
019    import com.google.common.collect.Lists;
020    import com.intellij.util.ArrayUtil;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.kotlin.codegen.ClosureCodegen;
024    import org.jetbrains.kotlin.codegen.StackValue;
025    import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
026    import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer;
027    import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
028    import org.jetbrains.org.objectweb.asm.Label;
029    import org.jetbrains.org.objectweb.asm.MethodVisitor;
030    import org.jetbrains.org.objectweb.asm.Opcodes;
031    import org.jetbrains.org.objectweb.asm.Type;
032    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
033    import org.jetbrains.org.objectweb.asm.commons.Method;
034    import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter;
035    import org.jetbrains.org.objectweb.asm.tree.*;
036    import org.jetbrains.org.objectweb.asm.tree.analysis.*;
037    
038    import java.util.*;
039    
040    import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.*;
041    
042    public class MethodInliner {
043    
044        private final MethodNode node;
045    
046        private final Parameters parameters;
047    
048        private final InliningContext inliningContext;
049    
050        private final FieldRemapper nodeRemapper;
051    
052        private final boolean isSameModule;
053    
054        private final String errorPrefix;
055    
056        private final SourceMapper sourceMapper;
057    
058        private final JetTypeMapper typeMapper;
059    
060        private final List<InvokeCall> invokeCalls = new ArrayList<InvokeCall>();
061    
062        //keeps order
063        private final List<AnonymousObjectGeneration> anonymousObjectGenerations = new ArrayList<AnonymousObjectGeneration>();
064        //current state
065        private final Map<String, String> currentTypeMapping = new HashMap<String, String>();
066    
067        private final InlineResult result;
068    
069        private int lambdasFinallyBlocks;
070    
071        /*
072         *
073         * @param node
074         * @param parameters
075         * @param inliningContext
076         * @param lambdaType - in case on lambda 'invoke' inlining
077         */
078        public MethodInliner(
079                @NotNull MethodNode node,
080                @NotNull Parameters parameters,
081                @NotNull InliningContext parent,
082                @NotNull FieldRemapper nodeRemapper,
083                boolean isSameModule,
084                @NotNull String errorPrefix,
085                @NotNull SourceMapper sourceMapper
086        ) {
087            this.node = node;
088            this.parameters = parameters;
089            this.inliningContext = parent;
090            this.nodeRemapper = nodeRemapper;
091            this.isSameModule = isSameModule;
092            this.errorPrefix = errorPrefix;
093            this.sourceMapper = sourceMapper;
094            this.typeMapper = parent.state.getTypeMapper();
095            this.result = InlineResult.create();
096        }
097    
098        public InlineResult doInline(
099                @NotNull MethodVisitor adapter,
100                @NotNull LocalVarRemapper remapper,
101                boolean remapReturn,
102                @NotNull LabelOwner labelOwner
103        ) {
104            return doInline(adapter, remapper, remapReturn, labelOwner, 0);
105        }
106    
107        private InlineResult doInline(
108                @NotNull MethodVisitor adapter,
109                @NotNull LocalVarRemapper remapper,
110                boolean remapReturn,
111                @NotNull LabelOwner labelOwner,
112                int finallyDeepShift
113        ) {
114            //analyze body
115            MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node, finallyDeepShift);
116    
117            //substitute returns with "goto end" instruction to keep non local returns in lambdas
118            Label end = new Label();
119            transformedNode = doInline(transformedNode);
120            removeClosureAssertions(transformedNode);
121            InsnList instructions = transformedNode.instructions;
122            instructions.resetLabels();
123    
124            MethodNode resultNode = new MethodNode(InlineCodegenUtil.API, transformedNode.access, transformedNode.name, transformedNode.desc,
125                                             transformedNode.signature, ArrayUtil.toStringArray(transformedNode.exceptions));
126            RemapVisitor visitor = new RemapVisitor(resultNode, remapper, nodeRemapper);
127            try {
128                transformedNode.accept(visitor);
129            }
130            catch (Exception e) {
131                throw wrapException(e, transformedNode, "couldn't inline method call");
132            }
133    
134            resultNode.visitLabel(end);
135    
136            if (inliningContext.isRoot()) {
137                InternalFinallyBlockInliner.processInlineFunFinallyBlocks(resultNode, lambdasFinallyBlocks, ((StackValue.Local)remapper.remap(parameters.getArgsSizeOnStack() + 1).value).index);
138            }
139    
140            processReturns(resultNode, labelOwner, remapReturn, end);
141            //flush transformed node to output
142            resultNode.accept(new InliningInstructionAdapter(adapter));
143    
144            sourceMapper.endMapping();
145            return result;
146        }
147    
148        private MethodNode doInline(MethodNode node) {
149    
150            final Deque<InvokeCall> currentInvokes = new LinkedList<InvokeCall>(invokeCalls);
151    
152            final MethodNode resultNode = new MethodNode(node.access, node.name, node.desc, node.signature, null);
153    
154            final Iterator<AnonymousObjectGeneration> iterator = anonymousObjectGenerations.iterator();
155    
156            RemappingMethodAdapter remappingMethodAdapter = new RemappingMethodAdapter(resultNode.access, resultNode.desc, resultNode,
157                                                                                       new TypeRemapper(currentTypeMapping));
158    
159            final int markerShift = InlineCodegenUtil.calcMarkerShift(parameters, node);
160            InlineAdapter lambdaInliner = new InlineAdapter(remappingMethodAdapter, parameters.getArgsSizeOnStack(), sourceMapper) {
161    
162                private AnonymousObjectGeneration anonymousObjectGen;
163                private void handleAnonymousObjectGeneration() {
164                    anonymousObjectGen = iterator.next();
165    
166                    if (anonymousObjectGen.shouldRegenerate()) {
167                        //TODO: need poping of type but what to do with local funs???
168                        String oldClassName = anonymousObjectGen.getOwnerInternalName();
169                        String newClassName = inliningContext.nameGenerator.genLambdaClassName();
170                        currentTypeMapping.put(oldClassName, newClassName);
171                        AnonymousObjectTransformer transformer =
172                                new AnonymousObjectTransformer(oldClassName,
173                                                               inliningContext
174                                                                       .subInlineWithClassRegeneration(
175                                                                               inliningContext.nameGenerator,
176                                                                               currentTypeMapping,
177                                                                               anonymousObjectGen),
178                                                               isSameModule, Type.getObjectType(newClassName)
179                                );
180    
181                        InlineResult transformResult = transformer.doTransform(anonymousObjectGen, nodeRemapper);
182                        result.addAllClassesToRemove(transformResult);
183                        result.addChangedType(oldClassName, newClassName);
184    
185                        if (inliningContext.isInliningLambda && !anonymousObjectGen.isStaticOrigin()) {
186                            // this class is transformed and original not used so we should remove original one after inlining
187                            // Note: It is unsafe to remove anonymous class that is referenced by GETSTATIC within lambda
188                            // because it can be local function from outer scope
189                            result.addClassToRemove(oldClassName);
190                        }
191    
192                        if (transformResult.getReifiedTypeParametersUsages().wereUsedReifiedParameters()) {
193                            ReifiedTypeInliner.putNeedClassReificationMarker(mv);
194                            result.getReifiedTypeParametersUsages().mergeAll(transformResult.getReifiedTypeParametersUsages());
195                        }
196                    }
197                }
198    
199                @Override
200                public void anew(@NotNull Type type) {
201                    if (isAnonymousConstructorCall(type.getInternalName(), "<init>")) {
202                        handleAnonymousObjectGeneration();
203                    }
204    
205                    //in case of regenerated anonymousObjectGen type would be remapped to new one via remappingMethodAdapter
206                    super.anew(type);
207                }
208    
209                @Override
210                public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
211                    if (/*INLINE_RUNTIME.equals(owner) &&*/ isInvokeOnLambda(owner, name)) { //TODO add method
212                        assert !currentInvokes.isEmpty();
213                        InvokeCall invokeCall = currentInvokes.remove();
214                        LambdaInfo info = invokeCall.lambdaInfo;
215    
216                        if (info == null) {
217                            //noninlinable lambda
218                            super.visitMethodInsn(opcode, owner, name, desc, itf);
219                            return;
220                        }
221    
222                        int valueParamShift = Math.max(getNextLocalIndex(), markerShift);//NB: don't inline cause it changes
223                        putStackValuesIntoLocals(info.getInvokeParamsWithoutCaptured(), valueParamShift, this, desc);
224    
225                        addInlineMarker(this, true);
226                        Parameters lambdaParameters = info.addAllParameters(nodeRemapper);
227    
228                        InlinedLambdaRemapper newCapturedRemapper =
229                                new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), nodeRemapper, lambdaParameters);
230    
231                        setLambdaInlining(true);
232                        SMAP lambdaSMAP = info.getNode().getClassSMAP();
233                        SourceMapper mapper =
234                                inliningContext.classRegeneration && !inliningContext.isInliningLambda ?
235                                new NestedSourceMapper(sourceMapper, lambdaSMAP.getIntervals(), lambdaSMAP.getSourceInfo())
236                                : new InlineLambdaSourceMapper(sourceMapper.getParent(), info.getNode());
237                        MethodInliner inliner = new MethodInliner(info.getNode().getNode(), lambdaParameters,
238                                                                  inliningContext.subInlineLambda(info),
239                                                                  newCapturedRemapper, true /*cause all calls in same module as lambda*/,
240                                                                  "Lambda inlining " + info.getLambdaClassType().getInternalName(),
241                                                                  mapper);
242    
243                        LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift);
244                        InlineResult lambdaResult = inliner.doInline(this.mv, remapper, true, info, invokeCall.finallyDepthShift);//TODO add skipped this and receiver
245                        result.addAllClassesToRemove(lambdaResult);
246    
247                        //return value boxing/unboxing
248                        Method bridge =
249                                typeMapper.mapSignature(ClosureCodegen.getErasedInvokeFunction(info.getFunctionDescriptor())).getAsmMethod();
250                        Method delegate = typeMapper.mapSignature(info.getFunctionDescriptor()).getAsmMethod();
251                        StackValue.onStack(delegate.getReturnType()).put(bridge.getReturnType(), this);
252                        setLambdaInlining(false);
253                        addInlineMarker(this, false);
254                        mapper.endMapping();
255                    }
256                    else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
257                        assert anonymousObjectGen != null : "<init> call not corresponds to new call" + owner + " " + name;
258                        if (anonymousObjectGen.shouldRegenerate()) {
259                            //put additional captured parameters on stack
260                            for (CapturedParamDesc capturedParamDesc : anonymousObjectGen.getAllRecapturedParameters()) {
261                                visitFieldInsn(Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(),
262                                               "$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor());
263                            }
264                            super.visitMethodInsn(opcode, anonymousObjectGen.getNewLambdaType().getInternalName(), name, anonymousObjectGen.getNewConstructorDescriptor(), itf);
265                            anonymousObjectGen = null;
266                        } else {
267                            super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf);
268                        }
269                    }
270                    else if (ReifiedTypeInliner.isNeedClassReificationMarker(new MethodInsnNode(opcode, owner, name, desc, false))) {
271                        // we will put it if needed in anew processing
272                    }
273                    else {
274                        super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf);
275                    }
276                }
277    
278                @Override
279                public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
280                    if (opcode == Opcodes.GETSTATIC && isAnonymousSingletonLoad(owner, name)) {
281                        handleAnonymousObjectGeneration();
282                    }
283                    super.visitFieldInsn(opcode, owner, name, desc);
284                }
285    
286                @Override
287                public void visitMaxs(int stack, int locals) {
288                    lambdasFinallyBlocks = resultNode.tryCatchBlocks.size();
289                    super.visitMaxs(stack, locals);
290                }
291    
292            };
293    
294            node.accept(lambdaInliner);
295    
296            return resultNode;
297        }
298    
299        @NotNull
300        public static CapturedParamInfo findCapturedField(FieldInsnNode node, FieldRemapper fieldRemapper) {
301            assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix";
302            FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
303            CapturedParamInfo field = fieldRemapper.findField(fin);
304            if (field == null) {
305                throw new IllegalStateException("Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName());
306            }
307            return field;
308        }
309    
310        @NotNull
311        public MethodNode prepareNode(@NotNull MethodNode node, int finallyDeepShift) {
312            final int capturedParamsSize = parameters.getCapturedArgsSizeOnStack();
313            final int realParametersSize = parameters.getRealArgsSizeOnStack();
314            Type[] types = Type.getArgumentTypes(node.desc);
315            Type returnType = Type.getReturnType(node.desc);
316    
317            List<Type> capturedTypes = parameters.getCapturedTypes();
318            Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()]));
319    
320            node.instructions.resetLabels();
321            MethodNode transformedNode = new MethodNode(InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null) {
322    
323                private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda();
324    
325                private int getNewIndex(int var) {
326                    return var + (var < realParametersSize ? 0 : capturedParamsSize);
327                }
328    
329                @Override
330                public void visitVarInsn(int opcode, int var) {
331                    super.visitVarInsn(opcode, getNewIndex(var));
332                }
333    
334                @Override
335                public void visitIincInsn(int var, int increment) {
336                    super.visitIincInsn(getNewIndex(var), increment);
337                }
338    
339                @Override
340                public void visitMaxs(int maxStack, int maxLocals) {
341                    super.visitMaxs(maxStack, maxLocals + capturedParamsSize);
342                }
343    
344                @Override
345                public void visitLineNumber(int line, @NotNull Label start) {
346                    if(isInliningLambda || InlineCodegenUtil.GENERATE_SMAP) {
347                        super.visitLineNumber(line, start);
348                    }
349                }
350    
351                @Override
352                public void visitLocalVariable(
353                        @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index
354                ) {
355                    if (isInliningLambda || InlineCodegenUtil.GENERATE_SMAP) {
356                        super.visitLocalVariable(name, desc, signature, start, end, getNewIndex(index));
357                    }
358                }
359            };
360    
361            node.accept(transformedNode);
362    
363            transformCaptured(transformedNode);
364            transformFinallyDeepIndex(transformedNode, finallyDeepShift);
365    
366            return transformedNode;
367        }
368    
369        @NotNull
370        protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) {
371            node = prepareNode(node, finallyDeepShift);
372    
373            try {
374                new MandatoryMethodTransformer().transform("fake", node);
375            }
376            catch (Throwable e) {
377                throw wrapException(e, node, "couldn't inline method call");
378            }
379    
380            Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
381                @NotNull
382                @Override
383                protected Frame<SourceValue> newFrame(
384                        int nLocals, int nStack
385                ) {
386                    return new Frame<SourceValue>(nLocals, nStack) {
387                        @Override
388                        public void execute(
389                                @NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter
390                        ) throws AnalyzerException {
391                            if (insn.getOpcode() == Opcodes.RETURN) {
392                                //there is exception on void non local return in frame
393                                return;
394                            }
395                            super.execute(insn, interpreter);
396                        }
397                    };
398                }
399            };
400    
401            Frame<SourceValue>[] sources;
402            try {
403                sources = analyzer.analyze("fake", node);
404            }
405            catch (AnalyzerException e) {
406                throw wrapException(e, node, "couldn't inline method call");
407            }
408    
409            AbstractInsnNode cur = node.instructions.getFirst();
410            int index = 0;
411    
412            boolean awaitClassReification = false;
413            int currentFinallyDeep = 0;
414    
415            while (cur != null) {
416                Frame<SourceValue> frame = sources[index];
417    
418                if (frame != null) {
419                    if (ReifiedTypeInliner.isNeedClassReificationMarker(cur)) {
420                        awaitClassReification = true;
421                    }
422                    else if (cur.getType() == AbstractInsnNode.METHOD_INSN) {
423                        if (InlineCodegenUtil.isFinallyStart(cur)) {
424                            //TODO deep index calc could be more precise
425                            currentFinallyDeep = InlineCodegenUtil.getConstant(cur.getPrevious());
426                        }
427    
428                        MethodInsnNode methodInsnNode = (MethodInsnNode) cur;
429                        String owner = methodInsnNode.owner;
430                        String desc = methodInsnNode.desc;
431                        String name = methodInsnNode.name;
432                        //TODO check closure
433                        Type[] argTypes = Type.getArgumentTypes(desc);
434                        int paramCount = argTypes.length + 1;//non static
435                        int firstParameterIndex = frame.getStackSize() - paramCount;
436                        if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
437                            SourceValue sourceValue = frame.getStack(firstParameterIndex);
438    
439                            LambdaInfo lambdaInfo = null;
440                            int varIndex = -1;
441    
442                            if (sourceValue.insns.size() == 1) {
443                                AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
444                                AbstractInsnNode processingInstruction = insnNode;
445    
446                                if (insnNode.getOpcode() == Opcodes.SWAP) {
447                                    processingInstruction = InlineCodegenUtil.getPrevMeaningful(insnNode);
448                                }
449                                lambdaInfo = getLambdaIfExists(processingInstruction);
450                                if (lambdaInfo != null) {
451                                    //remove inlinable access
452                                    assert processingInstruction != null;
453                                    InlineCodegenUtil.removeInterval(node, processingInstruction, insnNode);
454                                }
455                            }
456    
457                            invokeCalls.add(new InvokeCall(varIndex, lambdaInfo, currentFinallyDeep));
458                        }
459                        else if (isAnonymousConstructorCall(owner, name)) {
460                            Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
461    
462                            int offset = 0;
463                            for (int i = 0; i < paramCount; i++) {
464                                SourceValue sourceValue = frame.getStack(firstParameterIndex + i);
465                                if (sourceValue.insns.size() == 1) {
466                                    AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
467                                    LambdaInfo lambdaInfo = getLambdaIfExists(insnNode);
468                                    if (lambdaInfo != null) {
469                                        lambdaMapping.put(offset, lambdaInfo);
470                                        node.instructions.remove(insnNode);
471                                    }
472                                }
473                                offset += i == 0 ? 1 : argTypes[i - 1].getSize();
474                            }
475    
476                            anonymousObjectGenerations.add(
477                                    buildConstructorInvocation(
478                                            owner, desc, lambdaMapping, awaitClassReification
479                                    )
480                            );
481                            awaitClassReification = false;
482                        }
483                    }
484                    else if (cur.getOpcode() == Opcodes.GETSTATIC) {
485                        FieldInsnNode fieldInsnNode = (FieldInsnNode) cur;
486                        String owner = fieldInsnNode.owner;
487                        if (isAnonymousSingletonLoad(owner, fieldInsnNode.name)) {
488                            anonymousObjectGenerations.add(
489                                    new AnonymousObjectGeneration(
490                                            owner, isSameModule, awaitClassReification, isAlreadyRegenerated(owner), true
491                                    )
492                            );
493                            awaitClassReification = false;
494                        }
495                    }
496                }
497                AbstractInsnNode prevNode = cur;
498                cur = cur.getNext();
499                index++;
500    
501                //given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached (dead code).
502                if (frame == null) {
503                    //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
504                    if (prevNode.getType() == AbstractInsnNode.LABEL) {
505                        //NB: Cause we generate exception table for default handler using gaps (see ExpressionCodegen.visitTryExpression)
506                        //it may occurs that interval for default handler starts before catch start label, so this label seems as dead,
507                        //but as result all this labels will be merged into one (see KT-5863)
508                    } else {
509                        node.instructions.remove(prevNode);
510                    }
511                }
512            }
513    
514            //clean dead try/catch blocks
515            List<TryCatchBlockNode> blocks = node.tryCatchBlocks;
516            for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) {
517                TryCatchBlockNode block = iterator.next();
518                if (isEmptyTryInterval(block)) {
519                    iterator.remove();
520                }
521            }
522    
523            return node;
524        }
525    
526        private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) {
527            LabelNode start = tryCatchBlockNode.start;
528            AbstractInsnNode end = tryCatchBlockNode.end;
529            while (end != start && end instanceof LabelNode) {
530                end = end.getPrevious();
531            }
532            return start == end;
533        }
534    
535        @NotNull
536        private AnonymousObjectGeneration buildConstructorInvocation(
537                @NotNull String owner,
538                @NotNull String desc,
539                @NotNull Map<Integer, LambdaInfo> lambdaMapping,
540                boolean needReification
541        ) {
542            return new AnonymousObjectGeneration(
543                    owner, needReification, isSameModule, lambdaMapping,
544                    inliningContext.classRegeneration,
545                    isAlreadyRegenerated(owner),
546                    desc,
547                    false
548            );
549        }
550    
551        private boolean isAlreadyRegenerated(@NotNull String owner) {
552            return inliningContext.typeMapping.containsKey(owner);
553        }
554    
555        @Nullable
556        public LambdaInfo getLambdaIfExists(@Nullable AbstractInsnNode insnNode) {
557            if (insnNode == null) {
558                return null;
559            }
560    
561            if (insnNode.getOpcode() == Opcodes.ALOAD) {
562                int varIndex = ((VarInsnNode) insnNode).var;
563                return getLambdaIfExists(varIndex);
564            }
565            else if (insnNode instanceof FieldInsnNode) {
566                FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode;
567                if (fieldInsnNode.name.startsWith("$$$")) {
568                    return findCapturedField(fieldInsnNode, nodeRemapper).getLambda();
569                }
570            }
571    
572            return null;
573        }
574    
575        private LambdaInfo getLambdaIfExists(int varIndex) {
576            if (varIndex < parameters.getArgsSizeOnStack()) {
577                return parameters.getParameterByDeclarationSlot(varIndex).getLambda();
578            }
579            return null;
580        }
581    
582        private static void removeClosureAssertions(MethodNode node) {
583            AbstractInsnNode cur = node.instructions.getFirst();
584            while (cur != null && cur.getNext() != null) {
585                AbstractInsnNode next = cur.getNext();
586                if (next.getType() == AbstractInsnNode.METHOD_INSN) {
587                    MethodInsnNode methodInsnNode = (MethodInsnNode) next;
588                    if (methodInsnNode.name.equals("checkParameterIsNotNull") && methodInsnNode.owner.equals(IntrinsicMethods.INTRINSICS_CLASS_NAME)) {
589                        AbstractInsnNode prev = cur.getPrevious();
590    
591                        assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur;
592                        assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev;
593    
594                        node.instructions.remove(prev);
595                        node.instructions.remove(cur);
596                        cur = next.getNext();
597                        node.instructions.remove(next);
598                        next = cur;
599                    }
600                }
601                cur = next;
602            }
603        }
604    
605        private void transformCaptured(@NotNull MethodNode node) {
606            if (nodeRemapper.isRoot()) {
607                return;
608            }
609    
610            //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured
611            //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0)
612            AbstractInsnNode cur = node.instructions.getFirst();
613            while (cur != null) {
614                if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) {
615                    int varIndex = ((VarInsnNode) cur).var;
616                    if (varIndex == 0 || nodeRemapper.processNonAload0FieldAccessChains(getLambdaIfExists(varIndex) != null)) {
617                        List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur);
618                        AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node);
619                        if (insnNode != null) {
620                            cur = insnNode;
621                        }
622                    }
623                }
624                cur = cur.getNext();
625            }
626        }
627    
628        private void transformFinallyDeepIndex(@NotNull MethodNode node, int finallyDeepShift) {
629            if (finallyDeepShift == 0) {
630                return;
631            }
632    
633            AbstractInsnNode cur = node.instructions.getFirst();
634            while (cur != null) {
635                if (cur instanceof MethodInsnNode && InlineCodegenUtil.isFinallyMarker(cur)) {
636                    AbstractInsnNode constant = cur.getPrevious();
637                    int curDeep = InlineCodegenUtil.getConstant(constant);
638                    node.instructions.insert(constant, new LdcInsnNode(curDeep + finallyDeepShift));
639                    node.instructions.remove(constant);
640                }
641                cur = cur.getNext();
642            }
643        }
644    
645        @NotNull
646        public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) {
647            List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
648            fieldAccessChain.add(aload0);
649            AbstractInsnNode next = aload0.getNext();
650            while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) {
651                if (next instanceof LabelNode) {
652                    next = next.getNext();
653                    continue; //it will be delete on transformation
654                }
655                fieldAccessChain.add(next);
656                if ("this$0".equals(((FieldInsnNode) next).name)) {
657                    next = next.getNext();
658                }
659                else {
660                    break;
661                }
662            }
663    
664            return fieldAccessChain;
665        }
666    
667        public static void putStackValuesIntoLocals(List<Type> directOrder, int shift, InstructionAdapter iv, String descriptor) {
668            Type[] actualParams = Type.getArgumentTypes(descriptor);
669            assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!";
670    
671            int size = 0;
672            for (Type next : directOrder) {
673                size += next.getSize();
674            }
675    
676            shift += size;
677            int index = directOrder.size();
678    
679            for (Type next : Lists.reverse(directOrder)) {
680                shift -= next.getSize();
681                Type typeOnStack = actualParams[--index];
682                if (!typeOnStack.equals(next)) {
683                    StackValue.onStack(typeOnStack).put(next, iv);
684                }
685                iv.store(shift, next);
686            }
687        }
688    
689        //TODO: check it's external module
690        //TODO?: assert method exists in facade?
691        public String changeOwnerForExternalPackage(String type, int opcode) {
692            //if (isSameModule || (opcode & Opcodes.INVOKESTATIC) == 0) {
693            //    return type;
694            //}
695    
696            //JvmClassName name = JvmClassName.byInternalName(type);
697            //String packageClassInternalName = PackageClassUtils.getPackageClassInternalName(name.getPackageFqName());
698            //if (type.startsWith(packageClassInternalName + '$')) {
699            //    VirtualFile virtualFile = InlineCodegenUtil.findVirtualFile(inliningContext.state.getProject(), type);
700            //    if (virtualFile != null) {
701            //        KotlinJvmBinaryClass klass = KotlinBinaryClassCache.getKotlinBinaryClass(virtualFile);
702            //        if (klass != null && klass.getClassHeader().getSyntheticClassKind() == KotlinSyntheticClass.Kind.PACKAGE_PART) {
703            //            return packageClassInternalName;
704            //        }
705            //    }
706            //}
707    
708            return type;
709        }
710    
711        @NotNull
712        public RuntimeException wrapException(@NotNull Throwable originalException, @NotNull MethodNode node, @NotNull String errorSuffix) {
713            if (originalException instanceof InlineException) {
714                return new InlineException(errorPrefix + ": " + errorSuffix, originalException);
715            }
716            else {
717                return new InlineException(errorPrefix + ": " + errorSuffix + "\ncause: " +
718                                           getNodeText(node), originalException);
719            }
720        }
721    
722        @NotNull
723        //process local and global returns (local substituted with goto end-label global kept unchanged)
724        public static List<PointForExternalFinallyBlocks> processReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, Label endLabel) {
725            if (!remapReturn) {
726                return Collections.emptyList();
727            }
728            List<PointForExternalFinallyBlocks> result = new ArrayList<PointForExternalFinallyBlocks>();
729            InsnList instructions = node.instructions;
730            AbstractInsnNode insnNode = instructions.getFirst();
731            while (insnNode != null) {
732                if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
733                    AbstractInsnNode previous = insnNode.getPrevious();
734                    MethodInsnNode flagNode;
735                    boolean isLocalReturn = true;
736                    String labelName = null;
737                    if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) {
738                        flagNode = (MethodInsnNode) previous;
739                        labelName = flagNode.name;
740                    }
741    
742                    if (labelName != null) {
743                        isLocalReturn = labelOwner.isMyLabel(labelName);
744                        //remove global return flag
745                        if (isLocalReturn) {
746                            instructions.remove(previous);
747                        }
748                    }
749    
750                    if (isLocalReturn && endLabel != null) {
751                        LabelNode labelNode = (LabelNode) endLabel.info;
752                        JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode);
753                        instructions.insert(insnNode, jumpInsnNode);
754                        instructions.remove(insnNode);
755                        insnNode = jumpInsnNode;
756                    }
757    
758                    //genetate finally block before nonLocalReturn flag/return/goto
759                    LabelNode label = new LabelNode();
760                    instructions.insert(insnNode, label);
761                    result.add(new PointForExternalFinallyBlocks(getInstructionToInsertFinallyBefore(insnNode, isLocalReturn),
762                                                                 getReturnType(insnNode.getOpcode()),
763                                                                 label));
764                }
765                insnNode = insnNode.getNext();
766            }
767            return result;
768        }
769    
770        @NotNull
771        private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal)  {
772            return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious();
773        }
774    
775        //Place to insert finally blocks from try blocks that wraps inline fun call
776        public static class PointForExternalFinallyBlocks {
777    
778            final AbstractInsnNode beforeIns;
779    
780            final Type returnType;
781    
782            final LabelNode finallyIntervalEnd;
783    
784            public PointForExternalFinallyBlocks(
785                    @NotNull AbstractInsnNode beforeIns,
786                    @NotNull Type returnType,
787                    @NotNull LabelNode finallyIntervalEnd
788            ) {
789                this.beforeIns = beforeIns;
790                this.returnType = returnType;
791                this.finallyIntervalEnd = finallyIntervalEnd;
792            }
793    
794        }
795    
796    }