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