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 if (invokeCall.lambdaInfo.getFunctionDescriptor().getValueParameters().isEmpty()) {
223 // There won't be no parameters processing and line call can be left without actual instructions.
224 // Note: if function is called on the line with other instructions like 1 + foo(), 'nop' will still be generated.
225 visitInsn(Opcodes.NOP);
226 }
227
228 addInlineMarker(this, true);
229 Parameters lambdaParameters = info.addAllParameters(nodeRemapper);
230
231 InlinedLambdaRemapper newCapturedRemapper =
232 new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), nodeRemapper, lambdaParameters);
233
234 setLambdaInlining(true);
235 SMAP lambdaSMAP = info.getNode().getClassSMAP();
236 //noinspection ConstantConditions
237 SourceMapper mapper =
238 inliningContext.classRegeneration && !inliningContext.isInliningLambda
239 ? new NestedSourceMapper(sourceMapper, lambdaSMAP.getIntervals(), lambdaSMAP.getSourceInfo())
240 : new InlineLambdaSourceMapper(sourceMapper.getParent(), info.getNode());
241 MethodInliner inliner = new MethodInliner(
242 info.getNode().getNode(), lambdaParameters, inliningContext.subInlineLambda(info),
243 newCapturedRemapper, true /*cause all calls in same module as lambda*/,
244 "Lambda inlining " + info.getLambdaClassType().getInternalName(),
245 mapper, inlineCallSiteInfo, null
246 );
247
248 LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift);
249 //TODO add skipped this and receiver
250 InlineResult lambdaResult = inliner.doInline(this.mv, remapper, true, info, invokeCall.finallyDepthShift);
251 result.addAllClassesToRemove(lambdaResult);
252
253 //return value boxing/unboxing
254 Method bridge = typeMapper.mapAsmMethod(ClosureCodegen.getErasedInvokeFunction(info.getFunctionDescriptor()));
255 Method delegate = typeMapper.mapAsmMethod(info.getFunctionDescriptor());
256 StackValue.onStack(delegate.getReturnType()).put(bridge.getReturnType(), this);
257 setLambdaInlining(false);
258 addInlineMarker(this, false);
259 mapper.endMapping();
260 if (inlineOnlySmapSkipper != null) {
261 inlineOnlySmapSkipper.markCallSiteLineNumber(remappingMethodAdapter);
262 }
263 }
264 else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
265 //TODO add proper message
266 assert transformationInfo instanceof AnonymousObjectTransformationInfo :
267 "<init> call doesn't correspond to object transformation info: " +
268 owner + "." + name + ", info " + transformationInfo;
269 if (transformationInfo.shouldRegenerate(isSameModule)) {
270 //put additional captured parameters on stack
271 AnonymousObjectTransformationInfo info = (AnonymousObjectTransformationInfo) transformationInfo;
272 for (CapturedParamDesc capturedParamDesc : info.getAllRecapturedParameters()) {
273 visitFieldInsn(
274 Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(),
275 "$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor()
276 );
277 }
278 super.visitMethodInsn(opcode, transformationInfo.getNewClassName(), name, info.getNewConstructorDescriptor(), itf);
279
280 //TODO: add new inner class also for other contexts
281 if (inliningContext.getParent() instanceof RegeneratedClassContext) {
282 inliningContext.getParent().typeRemapper.addAdditionalMappings(
283 transformationInfo.getOldClassName(), transformationInfo.getNewClassName()
284 );
285 }
286
287 transformationInfo = null;
288 }
289 else {
290 super.visitMethodInsn(opcode, owner, name, desc, itf);
291 }
292 }
293 else if (!inliningContext.isInliningLambda &&
294 ReifiedTypeInliner.isNeedClassReificationMarker(new MethodInsnNode(opcode, owner, name, desc, false))) {
295 //we shouldn't process here content of inlining lambda it should be reified at external level
296 }
297 else {
298 super.visitMethodInsn(opcode, owner, name, desc, itf);
299 }
300 }
301
302 @Override
303 public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
304 if (opcode == Opcodes.GETSTATIC && (isAnonymousSingletonLoad(owner, name) || isWhenMappingAccess(owner, name))) {
305 handleAnonymousObjectRegeneration();
306 }
307 super.visitFieldInsn(opcode, owner, name, desc);
308 }
309
310 @Override
311 public void visitMaxs(int stack, int locals) {
312 lambdasFinallyBlocks = resultNode.tryCatchBlocks.size();
313 super.visitMaxs(stack, locals);
314 }
315 };
316
317 node.accept(lambdaInliner);
318
319 return resultNode;
320 }
321
322 @NotNull
323 public static CapturedParamInfo findCapturedField(@NotNull FieldInsnNode node, @NotNull FieldRemapper fieldRemapper) {
324 assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix";
325 FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
326 CapturedParamInfo field = fieldRemapper.findField(fin);
327 if (field == null) {
328 throw new IllegalStateException(
329 "Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName()
330 );
331 }
332 return field;
333 }
334
335 @NotNull
336 private MethodNode prepareNode(@NotNull MethodNode node, int finallyDeepShift) {
337 final int capturedParamsSize = parameters.getCapturedArgsSizeOnStack();
338 final int realParametersSize = parameters.getRealArgsSizeOnStack();
339 Type[] types = Type.getArgumentTypes(node.desc);
340 Type returnType = Type.getReturnType(node.desc);
341
342 List<Type> capturedTypes = parameters.getCapturedTypes();
343 Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()]));
344
345 node.instructions.resetLabels();
346 MethodNode transformedNode = new MethodNode(
347 InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null
348 ) {
349 @SuppressWarnings("ConstantConditions")
350 private final boolean GENERATE_DEBUG_INFO = InlineCodegenUtil.GENERATE_SMAP && inlineOnlySmapSkipper == null;
351
352 private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda();
353
354 private int getNewIndex(int var) {
355 return var + (var < realParametersSize ? 0 : capturedParamsSize);
356 }
357
358 @Override
359 public void visitVarInsn(int opcode, int var) {
360 super.visitVarInsn(opcode, getNewIndex(var));
361 }
362
363 @Override
364 public void visitIincInsn(int var, int increment) {
365 super.visitIincInsn(getNewIndex(var), increment);
366 }
367
368 @Override
369 public void visitMaxs(int maxStack, int maxLocals) {
370 super.visitMaxs(maxStack, maxLocals + capturedParamsSize);
371 }
372
373 @Override
374 public void visitLineNumber(int line, @NotNull Label start) {
375 if (isInliningLambda || GENERATE_DEBUG_INFO) {
376 super.visitLineNumber(line, start);
377 }
378 }
379
380 @Override
381 public void visitLocalVariable(
382 @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index
383 ) {
384 if (isInliningLambda || GENERATE_DEBUG_INFO) {
385 String varSuffix =
386 inliningContext.isRoot() && !InlineCodegenUtil.isFakeLocalVariableForInline(name) ? INLINE_FUN_VAR_SUFFIX : "";
387 String varName = !varSuffix.isEmpty() && name.equals("this") ? name + "_" : name;
388 super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index));
389 }
390 }
391 };
392
393 node.accept(transformedNode);
394
395 transformCaptured(transformedNode);
396 transformFinallyDeepIndex(transformedNode, finallyDeepShift);
397
398 return transformedNode;
399 }
400
401 @NotNull
402 private MethodNode markPlacesForInlineAndRemoveInlinable(
403 @NotNull MethodNode node, @NotNull LabelOwner labelOwner, int finallyDeepShift
404 ) {
405 node = prepareNode(node, finallyDeepShift);
406
407 Frame<SourceValue>[] sources = analyzeMethodNodeBeforeInline(node);
408 LocalReturnsNormalizer localReturnsNormalizer = LocalReturnsNormalizer.createFor(node, labelOwner, sources);
409
410 Set<AbstractInsnNode> toDelete = SmartSet.create();
411 InsnList instructions = node.instructions;
412 AbstractInsnNode cur = instructions.getFirst();
413
414 boolean awaitClassReification = false;
415 int currentFinallyDeep = 0;
416
417 while (cur != null) {
418 Frame<SourceValue> frame = sources[instructions.indexOf(cur)];
419
420 if (frame != null) {
421 if (ReifiedTypeInliner.isNeedClassReificationMarker(cur)) {
422 awaitClassReification = true;
423 }
424 else if (cur.getType() == AbstractInsnNode.METHOD_INSN) {
425 if (InlineCodegenUtil.isFinallyStart(cur)) {
426 //TODO deep index calc could be more precise
427 currentFinallyDeep = InlineCodegenUtil.getConstant(cur.getPrevious());
428 }
429
430 MethodInsnNode methodInsnNode = (MethodInsnNode) cur;
431 String owner = methodInsnNode.owner;
432 String desc = methodInsnNode.desc;
433 String name = methodInsnNode.name;
434 //TODO check closure
435 Type[] argTypes = Type.getArgumentTypes(desc);
436 int paramCount = argTypes.length + 1;//non static
437 int firstParameterIndex = frame.getStackSize() - paramCount;
438 if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
439 SourceValue sourceValue = frame.getStack(firstParameterIndex);
440
441 LambdaInfo lambdaInfo = MethodInlinerUtilKt.getLambdaIfExistsAndMarkInstructions(
442 this, MethodInlinerUtilKt.singleOrNullInsn(sourceValue), true, instructions, sources, toDelete
443 );
444
445 invokeCalls.add(new InvokeCall(lambdaInfo, currentFinallyDeep));
446 }
447 else if (isAnonymousConstructorCall(owner, name)) {
448 Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
449
450 int offset = 0;
451 for (int i = 0; i < paramCount; i++) {
452 SourceValue sourceValue = frame.getStack(firstParameterIndex + i);
453 LambdaInfo lambdaInfo = MethodInlinerUtilKt.getLambdaIfExistsAndMarkInstructions(
454 this, MethodInlinerUtilKt.singleOrNullInsn(sourceValue), false, instructions, sources, toDelete
455 );
456 if (lambdaInfo != null) {
457 lambdaMapping.put(offset, lambdaInfo);
458 }
459
460 offset += i == 0 ? 1 : argTypes[i - 1].getSize();
461 }
462
463 transformations.add(
464 buildConstructorInvocation(owner, desc, lambdaMapping, awaitClassReification)
465 );
466 awaitClassReification = false;
467 }
468 }
469 else if (cur.getOpcode() == Opcodes.GETSTATIC) {
470 FieldInsnNode fieldInsnNode = (FieldInsnNode) cur;
471 String className = fieldInsnNode.owner;
472 if (isAnonymousSingletonLoad(className, fieldInsnNode.name)) {
473 transformations.add(
474 new AnonymousObjectTransformationInfo(
475 className, awaitClassReification, isAlreadyRegenerated(className), true,
476 inliningContext.nameGenerator
477 )
478 );
479 awaitClassReification = false;
480 }
481 else if (isWhenMappingAccess(className, fieldInsnNode.name)) {
482 transformations.add(
483 new WhenMappingTransformationInfo(
484 className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode
485 )
486 );
487 }
488
489 }
490 }
491 AbstractInsnNode prevNode = cur;
492 cur = cur.getNext();
493
494 //given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached (dead code).
495 if (frame == null) {
496 //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
497 if (prevNode.getType() == AbstractInsnNode.LABEL) {
498 //NB: Cause we generate exception table for default handler using gaps (see ExpressionCodegen.visitTryExpression)
499 //it may occurs that interval for default handler starts before catch start label, so this label seems as dead,
500 //but as result all this labels will be merged into one (see KT-5863)
501 }
502 else {
503 toDelete.add(prevNode);
504 }
505 }
506 }
507
508 for (AbstractInsnNode insnNode : toDelete) {
509 instructions.remove(insnNode);
510 }
511
512 //clean dead try/catch blocks
513 List<TryCatchBlockNode> blocks = node.tryCatchBlocks;
514 for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) {
515 TryCatchBlockNode block = iterator.next();
516 if (isEmptyTryInterval(block)) {
517 iterator.remove();
518 }
519 }
520
521 localReturnsNormalizer.transform(node);
522
523 return node;
524 }
525
526 @NotNull
527 private Frame<SourceValue>[] analyzeMethodNodeBeforeInline(@NotNull MethodNode node) {
528 try {
529 new MandatoryMethodTransformer().transform("fake", node);
530 }
531 catch (Throwable e) {
532 throw wrapException(e, node, "couldn't inline method call");
533 }
534
535 Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
536 @NotNull
537 @Override
538 protected Frame<SourceValue> newFrame(int nLocals, int nStack) {
539 return new Frame<SourceValue>(nLocals, nStack) {
540 @Override
541 public void execute(@NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter) throws AnalyzerException {
542 // This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else.
543 if (insn.getOpcode() == Opcodes.RETURN) return;
544 super.execute(insn, interpreter);
545 }
546 };
547 }
548 };
549
550 try {
551 return analyzer.analyze("fake", node);
552 }
553 catch (AnalyzerException e) {
554 throw wrapException(e, node, "couldn't inline method call");
555 }
556 }
557
558 private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) {
559 LabelNode start = tryCatchBlockNode.start;
560 AbstractInsnNode end = tryCatchBlockNode.end;
561 while (end != start && end instanceof LabelNode) {
562 end = end.getPrevious();
563 }
564 return start == end;
565 }
566
567 @NotNull
568 private AnonymousObjectTransformationInfo buildConstructorInvocation(
569 @NotNull String anonymousType,
570 @NotNull String desc,
571 @NotNull Map<Integer, LambdaInfo> lambdaMapping,
572 boolean needReification
573 ) {
574 return new AnonymousObjectTransformationInfo(
575 anonymousType, needReification, lambdaMapping,
576 inliningContext.classRegeneration,
577 isAlreadyRegenerated(anonymousType),
578 desc,
579 false,
580 inliningContext.nameGenerator
581 );
582 }
583
584 private boolean isAlreadyRegenerated(@NotNull String owner) {
585 return inliningContext.typeRemapper.hasNoAdditionalMapping(owner);
586 }
587
588 @Nullable
589 LambdaInfo getLambdaIfExists(@Nullable AbstractInsnNode insnNode) {
590 if (insnNode == null) {
591 return null;
592 }
593
594 if (insnNode.getOpcode() == Opcodes.ALOAD) {
595 int varIndex = ((VarInsnNode) insnNode).var;
596 return getLambdaIfExists(varIndex);
597 }
598
599 if (insnNode instanceof FieldInsnNode) {
600 FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode;
601 if (fieldInsnNode.name.startsWith("$$$")) {
602 return findCapturedField(fieldInsnNode, nodeRemapper).getLambda();
603 }
604 }
605
606 return null;
607 }
608
609 @Nullable
610 private LambdaInfo getLambdaIfExists(int varIndex) {
611 if (varIndex < parameters.getArgsSizeOnStack()) {
612 return parameters.getParameterByDeclarationSlot(varIndex).getLambda();
613 }
614 return null;
615 }
616
617 private static void removeClosureAssertions(@NotNull MethodNode node) {
618 AbstractInsnNode cur = node.instructions.getFirst();
619 while (cur != null && cur.getNext() != null) {
620 AbstractInsnNode next = cur.getNext();
621 if (next.getType() == AbstractInsnNode.METHOD_INSN) {
622 MethodInsnNode methodInsnNode = (MethodInsnNode) next;
623 if (methodInsnNode.name.equals("checkParameterIsNotNull") &&
624 methodInsnNode.owner.equals(IntrinsicMethods.INTRINSICS_CLASS_NAME)) {
625 AbstractInsnNode prev = cur.getPrevious();
626
627 assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur;
628 assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev;
629
630 node.instructions.remove(prev);
631 node.instructions.remove(cur);
632 cur = next.getNext();
633 node.instructions.remove(next);
634 next = cur;
635 }
636 }
637 cur = next;
638 }
639 }
640
641 private void transformCaptured(@NotNull MethodNode node) {
642 if (nodeRemapper.isRoot()) {
643 return;
644 }
645
646 //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured
647 //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0)
648 AbstractInsnNode cur = node.instructions.getFirst();
649 while (cur != null) {
650 if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) {
651 int varIndex = ((VarInsnNode) cur).var;
652 if (varIndex == 0 || nodeRemapper.processNonAload0FieldAccessChains(getLambdaIfExists(varIndex) != null)) {
653 List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur);
654 AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node);
655 if (insnNode != null) {
656 cur = insnNode;
657 }
658 }
659 }
660 cur = cur.getNext();
661 }
662 }
663
664 private static void transformFinallyDeepIndex(@NotNull MethodNode node, int finallyDeepShift) {
665 if (finallyDeepShift == 0) {
666 return;
667 }
668
669 AbstractInsnNode cur = node.instructions.getFirst();
670 while (cur != null) {
671 if (cur instanceof MethodInsnNode && InlineCodegenUtil.isFinallyMarker(cur)) {
672 AbstractInsnNode constant = cur.getPrevious();
673 int curDeep = InlineCodegenUtil.getConstant(constant);
674 node.instructions.insert(constant, new LdcInsnNode(curDeep + finallyDeepShift));
675 node.instructions.remove(constant);
676 }
677 cur = cur.getNext();
678 }
679 }
680
681 @NotNull
682 private static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) {
683 List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
684 fieldAccessChain.add(aload0);
685 AbstractInsnNode next = aload0.getNext();
686 while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) {
687 if (next instanceof LabelNode) {
688 next = next.getNext();
689 continue; //it will be delete on transformation
690 }
691 fieldAccessChain.add(next);
692 if ("this$0".equals(((FieldInsnNode) next).name)) {
693 next = next.getNext();
694 }
695 else {
696 break;
697 }
698 }
699
700 return fieldAccessChain;
701 }
702
703 private static void putStackValuesIntoLocals(
704 @NotNull List<Type> directOrder, int shift, @NotNull InstructionAdapter iv, @NotNull String descriptor
705 ) {
706 Type[] actualParams = Type.getArgumentTypes(descriptor);
707 assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!";
708
709 int size = 0;
710 for (Type next : directOrder) {
711 size += next.getSize();
712 }
713
714 shift += size;
715 int index = directOrder.size();
716
717 for (Type next : Lists.reverse(directOrder)) {
718 shift -= next.getSize();
719 Type typeOnStack = actualParams[--index];
720 if (!typeOnStack.equals(next)) {
721 StackValue.onStack(typeOnStack).put(next, iv);
722 }
723 iv.store(shift, next);
724 }
725 }
726
727 @NotNull
728 private RuntimeException wrapException(@NotNull Throwable originalException, @NotNull MethodNode node, @NotNull String errorSuffix) {
729 if (originalException instanceof InlineException) {
730 return new InlineException(errorPrefix + ": " + errorSuffix, originalException);
731 }
732 else {
733 return new InlineException(errorPrefix + ": " + errorSuffix + "\nCause: " + getNodeText(node), originalException);
734 }
735 }
736
737 @NotNull
738 //process local and global returns (local substituted with goto end-label global kept unchanged)
739 public static List<PointForExternalFinallyBlocks> processReturns(
740 @NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, @Nullable Label endLabel
741 ) {
742 if (!remapReturn) {
743 return Collections.emptyList();
744 }
745 List<PointForExternalFinallyBlocks> result = new ArrayList<PointForExternalFinallyBlocks>();
746 InsnList instructions = node.instructions;
747 AbstractInsnNode insnNode = instructions.getFirst();
748 while (insnNode != null) {
749 if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
750 boolean isLocalReturn = true;
751 String labelName = InlineCodegenUtil.getMarkedReturnLabelOrNull(insnNode);
752
753 if (labelName != null) {
754 isLocalReturn = labelOwner.isMyLabel(labelName);
755 //remove global return flag
756 if (isLocalReturn) {
757 instructions.remove(insnNode.getPrevious());
758 }
759 }
760
761 if (isLocalReturn && endLabel != null) {
762 LabelNode labelNode = (LabelNode) endLabel.info;
763 JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode);
764 instructions.insert(insnNode, jumpInsnNode);
765 instructions.remove(insnNode);
766 insnNode = jumpInsnNode;
767 }
768
769 //generate finally block before nonLocalReturn flag/return/goto
770 LabelNode label = new LabelNode();
771 instructions.insert(insnNode, label);
772 result.add(new PointForExternalFinallyBlocks(
773 getInstructionToInsertFinallyBefore(insnNode, isLocalReturn), getReturnType(insnNode.getOpcode()), label
774 ));
775 }
776 insnNode = insnNode.getNext();
777 }
778 return result;
779 }
780
781 private static class LocalReturnsNormalizer {
782 private static class LocalReturn {
783 private final AbstractInsnNode returnInsn;
784 private final AbstractInsnNode insertBeforeInsn;
785 private final Frame<SourceValue> frame;
786
787 public LocalReturn(
788 @NotNull AbstractInsnNode returnInsn,
789 @NotNull AbstractInsnNode insertBeforeInsn,
790 @NotNull Frame<SourceValue> frame
791 ) {
792 this.returnInsn = returnInsn;
793 this.insertBeforeInsn = insertBeforeInsn;
794 this.frame = frame;
795 }
796
797 public void transform(@NotNull InsnList insnList, int returnVariableIndex) {
798 boolean isReturnWithValue = returnInsn.getOpcode() != Opcodes.RETURN;
799
800 int expectedStackSize = isReturnWithValue ? 1 : 0;
801 int actualStackSize = frame.getStackSize();
802 if (expectedStackSize == actualStackSize) return;
803
804 int stackSize = actualStackSize;
805 if (isReturnWithValue) {
806 int storeOpcode = Opcodes.ISTORE + returnInsn.getOpcode() - Opcodes.IRETURN;
807 insnList.insertBefore(insertBeforeInsn, new VarInsnNode(storeOpcode, returnVariableIndex));
808 stackSize--;
809 }
810
811 while (stackSize > 0) {
812 int stackElementSize = frame.getStack(stackSize - 1).getSize();
813 int popOpcode = stackElementSize == 1 ? Opcodes.POP : Opcodes.POP2;
814 insnList.insertBefore(insertBeforeInsn, new InsnNode(popOpcode));
815 stackSize--;
816 }
817
818 if (isReturnWithValue) {
819 int loadOpcode = Opcodes.ILOAD + returnInsn.getOpcode() - Opcodes.IRETURN;
820 insnList.insertBefore(insertBeforeInsn, new VarInsnNode(loadOpcode, returnVariableIndex));
821 }
822 }
823 }
824
825 private final List<LocalReturn> localReturns = new SmartList<LocalReturn>();
826
827 private boolean needsReturnVariable = false;
828 private int returnOpcode = -1;
829
830 private void addLocalReturnToTransform(
831 @NotNull AbstractInsnNode returnInsn,
832 @NotNull AbstractInsnNode insertBeforeInsn,
833 @NotNull Frame<SourceValue> sourceValueFrame
834 ) {
835 assert InlineCodegenUtil.isReturnOpcode(returnInsn.getOpcode()) : "return instruction expected";
836 assert returnOpcode < 0 || returnOpcode == returnInsn.getOpcode() :
837 "Return op should be " + Printer.OPCODES[returnOpcode] + ", got " + Printer.OPCODES[returnInsn.getOpcode()];
838 returnOpcode = returnInsn.getOpcode();
839
840 localReturns.add(new LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame));
841
842 if (returnInsn.getOpcode() != Opcodes.RETURN && sourceValueFrame.getStackSize() > 1) {
843 needsReturnVariable = true;
844 }
845 }
846
847 public void transform(@NotNull MethodNode methodNode) {
848 int returnVariableIndex = -1;
849 if (needsReturnVariable) {
850 returnVariableIndex = methodNode.maxLocals;
851 methodNode.maxLocals++;
852 }
853
854 for (LocalReturn localReturn : localReturns) {
855 localReturn.transform(methodNode.instructions, returnVariableIndex);
856 }
857 }
858
859 @NotNull
860 public static LocalReturnsNormalizer createFor(
861 @NotNull MethodNode methodNode,
862 @NotNull LabelOwner owner,
863 @NotNull Frame<SourceValue>[] frames
864 ) {
865 LocalReturnsNormalizer result = new LocalReturnsNormalizer();
866
867 AbstractInsnNode[] instructions = methodNode.instructions.toArray();
868
869 for (int i = 0; i < instructions.length; ++i) {
870 Frame<SourceValue> frame = frames[i];
871 // Don't care about dead code, it will be eliminated
872 if (frame == null) continue;
873
874 AbstractInsnNode insnNode = instructions[i];
875 if (!InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) continue;
876
877 AbstractInsnNode insertBeforeInsn = insnNode;
878
879 // TODO extract isLocalReturn / isNonLocalReturn, see processReturns
880 String labelName = getMarkedReturnLabelOrNull(insnNode);
881 if (labelName != null) {
882 if (!owner.isMyLabel(labelName)) continue;
883 insertBeforeInsn = insnNode.getPrevious();
884 }
885
886 result.addLocalReturnToTransform(insnNode, insertBeforeInsn, frame);
887 }
888
889 return result;
890 }
891 }
892
893 @NotNull
894 private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal) {
895 return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious();
896 }
897
898 //Place to insert finally blocks from try blocks that wraps inline fun call
899 public static class PointForExternalFinallyBlocks {
900 public final AbstractInsnNode beforeIns;
901 public final Type returnType;
902 public final LabelNode finallyIntervalEnd;
903
904 public PointForExternalFinallyBlocks(
905 @NotNull AbstractInsnNode beforeIns,
906 @NotNull Type returnType,
907 @NotNull LabelNode finallyIntervalEnd
908 ) {
909 this.beforeIns = beforeIns;
910 this.returnType = returnType;
911 this.finallyIntervalEnd = finallyIntervalEnd;
912 }
913 }
914 }