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 }