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