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