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