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