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