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, LocalVarRemapper remapper) {
076 return doInline(adapter, remapper, true);
077 }
078
079 public InlineResult doInline(
080 MethodVisitor adapter,
081 LocalVarRemapper remapper,
082 boolean remapReturn
083 ) {
084 //analyze body
085 MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node);
086
087 transformedNode = doInline(transformedNode);
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) {
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, nodeRemapper);
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();
163
164 InlinedLambdaRemapper newCapturedRemapper =
165 new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), nodeRemapper, lambdaParameters);
166
167 setInlining(true);
168 MethodInliner inliner = new MethodInliner(info.getNode(), lambdaParameters,
169 inliningContext.subInlineLambda(info),
170 newCapturedRemapper, true /*cause all calls in same module as lambda*/,
171 "Lambda inlining " + info.getLambdaClassType().getInternalName());
172
173 LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift);
174 InlineResult lambdaResult = inliner.doInline(this.mv, remapper);//TODO add skipped this and receiver
175 result.addAllClassesToRemove(lambdaResult);
176
177 //return value boxing/unboxing
178 Method bridge = typeMapper.mapSignature(ClosureCodegen.getInvokeFunction(info.getFunctionDescriptor())).getAsmMethod();
179 Method delegate = typeMapper.mapSignature(info.getFunctionDescriptor()).getAsmMethod();
180 StackValue.onStack(delegate.getReturnType()).put(bridge.getReturnType(), this);
181 setInlining(false);
182 }
183 else if (isLambdaConstructorCall(owner, name)) { //TODO add method
184 assert invocation != null : "<init> call not corresponds to new call" + owner + " " + name;
185 if (invocation.shouldRegenerate()) {
186 //put additional captured parameters on stack
187 for (CapturedParamInfo capturedParamInfo : invocation.getAllRecapturedParameters()) {
188 visitFieldInsn(Opcodes.GETSTATIC, capturedParamInfo.getContainingLambdaName(), "$$$" + capturedParamInfo.getOriginalFieldName(), capturedParamInfo.getType().getDescriptor());
189 }
190 super.visitMethodInsn(opcode, invocation.getNewLambdaType().getInternalName(), name, invocation.getNewConstructorDescriptor());
191 invocation = null;
192 } else {
193 super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc);
194 }
195 }
196 else {
197 super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc);
198 }
199 }
200
201 };
202
203 node.accept(inliner);
204
205 return resultNode;
206 }
207
208 @NotNull
209 public static CapturedParamInfo findCapturedField(FieldInsnNode node, FieldRemapper fieldRemapper) {
210 assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix";
211 FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
212 CapturedParamInfo field = fieldRemapper.findField(fin);
213 if (field == null) {
214 throw new IllegalStateException("Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName());
215 }
216 return field;
217 }
218
219 @NotNull
220 public MethodNode prepareNode(@NotNull MethodNode node) {
221 final int capturedParamsSize = parameters.getCaptured().size();
222 final int realParametersSize = parameters.getReal().size();
223 Type[] types = Type.getArgumentTypes(node.desc);
224 Type returnType = Type.getReturnType(node.desc);
225
226 ArrayList<Type> capturedTypes = parameters.getCapturedTypes();
227 Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()]));
228
229 node.instructions.resetLabels();
230 MethodNode transformedNode = new MethodNode(node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null) {
231
232 private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda();
233
234 private int getNewIndex(int var) {
235 return var + (var < realParametersSize ? 0 : capturedParamsSize);
236 }
237
238 @Override
239 public void visitVarInsn(int opcode, int var) {
240 super.visitVarInsn(opcode, getNewIndex(var));
241 }
242
243 @Override
244 public void visitIincInsn(int var, int increment) {
245 super.visitIincInsn(getNewIndex(var), increment);
246 }
247
248 @Override
249 public void visitMaxs(int maxStack, int maxLocals) {
250 super.visitMaxs(maxStack, maxLocals + capturedParamsSize);
251 }
252
253 @Override
254 public void visitLineNumber(int line, Label start) {
255 if(isInliningLambda) {
256 super.visitLineNumber(line, start);
257 }
258 }
259
260 @Override
261 public void visitLocalVariable(
262 String name, String desc, String signature, Label start, Label end, int index
263 ) {
264 if (isInliningLambda) {
265 super.visitLocalVariable(name, desc, signature, start, end, getNewIndex(index));
266 }
267 }
268 };
269
270 node.accept(transformedNode);
271
272 transformCaptured(transformedNode);
273
274 return transformedNode;
275 }
276
277 @NotNull
278 protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) {
279 node = prepareNode(node);
280
281 Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter());
282 Frame<SourceValue>[] sources;
283 try {
284 sources = analyzer.analyze("fake", node);
285 }
286 catch (AnalyzerException e) {
287 throw wrapException(e, node, "couldn't inline method call");
288 }
289
290 AbstractInsnNode cur = node.instructions.getFirst();
291 int index = 0;
292 Set<LabelNode> deadLabels = new HashSet<LabelNode>();
293
294 while (cur != null) {
295 Frame<SourceValue> frame = sources[index];
296
297 if (frame != null) {
298 if (cur.getType() == AbstractInsnNode.METHOD_INSN) {
299 MethodInsnNode methodInsnNode = (MethodInsnNode) cur;
300 String owner = methodInsnNode.owner;
301 String desc = methodInsnNode.desc;
302 String name = methodInsnNode.name;
303 //TODO check closure
304 int paramLength = Type.getArgumentTypes(desc).length + 1;//non static
305 if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
306 SourceValue sourceValue = frame.getStack(frame.getStackSize() - paramLength);
307
308 LambdaInfo lambdaInfo = null;
309 int varIndex = -1;
310
311 if (sourceValue.insns.size() == 1) {
312 AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
313
314 lambdaInfo = getLambdaIfExists(insnNode);
315 if (lambdaInfo != null) {
316 //remove inlinable access
317 node.instructions.remove(insnNode);
318 }
319 }
320
321 invokeCalls.add(new InvokeCall(varIndex, lambdaInfo));
322 }
323 else if (isLambdaConstructorCall(owner, name)) {
324 Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
325 int paramStart = frame.getStackSize() - paramLength;
326
327 for (int i = 0; i < paramLength; i++) {
328 SourceValue sourceValue = frame.getStack(paramStart + i);
329 if (sourceValue.insns.size() == 1) {
330 AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
331 LambdaInfo lambdaInfo = getLambdaIfExists(insnNode);
332 if (lambdaInfo != null) {
333 lambdaMapping.put(i, lambdaInfo);
334 node.instructions.remove(insnNode);
335 }
336 }
337 }
338
339 constructorInvocations.add(new ConstructorInvocation(owner, lambdaMapping, isSameModule, inliningContext.classRegeneration));
340 }
341 }
342 }
343
344 AbstractInsnNode prevNode = cur;
345 cur = cur.getNext();
346 index++;
347
348 //given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached (dead code).
349 if (frame == null) {
350 //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
351 if (prevNode.getType() == AbstractInsnNode.LABEL) {
352 deadLabels.add((LabelNode) prevNode);
353 } else {
354 node.instructions.remove(prevNode);
355 }
356 }
357 }
358
359 //clean dead try/catch blocks
360 List<TryCatchBlockNode> blocks = node.tryCatchBlocks;
361 for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) {
362 TryCatchBlockNode block = iterator.next();
363 if (deadLabels.contains(block.start) && deadLabels.contains(block.end)) {
364 iterator.remove();
365 }
366 }
367
368 return node;
369 }
370
371 public LambdaInfo getLambdaIfExists(AbstractInsnNode insnNode) {
372 if (insnNode.getOpcode() == Opcodes.ALOAD) {
373 int varIndex = ((VarInsnNode) insnNode).var;
374 if (varIndex < parameters.totalSize()) {
375 return parameters.get(varIndex).getLambda();
376 }
377 }
378 else if (insnNode instanceof FieldInsnNode) {
379 FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode;
380 if (fieldInsnNode.name.startsWith("$$$")) {
381 return findCapturedField(fieldInsnNode, nodeRemapper).getLambda();
382 }
383 }
384
385 return null;
386 }
387
388 private static void removeClosureAssertions(MethodNode node) {
389 AbstractInsnNode cur = node.instructions.getFirst();
390 while (cur != null && cur.getNext() != null) {
391 AbstractInsnNode next = cur.getNext();
392 if (next.getType() == AbstractInsnNode.METHOD_INSN) {
393 MethodInsnNode methodInsnNode = (MethodInsnNode) next;
394 if (methodInsnNode.name.equals("checkParameterIsNotNull") && methodInsnNode.owner.equals("kotlin/jvm/internal/Intrinsics")) {
395 AbstractInsnNode prev = cur.getPrevious();
396
397 assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur;
398 assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev;
399
400 node.instructions.remove(prev);
401 node.instructions.remove(cur);
402 cur = next.getNext();
403 node.instructions.remove(next);
404 next = cur;
405 }
406 }
407 cur = next;
408 }
409 }
410
411 private void transformCaptured(@NotNull MethodNode node) {
412 if (nodeRemapper.isRoot()) {
413 return;
414 }
415
416 //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured
417 //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0)
418 AbstractInsnNode cur = node.instructions.getFirst();
419 while (cur != null) {
420 if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) {
421 if (((VarInsnNode) cur).var == 0) {
422 List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur);
423 AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node);
424 if (insnNode != null) {
425 cur = insnNode;
426 }
427 }
428 }
429 cur = cur.getNext();
430 }
431 }
432
433 @NotNull
434 public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) {
435 List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
436 fieldAccessChain.add(aload0);
437 AbstractInsnNode next = aload0.getNext();
438 while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) {
439 if (next instanceof LabelNode) {
440 next = next.getNext();
441 continue; //it will be delete on transformation
442 }
443 fieldAccessChain.add(next);
444 if ("this$0".equals(((FieldInsnNode) next).name)) {
445 next = next.getNext();
446 }
447 else {
448 break;
449 }
450 }
451
452 return fieldAccessChain;
453 }
454
455 public static void putStackValuesIntoLocals(List<Type> directOrder, int shift, InstructionAdapter iv, String descriptor) {
456 Type[] actualParams = Type.getArgumentTypes(descriptor);
457 assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!";
458
459 int size = 0;
460 for (Type next : directOrder) {
461 size += next.getSize();
462 }
463
464 shift += size;
465 int index = directOrder.size();
466
467 for (Type next : Lists.reverse(directOrder)) {
468 shift -= next.getSize();
469 Type typeOnStack = actualParams[--index];
470 if (!typeOnStack.equals(next)) {
471 StackValue.onStack(typeOnStack).put(next, iv);
472 }
473 iv.store(shift, next);
474 }
475 }
476
477 //TODO: check annotation on class - it's package part
478 //TODO: check it's external module
479 //TODO?: assert method exists in facade?
480 public String changeOwnerForExternalPackage(String type, int opcode) {
481 if (isSameModule || (opcode & Opcodes.INVOKESTATIC) == 0) {
482 return type;
483 }
484
485 int i = type.indexOf('-');
486 if (i >= 0) {
487 return type.substring(0, i);
488 }
489 return type;
490 }
491
492
493 public RuntimeException wrapException(@NotNull Exception originalException, @NotNull MethodNode node, @NotNull String errorSuffix) {
494 if (originalException instanceof InlineException) {
495 return new InlineException(errorPrefix + ": " + errorSuffix, originalException);
496 } else {
497 return new InlineException(errorPrefix + ": " + errorSuffix + "\ncause: " +
498 InlineCodegen.getNodeText(node), originalException);
499 }
500 }
501 }