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