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 com.intellij.util.containers.SmartHashSet;
022 import org.jetbrains.annotations.NotNull;
023 import org.jetbrains.annotations.Nullable;
024 import org.jetbrains.kotlin.codegen.ClosureCodegen;
025 import org.jetbrains.kotlin.codegen.StackValue;
026 import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
027 import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer;
028 import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
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 // we will put it if needed in anew processing
290 }
291 else {
292 super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf);
293 }
294 }
295
296 @Override
297 public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
298 if (opcode == Opcodes.GETSTATIC && isAnonymousSingletonLoad(owner, name)) {
299 handleAnonymousObjectGeneration();
300 }
301 super.visitFieldInsn(opcode, owner, name, desc);
302 }
303
304 @Override
305 public void visitMaxs(int stack, int locals) {
306 lambdasFinallyBlocks = resultNode.tryCatchBlocks.size();
307 super.visitMaxs(stack, locals);
308 }
309
310 };
311
312 node.accept(lambdaInliner);
313
314 return resultNode;
315 }
316
317 @NotNull
318 public static CapturedParamInfo findCapturedField(FieldInsnNode node, FieldRemapper fieldRemapper) {
319 assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix";
320 FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
321 CapturedParamInfo field = fieldRemapper.findField(fin);
322 if (field == null) {
323 throw new IllegalStateException("Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName());
324 }
325 return field;
326 }
327
328 @NotNull
329 public MethodNode prepareNode(@NotNull MethodNode node, int finallyDeepShift) {
330 final int capturedParamsSize = parameters.getCapturedArgsSizeOnStack();
331 final int realParametersSize = parameters.getRealArgsSizeOnStack();
332 Type[] types = Type.getArgumentTypes(node.desc);
333 Type returnType = Type.getReturnType(node.desc);
334
335 List<Type> capturedTypes = parameters.getCapturedTypes();
336 Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()]));
337
338 node.instructions.resetLabels();
339 MethodNode transformedNode = new MethodNode(InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null) {
340
341 private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda();
342
343 private int getNewIndex(int var) {
344 return var + (var < realParametersSize ? 0 : capturedParamsSize);
345 }
346
347 @Override
348 public void visitVarInsn(int opcode, int var) {
349 super.visitVarInsn(opcode, getNewIndex(var));
350 }
351
352 @Override
353 public void visitIincInsn(int var, int increment) {
354 super.visitIincInsn(getNewIndex(var), increment);
355 }
356
357 @Override
358 public void visitMaxs(int maxStack, int maxLocals) {
359 super.visitMaxs(maxStack, maxLocals + capturedParamsSize);
360 }
361
362 @Override
363 public void visitLineNumber(int line, @NotNull Label start) {
364 if(isInliningLambda || InlineCodegenUtil.GENERATE_SMAP) {
365 super.visitLineNumber(line, start);
366 }
367 }
368
369 @Override
370 public void visitLocalVariable(
371 @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index
372 ) {
373 if (isInliningLambda || InlineCodegenUtil.GENERATE_SMAP) {
374 String varSuffix = inliningContext.isRoot() &&
375 !((RootInliningContext) inliningContext).isDefaultCompilation &&
376 !InlineCodegenUtil.isFakeLocalVariableForInline(name) ?
377 INLINE_FUN_VAR_SUFFIX : "";
378 String varName = !varSuffix.isEmpty() && name.equals("this") ? name + "_" : name;
379 super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index));
380 }
381 }
382 };
383
384 node.accept(transformedNode);
385
386 transformCaptured(transformedNode);
387 transformFinallyDeepIndex(transformedNode, finallyDeepShift);
388
389 return transformedNode;
390 }
391
392 @NotNull
393 protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) {
394 node = prepareNode(node, finallyDeepShift);
395
396 try {
397 new MandatoryMethodTransformer().transform("fake", node);
398 }
399 catch (Throwable e) {
400 throw wrapException(e, node, "couldn't inline method call");
401 }
402
403 Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
404 @NotNull
405 @Override
406 protected Frame<SourceValue> newFrame(
407 int nLocals, int nStack
408 ) {
409 return new Frame<SourceValue>(nLocals, nStack) {
410 @Override
411 public void execute(
412 @NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter
413 ) throws AnalyzerException {
414 if (insn.getOpcode() == Opcodes.RETURN) {
415 //there is exception on void non local return in frame
416 return;
417 }
418 super.execute(insn, interpreter);
419 }
420 };
421 }
422 };
423
424 Frame<SourceValue>[] sources;
425 try {
426 sources = analyzer.analyze("fake", node);
427 }
428 catch (AnalyzerException e) {
429 throw wrapException(e, node, "couldn't inline method call");
430 }
431 Set<AbstractInsnNode> toDelete = new SmartHashSet<AbstractInsnNode>();
432 InstructionsAndFrames instructionsAndFrames = new InstructionsAndFrames(sources, node.instructions);
433 AbstractInsnNode cur = node.instructions.getFirst();
434 int index = 0;
435
436 boolean awaitClassReification = false;
437 int currentFinallyDeep = 0;
438
439 while (cur != null) {
440 Frame<SourceValue> frame = sources[index];
441
442 if (frame != null) {
443 if (ReifiedTypeInliner.isNeedClassReificationMarker(cur)) {
444 awaitClassReification = true;
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 = null;
464 int varIndex = -1;
465
466 if (sourceValue.insns.size() == 1) {
467 AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
468 AbstractInsnNode processingInstruction = insnNode;
469
470 if (insnNode.getOpcode() == Opcodes.SWAP) {
471 processingInstruction = InlineCodegenUtil.getPrevMeaningful(insnNode);
472 }
473 lambdaInfo = getLambdaIfExistsAndMarkInstructions(processingInstruction, frame, instructionsAndFrames, toDelete);
474 if (lambdaInfo != null) {
475 //remove inlinable access
476 assert processingInstruction != null;
477 InlineCodegenUtil.removeInterval(node, processingInstruction, insnNode);
478 }
479 }
480
481 invokeCalls.add(new InvokeCall(varIndex, lambdaInfo, currentFinallyDeep));
482 }
483 else if (isAnonymousConstructorCall(owner, name)) {
484 Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
485
486 int offset = 0;
487 for (int i = 0; i < paramCount; i++) {
488 SourceValue sourceValue = frame.getStack(firstParameterIndex + i);
489 if (sourceValue.insns.size() == 1) {
490 AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
491 LambdaInfo lambdaInfo = getLambdaIfExistsAndMarkInstructions(insnNode, frame, instructionsAndFrames, toDelete);
492 if (lambdaInfo != null) {
493 lambdaMapping.put(offset, lambdaInfo);
494 node.instructions.remove(insnNode);
495 }
496 }
497 offset += i == 0 ? 1 : argTypes[i - 1].getSize();
498 }
499
500 anonymousObjectGenerations.add(
501 buildConstructorInvocation(
502 owner, desc, lambdaMapping, awaitClassReification
503 )
504 );
505 awaitClassReification = false;
506 }
507 }
508 else if (cur.getOpcode() == Opcodes.GETSTATIC) {
509 FieldInsnNode fieldInsnNode = (FieldInsnNode) cur;
510 String owner = fieldInsnNode.owner;
511 if (isAnonymousSingletonLoad(owner, fieldInsnNode.name)) {
512 anonymousObjectGenerations.add(
513 new AnonymousObjectGeneration(
514 owner, isSameModule, awaitClassReification, isAlreadyRegenerated(owner), true
515 )
516 );
517 awaitClassReification = false;
518 }
519 }
520 }
521 AbstractInsnNode prevNode = cur;
522 cur = cur.getNext();
523 index++;
524
525 //given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached (dead code).
526 if (frame == null) {
527 //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
528 if (prevNode.getType() == AbstractInsnNode.LABEL) {
529 //NB: Cause we generate exception table for default handler using gaps (see ExpressionCodegen.visitTryExpression)
530 //it may occurs that interval for default handler starts before catch start label, so this label seems as dead,
531 //but as result all this labels will be merged into one (see KT-5863)
532 } else {
533 node.instructions.remove(prevNode);
534 }
535 }
536 }
537
538 for (AbstractInsnNode insnNode : toDelete) {
539 node.instructions.remove(insnNode);
540 }
541
542 //clean dead try/catch blocks
543 List<TryCatchBlockNode> blocks = node.tryCatchBlocks;
544 for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) {
545 TryCatchBlockNode block = iterator.next();
546 if (isEmptyTryInterval(block)) {
547 iterator.remove();
548 }
549 }
550
551 return node;
552 }
553
554 private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) {
555 LabelNode start = tryCatchBlockNode.start;
556 AbstractInsnNode end = tryCatchBlockNode.end;
557 while (end != start && end instanceof LabelNode) {
558 end = end.getPrevious();
559 }
560 return start == end;
561 }
562
563 @NotNull
564 private AnonymousObjectGeneration buildConstructorInvocation(
565 @NotNull String owner,
566 @NotNull String desc,
567 @NotNull Map<Integer, LambdaInfo> lambdaMapping,
568 boolean needReification
569 ) {
570 return new AnonymousObjectGeneration(
571 owner, needReification, isSameModule, lambdaMapping,
572 inliningContext.classRegeneration,
573 isAlreadyRegenerated(owner),
574 desc,
575 false
576 );
577 }
578
579 private boolean isAlreadyRegenerated(@NotNull String owner) {
580 return inliningContext.typeRemapper.hasNoAdditionalMapping(owner);
581 }
582
583 @Nullable
584 private LambdaInfo getLambdaIfExistsAndMarkInstructions(
585 @Nullable AbstractInsnNode insnNode,
586 @NotNull Frame<SourceValue> localFrame,
587 @NotNull InstructionsAndFrames insAndFrames,
588 @NotNull Set<AbstractInsnNode> toDelete
589 ) {
590 LambdaInfo lambdaInfo = getLambdaIfExists(insnNode);
591
592 if (lambdaInfo == null && insnNode instanceof VarInsnNode && insnNode.getOpcode() == Opcodes.ALOAD) {
593 int varIndex = ((VarInsnNode) insnNode).var;
594 SourceValue local = localFrame.getLocal(varIndex);
595 if (local.insns.size() == 1) {
596 AbstractInsnNode storeIns = local.insns.iterator().next();
597 if (storeIns instanceof VarInsnNode && storeIns.getOpcode() == Opcodes.ASTORE) {
598 Frame<SourceValue> frame = insAndFrames.get(storeIns);
599 if (frame != null) {
600 SourceValue topOfStack = frame.getStack(frame.getStackSize() - 1);
601 if(topOfStack.insns.size() == 1) {
602 AbstractInsnNode lambdaAload = topOfStack.insns.iterator().next();
603 lambdaInfo = getLambdaIfExistsAndMarkInstructions(lambdaAload, frame, insAndFrames, toDelete);
604 if (lambdaInfo != null) {
605 toDelete.add(storeIns);
606 toDelete.add(lambdaAload);
607 }
608 }
609 }
610 }
611 }
612 }
613
614 return lambdaInfo;
615 }
616
617 @Nullable
618 private LambdaInfo getLambdaIfExists(@Nullable AbstractInsnNode insnNode) {
619 if (insnNode == null) {
620 return null;
621 }
622
623 if (insnNode.getOpcode() == Opcodes.ALOAD) {
624 int varIndex = ((VarInsnNode) insnNode).var;
625 return getLambdaIfExists(varIndex);
626 }
627 else if (insnNode instanceof FieldInsnNode) {
628 FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode;
629 if (fieldInsnNode.name.startsWith("$$$")) {
630 return findCapturedField(fieldInsnNode, nodeRemapper).getLambda();
631 }
632 }
633
634 return null;
635 }
636
637 private LambdaInfo getLambdaIfExists(int varIndex) {
638 if (varIndex < parameters.getArgsSizeOnStack()) {
639 return parameters.getParameterByDeclarationSlot(varIndex).getLambda();
640 }
641 return null;
642 }
643
644 private static void removeClosureAssertions(MethodNode node) {
645 AbstractInsnNode cur = node.instructions.getFirst();
646 while (cur != null && cur.getNext() != null) {
647 AbstractInsnNode next = cur.getNext();
648 if (next.getType() == AbstractInsnNode.METHOD_INSN) {
649 MethodInsnNode methodInsnNode = (MethodInsnNode) next;
650 if (methodInsnNode.name.equals("checkParameterIsNotNull") && methodInsnNode.owner.equals(IntrinsicMethods.INTRINSICS_CLASS_NAME)) {
651 AbstractInsnNode prev = cur.getPrevious();
652
653 assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur;
654 assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev;
655
656 node.instructions.remove(prev);
657 node.instructions.remove(cur);
658 cur = next.getNext();
659 node.instructions.remove(next);
660 next = cur;
661 }
662 }
663 cur = next;
664 }
665 }
666
667 private void transformCaptured(@NotNull MethodNode node) {
668 if (nodeRemapper.isRoot()) {
669 return;
670 }
671
672 //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured
673 //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0)
674 AbstractInsnNode cur = node.instructions.getFirst();
675 while (cur != null) {
676 if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) {
677 int varIndex = ((VarInsnNode) cur).var;
678 if (varIndex == 0 || nodeRemapper.processNonAload0FieldAccessChains(getLambdaIfExists(varIndex) != null)) {
679 List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur);
680 AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node);
681 if (insnNode != null) {
682 cur = insnNode;
683 }
684 }
685 }
686 cur = cur.getNext();
687 }
688 }
689
690 private static void transformFinallyDeepIndex(@NotNull MethodNode node, int finallyDeepShift) {
691 if (finallyDeepShift == 0) {
692 return;
693 }
694
695 AbstractInsnNode cur = node.instructions.getFirst();
696 while (cur != null) {
697 if (cur instanceof MethodInsnNode && InlineCodegenUtil.isFinallyMarker(cur)) {
698 AbstractInsnNode constant = cur.getPrevious();
699 int curDeep = InlineCodegenUtil.getConstant(constant);
700 node.instructions.insert(constant, new LdcInsnNode(curDeep + finallyDeepShift));
701 node.instructions.remove(constant);
702 }
703 cur = cur.getNext();
704 }
705 }
706
707 @NotNull
708 public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) {
709 List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
710 fieldAccessChain.add(aload0);
711 AbstractInsnNode next = aload0.getNext();
712 while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) {
713 if (next instanceof LabelNode) {
714 next = next.getNext();
715 continue; //it will be delete on transformation
716 }
717 fieldAccessChain.add(next);
718 if ("this$0".equals(((FieldInsnNode) next).name)) {
719 next = next.getNext();
720 }
721 else {
722 break;
723 }
724 }
725
726 return fieldAccessChain;
727 }
728
729 public static void putStackValuesIntoLocals(List<Type> directOrder, int shift, InstructionAdapter iv, String descriptor) {
730 Type[] actualParams = Type.getArgumentTypes(descriptor);
731 assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!";
732
733 int size = 0;
734 for (Type next : directOrder) {
735 size += next.getSize();
736 }
737
738 shift += size;
739 int index = directOrder.size();
740
741 for (Type next : Lists.reverse(directOrder)) {
742 shift -= next.getSize();
743 Type typeOnStack = actualParams[--index];
744 if (!typeOnStack.equals(next)) {
745 StackValue.onStack(typeOnStack).put(next, iv);
746 }
747 iv.store(shift, next);
748 }
749 }
750
751 //TODO: check it's external module
752 //TODO?: assert method exists in facade?
753 public String changeOwnerForExternalPackage(String type, int opcode) {
754 //if (isSameModule || (opcode & Opcodes.INVOKESTATIC) == 0) {
755 // return type;
756 //}
757
758 //JvmClassName name = JvmClassName.byInternalName(type);
759 //String packageClassInternalName = PackageClassUtils.getPackageClassInternalName(name.getPackageFqName());
760 //if (type.startsWith(packageClassInternalName + '$')) {
761 // VirtualFile virtualFile = InlineCodegenUtil.findVirtualFile(inliningContext.state.getProject(), type);
762 // if (virtualFile != null) {
763 // KotlinJvmBinaryClass klass = KotlinBinaryClassCache.getKotlinBinaryClass(virtualFile);
764 // if (klass != null && klass.getClassHeader().getSyntheticClassKind() == KotlinSyntheticClass.Kind.PACKAGE_PART) {
765 // return packageClassInternalName;
766 // }
767 // }
768 //}
769
770 return type;
771 }
772
773 @NotNull
774 public RuntimeException wrapException(@NotNull Throwable originalException, @NotNull MethodNode node, @NotNull String errorSuffix) {
775 if (originalException instanceof InlineException) {
776 return new InlineException(errorPrefix + ": " + errorSuffix, originalException);
777 }
778 else {
779 return new InlineException(errorPrefix + ": " + errorSuffix + "\ncause: " +
780 getNodeText(node), originalException);
781 }
782 }
783
784 @NotNull
785 //process local and global returns (local substituted with goto end-label global kept unchanged)
786 public static List<PointForExternalFinallyBlocks> processReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, Label endLabel) {
787 if (!remapReturn) {
788 return Collections.emptyList();
789 }
790 List<PointForExternalFinallyBlocks> result = new ArrayList<PointForExternalFinallyBlocks>();
791 InsnList instructions = node.instructions;
792 AbstractInsnNode insnNode = instructions.getFirst();
793 while (insnNode != null) {
794 if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
795 AbstractInsnNode previous = insnNode.getPrevious();
796 MethodInsnNode flagNode;
797 boolean isLocalReturn = true;
798 String labelName = null;
799 if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) {
800 flagNode = (MethodInsnNode) previous;
801 labelName = flagNode.name;
802 }
803
804 if (labelName != null) {
805 isLocalReturn = labelOwner.isMyLabel(labelName);
806 //remove global return flag
807 if (isLocalReturn) {
808 instructions.remove(previous);
809 }
810 }
811
812 if (isLocalReturn && endLabel != null) {
813 LabelNode labelNode = (LabelNode) endLabel.info;
814 JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode);
815 instructions.insert(insnNode, jumpInsnNode);
816 instructions.remove(insnNode);
817 insnNode = jumpInsnNode;
818 }
819
820 //genetate finally block before nonLocalReturn flag/return/goto
821 LabelNode label = new LabelNode();
822 instructions.insert(insnNode, label);
823 result.add(new PointForExternalFinallyBlocks(getInstructionToInsertFinallyBefore(insnNode, isLocalReturn),
824 getReturnType(insnNode.getOpcode()),
825 label));
826 }
827 insnNode = insnNode.getNext();
828 }
829 return result;
830 }
831
832 @NotNull
833 private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal) {
834 return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious();
835 }
836
837 //Place to insert finally blocks from try blocks that wraps inline fun call
838 public static class PointForExternalFinallyBlocks {
839
840 final AbstractInsnNode beforeIns;
841
842 final Type returnType;
843
844 final LabelNode finallyIntervalEnd;
845
846 public PointForExternalFinallyBlocks(
847 @NotNull AbstractInsnNode beforeIns,
848 @NotNull Type returnType,
849 @NotNull LabelNode finallyIntervalEnd
850 ) {
851 this.beforeIns = beforeIns;
852 this.returnType = returnType;
853 this.finallyIntervalEnd = finallyIntervalEnd;
854 }
855
856 }
857
858 }