001    /*
002     * Copyright 2010-2013 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.intellij.openapi.util.Pair;
020    import com.intellij.openapi.vfs.VirtualFile;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.asm4.*;
024    import org.jetbrains.asm4.commons.Method;
025    import org.jetbrains.asm4.tree.AbstractInsnNode;
026    import org.jetbrains.asm4.tree.FieldInsnNode;
027    import org.jetbrains.asm4.tree.MethodNode;
028    import org.jetbrains.asm4.tree.VarInsnNode;
029    import org.jetbrains.jet.OutputFile;
030    import org.jetbrains.jet.codegen.*;
031    import org.jetbrains.jet.codegen.state.GenerationState;
032    import org.jetbrains.jet.codegen.state.JetTypeMapper;
033    
034    import java.io.IOException;
035    import java.util.ArrayList;
036    import java.util.HashMap;
037    import java.util.List;
038    import java.util.Map;
039    
040    import static org.jetbrains.asm4.Opcodes.ASM4;
041    import static org.jetbrains.asm4.Opcodes.V1_6;
042    
043    public class LambdaTransformer {
044    
045        protected final GenerationState state;
046    
047        protected final JetTypeMapper typeMapper;
048    
049        private final MethodNode constructor;
050    
051        private final MethodNode invoke;
052    
053        private final MethodNode bridge;
054    
055        private final InliningContext inliningContext;
056    
057        private final Type oldLambdaType;
058    
059        private final Type newLambdaType;
060    
061        private int classAccess;
062        private String signature;
063        private String superName;
064        private String[] interfaces;
065        private final boolean isSameModule;
066    
067        public LambdaTransformer(String lambdaInternalName, InliningContext inliningContext, boolean isSameModule, Type newLambdaType) {
068            this.isSameModule = isSameModule;
069            this.state = inliningContext.state;
070            this.typeMapper = state.getTypeMapper();
071            this.inliningContext = inliningContext;
072            this.oldLambdaType = Type.getObjectType(lambdaInternalName);
073            this.newLambdaType = newLambdaType;
074    
075            //try to find just compiled classes then in dependencies
076            ClassReader reader;
077            try {
078                OutputFile outputFile = state.getFactory().get(lambdaInternalName + ".class");
079                if (outputFile != null) {
080                    reader = new ClassReader(outputFile.asByteArray());
081                } else {
082                    VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), lambdaInternalName);
083                    if (file == null) {
084                        throw new RuntimeException("Couldn't find virtual file for " + lambdaInternalName);
085                    }
086                    reader = new ClassReader(file.getInputStream());
087                }
088            }
089            catch (IOException e) {
090                throw new RuntimeException(e);
091            }
092    
093            //TODO rewrite to one step
094            constructor = getMethodNode(reader, true, false);
095            invoke = getMethodNode(reader, false, false);
096            bridge = getMethodNode(reader, false, true);
097        }
098    
099        private void buildInvokeParams(ParametersBuilder builder) {
100            builder.addThis(oldLambdaType, false);
101    
102            Type[] types = Type.getArgumentTypes(invoke.desc);
103            for (Type type : types) {
104                builder.addNextParameter(type, false, null);
105            }
106        }
107    
108        public InlineResult doTransform(ConstructorInvocation invocation, FieldRemapper parentRemapper) {
109            ClassBuilder classBuilder = createClassBuilder();
110    
111            //TODO: public visibility for inline function
112            classBuilder.defineClass(null,
113                                     V1_6,
114                                     classAccess,
115                                     newLambdaType.getInternalName(),
116                                     signature,
117                                     superName,
118                                     interfaces
119            );
120    
121            // TODO: load synthetic class kind from the transformed class and write the same kind to the copy of that class here
122            // See AsmUtil.writeKotlinSyntheticClassAnnotation
123    
124            ParametersBuilder builder = ParametersBuilder.newBuilder();
125            Parameters parameters = getLambdaParameters(builder, invocation);
126    
127            MethodVisitor invokeVisitor = newMethod(classBuilder, invoke);
128    
129            RegeneratedLambdaFieldRemapper remapper =
130                    new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(),
131                                                       parameters, invocation.getCapturedLambdasToInline(),
132                                                       parentRemapper);
133    
134            MethodInliner inliner = new MethodInliner(invoke, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
135                                                      remapper, isSameModule, "Transformer for " + invocation.getOwnerInternalName());
136            InlineResult result = inliner.doInline(invokeVisitor, new VarRemapper.ParamRemapper(parameters, 0), remapper, false);
137            invokeVisitor.visitMaxs(-1, -1);
138    
139            generateConstructorAndFields(classBuilder, builder, invocation);
140    
141            if (bridge != null) {
142                MethodVisitor invokeBridge = newMethod(classBuilder, bridge);
143                bridge.accept(new MethodVisitor(ASM4, invokeBridge) {
144                    @Override
145                    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
146                        if (owner.equals(oldLambdaType.getInternalName())) {
147                            super.visitMethodInsn(opcode, newLambdaType.getInternalName(), name, desc);
148                        } else {
149                            super.visitMethodInsn(opcode, owner, name, desc);
150                        }
151                    }
152                });
153            }
154    
155            classBuilder.done();
156    
157            invocation.setNewLambdaType(newLambdaType);
158            return result;
159        }
160    
161        private void generateConstructorAndFields(@NotNull ClassBuilder classBuilder, @NotNull ParametersBuilder builder, @NotNull ConstructorInvocation invocation) {
162            List<CapturedParamInfo> infos = builder.buildCaptured();
163            List<Pair<String, Type>> newConstructorSignature = new ArrayList<Pair<String, Type>>();
164            for (CapturedParamInfo capturedParamInfo : infos) {
165                if (capturedParamInfo.getLambda() == null) { //not inlined
166                    newConstructorSignature.add(new Pair<String, Type>(capturedParamInfo.getFieldName(), capturedParamInfo.getType()));
167                }
168            }
169    
170            List<FieldInfo> fields = AsmUtil.transformCapturedParams(newConstructorSignature, newLambdaType);
171    
172            AsmUtil.genClosureFields(newConstructorSignature, classBuilder);
173    
174            //TODO for inline method make public class
175            Method newConstructor = ClosureCodegen.generateConstructor(classBuilder, fields, null, Type.getObjectType(superName), state, AsmUtil.NO_FLAG_PACKAGE_PRIVATE);
176            invocation.setNewConstructorDescriptor(newConstructor.getDescriptor());
177        }
178    
179        private Parameters getLambdaParameters(ParametersBuilder builder, ConstructorInvocation invocation) {
180            buildInvokeParams(builder);
181            extractParametersMapping(constructor, builder, invocation);
182            return builder.buildParameters();
183        }
184    
185        private ClassBuilder createClassBuilder() {
186            return new RemappingClassBuilder(state.getFactory().forLambdaInlining(newLambdaType, inliningContext.call.getCallElement().getContainingFile()),
187                         new TypeRemapper(inliningContext.typeMapping));
188        }
189    
190        private static MethodVisitor newMethod(ClassBuilder builder, MethodNode original) {
191            return builder.newMethod(
192                    null,
193                    original.access,
194                    original.name,
195                    original.desc,
196                    original.signature,
197                    null //TODO: change signature to list
198            );
199        }
200    
201        private void extractParametersMapping(MethodNode constructor, ParametersBuilder builder, final ConstructorInvocation invocation) {
202            Map<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline();
203    
204            AbstractInsnNode cur = constructor.instructions.getFirst();
205            cur = cur.getNext(); //skip super call
206            List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter
207            CapturedParamOwner owner = new CapturedParamOwner() {
208                @Override
209                public Type getType() {
210                    return Type.getObjectType(invocation.getOwnerInternalName());
211                }
212            };
213    
214            while (cur != null) {
215                if (cur.getType() == AbstractInsnNode.FIELD_INSN) {
216                    FieldInsnNode fieldNode = (FieldInsnNode) cur;
217                    CapturedParamInfo info = builder.addCapturedParam(fieldNode.name, Type.getType(fieldNode.desc), false, null, owner);
218    
219                    assert fieldNode.getPrevious() instanceof VarInsnNode : "Previous instruction should be VarInsnNode but was " + fieldNode.getPrevious();
220                    VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
221                    int varIndex = previous.var;
222                    LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
223                    if (lambdaInfo != null) {
224                        info.setLambda(lambdaInfo);
225                        capturedLambdas.add(lambdaInfo);
226                    }
227                }
228                cur = cur.getNext();
229            }
230    
231            //For all inlined lambdas add their captured parameters
232            //TODO: some of such parameters could be skipped - we should perform additional analysis
233            Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter
234            List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>();
235            for (LambdaInfo info : capturedLambdas) {
236                for (CapturedParamInfo var : info.getCapturedVars()) {
237                    CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(getNewFieldName(var.getFieldName()), var.getType(), var.isSkipped, var, info);
238                    StackValue composed = StackValue.composed(StackValue.local(0, oldLambdaType),
239                                                              StackValue.field(var.getType(),
240                                                                               oldLambdaType, /*TODO owner type*/
241                                                                               getNewFieldName(var.getFieldName()), false)
242                    );
243                    recapturedParamInfo.setRemapValue(composed);
244                    allRecapturedParameters.add(var);
245                }
246                capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
247            }
248    
249            invocation.setAllRecapturedParameters(allRecapturedParameters);
250            invocation.setCapturedLambdasToInline(capturedLambdasToInline);
251        }
252    
253        @Nullable
254        public MethodNode getMethodNode(@NotNull ClassReader reader, final boolean findConstructor, final boolean findBridge) {
255            final MethodNode[] methodNode = new MethodNode[1];
256            reader.accept(new ClassVisitor(InlineCodegenUtil.API) {
257    
258                @Override
259                public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
260                    super.visit(version, access, name, signature, superName, interfaces);
261                    LambdaTransformer.this.classAccess = access;
262                    LambdaTransformer.this.signature = signature;
263                    LambdaTransformer.this.superName = superName;
264                    LambdaTransformer.this.interfaces = interfaces;
265                }
266    
267                @Override
268                public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
269                    boolean isConstructorMethod = "<init>".equals(name);
270                    boolean isBridge = (access & Opcodes.ACC_BRIDGE) != 0;
271                    if (findConstructor && isConstructorMethod || (!findConstructor && !isConstructorMethod && (isBridge == findBridge))) {
272                        assert methodNode[0] == null : "Wrong lambda/sam structure: " + methodNode[0].name + " conflicts with " + name;
273                        return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions);
274                    }
275                    return null;
276                }
277            }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
278    
279            if (methodNode[0] == null && !findBridge) {
280                throw new RuntimeException("Couldn't find operation method of lambda/sam class " + oldLambdaType.getInternalName() + ": findConstructor = " + findConstructor);
281            }
282    
283            return methodNode[0];
284        }
285    
286        public static String getNewFieldName(String oldName) {
287            if (oldName.equals("this$0")) {
288                //"this$0" couldn't clash and we should keep this name invariant for further transformations
289                return oldName;
290            }
291            return oldName + "$inlined";
292        }
293    }