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