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