001 /* 002 * Copyright 2010-2014 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.jet.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.jet.codegen.ClosureCodegen; 023 import org.jetbrains.jet.codegen.StackValue; 024 import org.jetbrains.jet.codegen.state.JetTypeMapper; 025 import org.jetbrains.org.objectweb.asm.Label; 026 import org.jetbrains.org.objectweb.asm.MethodVisitor; 027 import org.jetbrains.org.objectweb.asm.Opcodes; 028 import org.jetbrains.org.objectweb.asm.Type; 029 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 030 import org.jetbrains.org.objectweb.asm.commons.Method; 031 import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter; 032 import org.jetbrains.org.objectweb.asm.tree.*; 033 import org.jetbrains.org.objectweb.asm.tree.analysis.*; 034 035 import java.util.*; 036 037 import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.getReturnType; 038 import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.isAnonymousConstructorCall; 039 import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.isInvokeOnLambda; 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 JetTypeMapper typeMapper; 056 057 private final List<InvokeCall> invokeCalls = new ArrayList<InvokeCall>(); 058 059 //keeps order 060 private final List<ConstructorInvocation> constructorInvocations = new ArrayList<ConstructorInvocation>(); 061 //current state 062 private final Map<String, String> currentTypeMapping = new HashMap<String, String>(); 063 064 private final InlineResult result; 065 066 /* 067 * 068 * @param node 069 * @param parameters 070 * @param inliningContext 071 * @param lambdaType - in case on lambda 'invoke' inlining 072 */ 073 public MethodInliner( 074 @NotNull MethodNode node, 075 @NotNull Parameters parameters, 076 @NotNull InliningContext parent, 077 @NotNull FieldRemapper nodeRemapper, 078 boolean isSameModule, 079 @NotNull String errorPrefix 080 ) { 081 this.node = node; 082 this.parameters = parameters; 083 this.inliningContext = parent; 084 this.nodeRemapper = nodeRemapper; 085 this.isSameModule = isSameModule; 086 this.errorPrefix = errorPrefix; 087 this.typeMapper = parent.state.getTypeMapper(); 088 this.result = InlineResult.create(); 089 } 090 091 public InlineResult doInline( 092 @NotNull MethodVisitor adapter, 093 @NotNull LocalVarRemapper remapper, 094 boolean remapReturn, 095 @NotNull LabelOwner labelOwner 096 ) { 097 //analyze body 098 MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node); 099 100 //substitute returns with "goto end" instruction to keep non local returns in lambdas 101 Label end = new Label(); 102 transformedNode = doInline(transformedNode); 103 removeClosureAssertions(transformedNode); 104 InsnList instructions = transformedNode.instructions; 105 instructions.resetLabels(); 106 107 MethodNode resultNode = new MethodNode(InlineCodegenUtil.API, transformedNode.access, transformedNode.name, transformedNode.desc, 108 transformedNode.signature, ArrayUtil.toStringArray(transformedNode.exceptions)); 109 RemapVisitor visitor = new RemapVisitor(resultNode, remapper, nodeRemapper); 110 try { 111 transformedNode.accept(visitor); 112 } 113 catch (Exception e) { 114 throw wrapException(e, transformedNode, "couldn't inline method call"); 115 } 116 117 resultNode.visitLabel(end); 118 processReturns(resultNode, labelOwner, remapReturn, end); 119 120 //flush transformed node to output 121 resultNode.accept(new InliningInstructionAdapter(adapter)); 122 123 return result; 124 } 125 126 private MethodNode doInline(MethodNode node) { 127 128 final Deque<InvokeCall> currentInvokes = new LinkedList<InvokeCall>(invokeCalls); 129 130 MethodNode resultNode = new MethodNode(node.access, node.name, node.desc, node.signature, null); 131 132 final Iterator<ConstructorInvocation> iterator = constructorInvocations.iterator(); 133 134 RemappingMethodAdapter remappingMethodAdapter = new RemappingMethodAdapter(resultNode.access, resultNode.desc, resultNode, 135 new TypeRemapper(currentTypeMapping)); 136 137 InlineAdapter lambdaInliner = new InlineAdapter(remappingMethodAdapter, parameters.totalSize()) { 138 139 private ConstructorInvocation invocation; 140 @Override 141 public void anew(@NotNull Type type) { 142 if (isAnonymousConstructorCall(type.getInternalName(), "<init>")) { 143 invocation = iterator.next(); 144 145 if (invocation.shouldRegenerate()) { 146 //TODO: need poping of type but what to do with local funs??? 147 Type newLambdaType = Type.getObjectType(inliningContext.nameGenerator.genLambdaClassName()); 148 currentTypeMapping.put(invocation.getOwnerInternalName(), newLambdaType.getInternalName()); 149 AnonymousObjectTransformer transformer = 150 new AnonymousObjectTransformer(invocation.getOwnerInternalName(), 151 inliningContext 152 .subInlineWithClassRegeneration( 153 inliningContext.nameGenerator, 154 currentTypeMapping, 155 invocation), 156 isSameModule, newLambdaType 157 ); 158 159 InlineResult transformResult = transformer.doTransform(invocation, nodeRemapper); 160 result.addAllClassesToRemove(transformResult); 161 162 if (inliningContext.isInliningLambda) { 163 //this class is transformed and original not used so we should remove original one after inlining 164 result.addClassToRemove(invocation.getOwnerInternalName()); 165 } 166 } 167 } 168 169 //in case of regenerated invocation type would be remapped to new one via remappingMethodAdapter 170 super.anew(type); 171 } 172 173 @Override 174 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 175 if (/*INLINE_RUNTIME.equals(owner) &&*/ isInvokeOnLambda(owner, name)) { //TODO add method 176 assert !currentInvokes.isEmpty(); 177 InvokeCall invokeCall = currentInvokes.remove(); 178 LambdaInfo info = invokeCall.lambdaInfo; 179 180 if (info == null) { 181 //noninlinable lambda 182 super.visitMethodInsn(opcode, owner, name, desc, itf); 183 return; 184 } 185 186 int valueParamShift = getNextLocalIndex();//NB: don't inline cause it changes 187 putStackValuesIntoLocals(info.getInvokeParamsWithoutCaptured(), valueParamShift, this, desc); 188 189 Parameters lambdaParameters = info.addAllParameters(nodeRemapper); 190 191 InlinedLambdaRemapper newCapturedRemapper = 192 new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), nodeRemapper, lambdaParameters); 193 194 setLambdaInlining(true); 195 MethodInliner inliner = new MethodInliner(info.getNode(), lambdaParameters, 196 inliningContext.subInlineLambda(info), 197 newCapturedRemapper, true /*cause all calls in same module as lambda*/, 198 "Lambda inlining " + info.getLambdaClassType().getInternalName()); 199 200 LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift); 201 InlineResult lambdaResult = inliner.doInline(this.mv, remapper, true, info);//TODO add skipped this and receiver 202 result.addAllClassesToRemove(lambdaResult); 203 204 //return value boxing/unboxing 205 Method bridge = 206 typeMapper.mapSignature(ClosureCodegen.getErasedInvokeFunction(info.getFunctionDescriptor())).getAsmMethod(); 207 Method delegate = typeMapper.mapSignature(info.getFunctionDescriptor()).getAsmMethod(); 208 StackValue.onStack(delegate.getReturnType()).put(bridge.getReturnType(), this); 209 setLambdaInlining(false); 210 } 211 else if (isAnonymousConstructorCall(owner, name)) { //TODO add method 212 assert invocation != null : "<init> call not corresponds to new call" + owner + " " + name; 213 if (invocation.shouldRegenerate()) { 214 //put additional captured parameters on stack 215 for (CapturedParamDesc capturedParamDesc : invocation.getAllRecapturedParameters()) { 216 visitFieldInsn(Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(), 217 "$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor()); 218 } 219 super.visitMethodInsn(opcode, invocation.getNewLambdaType().getInternalName(), name, invocation.getNewConstructorDescriptor(), itf); 220 invocation = null; 221 } else { 222 super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf); 223 } 224 } 225 else { 226 super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf); 227 } 228 } 229 230 }; 231 232 node.accept(lambdaInliner); 233 234 return resultNode; 235 } 236 237 @NotNull 238 public static CapturedParamInfo findCapturedField(FieldInsnNode node, FieldRemapper fieldRemapper) { 239 assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix"; 240 FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc); 241 CapturedParamInfo field = fieldRemapper.findField(fin); 242 if (field == null) { 243 throw new IllegalStateException("Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName()); 244 } 245 return field; 246 } 247 248 @NotNull 249 public MethodNode prepareNode(@NotNull MethodNode node) { 250 final int capturedParamsSize = parameters.getCaptured().size(); 251 final int realParametersSize = parameters.getReal().size(); 252 Type[] types = Type.getArgumentTypes(node.desc); 253 Type returnType = Type.getReturnType(node.desc); 254 255 ArrayList<Type> capturedTypes = parameters.getCapturedTypes(); 256 Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()])); 257 258 node.instructions.resetLabels(); 259 MethodNode transformedNode = new MethodNode(InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null) { 260 261 private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda(); 262 263 private int getNewIndex(int var) { 264 return var + (var < realParametersSize ? 0 : capturedParamsSize); 265 } 266 267 @Override 268 public void visitVarInsn(int opcode, int var) { 269 super.visitVarInsn(opcode, getNewIndex(var)); 270 } 271 272 @Override 273 public void visitIincInsn(int var, int increment) { 274 super.visitIincInsn(getNewIndex(var), increment); 275 } 276 277 @Override 278 public void visitMaxs(int maxStack, int maxLocals) { 279 super.visitMaxs(maxStack, maxLocals + capturedParamsSize); 280 } 281 282 @Override 283 public void visitLineNumber(int line, @NotNull Label start) { 284 if(isInliningLambda) { 285 super.visitLineNumber(line, start); 286 } 287 } 288 289 @Override 290 public void visitLocalVariable( 291 @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index 292 ) { 293 if (isInliningLambda) { 294 super.visitLocalVariable(name, desc, signature, start, end, getNewIndex(index)); 295 } 296 } 297 }; 298 299 node.accept(transformedNode); 300 301 transformCaptured(transformedNode); 302 303 return transformedNode; 304 } 305 306 @NotNull 307 protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) { 308 node = prepareNode(node); 309 310 Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) { 311 @NotNull 312 @Override 313 protected Frame<SourceValue> newFrame( 314 int nLocals, int nStack 315 ) { 316 return new Frame<SourceValue>(nLocals, nStack) { 317 @Override 318 public void execute( 319 @NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter 320 ) throws AnalyzerException { 321 if (insn.getOpcode() == Opcodes.RETURN) { 322 //there is exception on void non local return in frame 323 return; 324 } 325 super.execute(insn, interpreter); 326 } 327 }; 328 } 329 }; 330 331 Frame<SourceValue>[] sources; 332 try { 333 sources = analyzer.analyze("fake", node); 334 } 335 catch (AnalyzerException e) { 336 throw wrapException(e, node, "couldn't inline method call"); 337 } 338 339 AbstractInsnNode cur = node.instructions.getFirst(); 340 int index = 0; 341 Set<LabelNode> deadLabels = new HashSet<LabelNode>(); 342 343 while (cur != null) { 344 Frame<SourceValue> frame = sources[index]; 345 346 if (frame != null) { 347 if (cur.getType() == AbstractInsnNode.METHOD_INSN) { 348 MethodInsnNode methodInsnNode = (MethodInsnNode) cur; 349 String owner = methodInsnNode.owner; 350 String desc = methodInsnNode.desc; 351 String name = methodInsnNode.name; 352 //TODO check closure 353 int paramLength = Type.getArgumentTypes(desc).length + 1;//non static 354 if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) { 355 SourceValue sourceValue = frame.getStack(frame.getStackSize() - paramLength); 356 357 LambdaInfo lambdaInfo = null; 358 int varIndex = -1; 359 360 if (sourceValue.insns.size() == 1) { 361 AbstractInsnNode insnNode = sourceValue.insns.iterator().next(); 362 363 lambdaInfo = getLambdaIfExists(insnNode); 364 if (lambdaInfo != null) { 365 //remove inlinable access 366 node.instructions.remove(insnNode); 367 } 368 } 369 370 invokeCalls.add(new InvokeCall(varIndex, lambdaInfo)); 371 } 372 else if (isAnonymousConstructorCall(owner, name)) { 373 Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>(); 374 int paramStart = frame.getStackSize() - paramLength; 375 376 for (int i = 0; i < paramLength; i++) { 377 SourceValue sourceValue = frame.getStack(paramStart + i); 378 if (sourceValue.insns.size() == 1) { 379 AbstractInsnNode insnNode = sourceValue.insns.iterator().next(); 380 LambdaInfo lambdaInfo = getLambdaIfExists(insnNode); 381 if (lambdaInfo != null) { 382 lambdaMapping.put(i, lambdaInfo); 383 node.instructions.remove(insnNode); 384 } 385 } 386 } 387 388 constructorInvocations.add(new ConstructorInvocation(owner, desc, lambdaMapping, isSameModule, inliningContext.classRegeneration)); 389 } 390 } 391 } 392 393 AbstractInsnNode prevNode = cur; 394 cur = cur.getNext(); 395 index++; 396 397 //given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached (dead code). 398 if (frame == null) { 399 //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems 400 if (prevNode.getType() == AbstractInsnNode.LABEL) { 401 deadLabels.add((LabelNode) prevNode); 402 } else { 403 node.instructions.remove(prevNode); 404 } 405 } 406 } 407 408 //clean dead try/catch blocks 409 List<TryCatchBlockNode> blocks = node.tryCatchBlocks; 410 for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) { 411 TryCatchBlockNode block = iterator.next(); 412 if (deadLabels.contains(block.start) && deadLabels.contains(block.end)) { 413 iterator.remove(); 414 } 415 } 416 417 return node; 418 } 419 420 public LambdaInfo getLambdaIfExists(AbstractInsnNode insnNode) { 421 if (insnNode.getOpcode() == Opcodes.ALOAD) { 422 int varIndex = ((VarInsnNode) insnNode).var; 423 if (varIndex < parameters.totalSize()) { 424 return parameters.get(varIndex).getLambda(); 425 } 426 } 427 else if (insnNode instanceof FieldInsnNode) { 428 FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode; 429 if (fieldInsnNode.name.startsWith("$$$")) { 430 return findCapturedField(fieldInsnNode, nodeRemapper).getLambda(); 431 } 432 } 433 434 return null; 435 } 436 437 private static void removeClosureAssertions(MethodNode node) { 438 AbstractInsnNode cur = node.instructions.getFirst(); 439 while (cur != null && cur.getNext() != null) { 440 AbstractInsnNode next = cur.getNext(); 441 if (next.getType() == AbstractInsnNode.METHOD_INSN) { 442 MethodInsnNode methodInsnNode = (MethodInsnNode) next; 443 if (methodInsnNode.name.equals("checkParameterIsNotNull") && methodInsnNode.owner.equals("kotlin/jvm/internal/Intrinsics")) { 444 AbstractInsnNode prev = cur.getPrevious(); 445 446 assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur; 447 assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev; 448 449 node.instructions.remove(prev); 450 node.instructions.remove(cur); 451 cur = next.getNext(); 452 node.instructions.remove(next); 453 next = cur; 454 } 455 } 456 cur = next; 457 } 458 } 459 460 private void transformCaptured(@NotNull MethodNode node) { 461 if (nodeRemapper.isRoot()) { 462 return; 463 } 464 465 //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured 466 //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0) 467 AbstractInsnNode cur = node.instructions.getFirst(); 468 while (cur != null) { 469 if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) { 470 if (((VarInsnNode) cur).var == 0) { 471 List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur); 472 AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node); 473 if (insnNode != null) { 474 cur = insnNode; 475 } 476 } 477 } 478 cur = cur.getNext(); 479 } 480 } 481 482 @NotNull 483 public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) { 484 List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>(); 485 fieldAccessChain.add(aload0); 486 AbstractInsnNode next = aload0.getNext(); 487 while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) { 488 if (next instanceof LabelNode) { 489 next = next.getNext(); 490 continue; //it will be delete on transformation 491 } 492 fieldAccessChain.add(next); 493 if ("this$0".equals(((FieldInsnNode) next).name)) { 494 next = next.getNext(); 495 } 496 else { 497 break; 498 } 499 } 500 501 return fieldAccessChain; 502 } 503 504 public static void putStackValuesIntoLocals(List<Type> directOrder, int shift, InstructionAdapter iv, String descriptor) { 505 Type[] actualParams = Type.getArgumentTypes(descriptor); 506 assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!"; 507 508 int size = 0; 509 for (Type next : directOrder) { 510 size += next.getSize(); 511 } 512 513 shift += size; 514 int index = directOrder.size(); 515 516 for (Type next : Lists.reverse(directOrder)) { 517 shift -= next.getSize(); 518 Type typeOnStack = actualParams[--index]; 519 if (!typeOnStack.equals(next)) { 520 StackValue.onStack(typeOnStack).put(next, iv); 521 } 522 iv.store(shift, next); 523 } 524 } 525 526 //TODO: check annotation on class - it's package part 527 //TODO: check it's external module 528 //TODO?: assert method exists in facade? 529 public String changeOwnerForExternalPackage(String type, int opcode) { 530 if (isSameModule || (opcode & Opcodes.INVOKESTATIC) == 0) { 531 return type; 532 } 533 534 int i = type.indexOf('-'); 535 if (i >= 0) { 536 return type.substring(0, i); 537 } 538 return type; 539 } 540 541 @NotNull 542 public RuntimeException wrapException(@NotNull Exception originalException, @NotNull MethodNode node, @NotNull String errorSuffix) { 543 if (originalException instanceof InlineException) { 544 return new InlineException(errorPrefix + ": " + errorSuffix, originalException); 545 } else { 546 return new InlineException(errorPrefix + ": " + errorSuffix + "\ncause: " + 547 InlineCodegen.getNodeText(node), originalException); 548 } 549 } 550 551 @NotNull 552 public static List<FinallyBlockInfo> processReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, Label endLabel) { 553 if (!remapReturn) { 554 return Collections.emptyList(); 555 } 556 List<FinallyBlockInfo> result = new ArrayList<FinallyBlockInfo>(); 557 InsnList instructions = node.instructions; 558 AbstractInsnNode insnNode = instructions.getFirst(); 559 while (insnNode != null) { 560 if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) { 561 AbstractInsnNode previous = insnNode.getPrevious(); 562 MethodInsnNode flagNode; 563 boolean isLocalReturn = true; 564 String labelName = null; 565 if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) { 566 flagNode = (MethodInsnNode) previous; 567 labelName = flagNode.name; 568 } 569 570 if (labelName != null) { 571 isLocalReturn = labelOwner.isMyLabel(labelName); 572 //remove global return flag 573 if (isLocalReturn) { 574 instructions.remove(previous); 575 } 576 } 577 578 if (isLocalReturn && endLabel != null) { 579 LabelNode labelNode = (LabelNode) endLabel.info; 580 JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode); 581 instructions.insert(insnNode, jumpInsnNode); 582 instructions.remove(insnNode); 583 insnNode = jumpInsnNode; 584 } 585 586 //genetate finally block before nonLocalReturn flag/return/goto 587 result.add(new FinallyBlockInfo(isLocalReturn ? insnNode : insnNode.getPrevious(), getReturnType(insnNode.getOpcode()))); 588 } 589 insnNode = insnNode.getNext(); 590 } 591 return result; 592 } 593 594 public static class FinallyBlockInfo { 595 596 final AbstractInsnNode beforeIns; 597 598 final Type returnType; 599 600 public FinallyBlockInfo(AbstractInsnNode beforeIns, Type returnType) { 601 this.beforeIns = beforeIns; 602 this.returnType = returnType; 603 } 604 605 } 606 }