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