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