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