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.AsmUtil;
031    import org.jetbrains.jet.codegen.ClassBuilder;
032    import org.jetbrains.jet.codegen.ClosureCodegen;
033    import org.jetbrains.jet.codegen.FieldInfo;
034    import org.jetbrains.jet.codegen.state.GenerationState;
035    import org.jetbrains.jet.codegen.state.JetTypeMapper;
036    
037    import java.io.IOException;
038    import java.util.ArrayList;
039    import java.util.HashMap;
040    import java.util.List;
041    import java.util.Map;
042    
043    import static org.jetbrains.asm4.Opcodes.ASM4;
044    import static org.jetbrains.asm4.Opcodes.V1_6;
045    
046    public class LambdaTransformer {
047    
048        protected final GenerationState state;
049    
050        protected final JetTypeMapper typeMapper;
051    
052        private final MethodNode constructor;
053    
054        private final MethodNode invoke;
055    
056        private final MethodNode bridge;
057    
058        private final InliningContext inliningContext;
059    
060        private final Type oldLambdaType;
061    
062        private final Type newLambdaType;
063    
064        private int classAccess;
065        private String signature;
066        private String superName;
067        private String[] interfaces;
068        private final boolean isSameModule;
069    
070        public LambdaTransformer(String lambdaInternalName, InliningContext inliningContext, boolean isSameModule, Type newLambdaType) {
071            this.isSameModule = isSameModule;
072            this.state = inliningContext.state;
073            this.typeMapper = state.getTypeMapper();
074            this.inliningContext = inliningContext;
075            this.oldLambdaType = Type.getObjectType(lambdaInternalName);
076            this.newLambdaType = newLambdaType;
077    
078            //try to find just compiled classes then in dependencies
079            ClassReader reader;
080            try {
081                OutputFile outputFile = state.getFactory().get(lambdaInternalName + ".class");
082                if (outputFile != null) {
083                    reader = new ClassReader(outputFile.asByteArray());
084                } else {
085                    VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), lambdaInternalName);
086                    if (file == null) {
087                        throw new RuntimeException("Couldn't find virtual file for " + lambdaInternalName);
088                    }
089                    reader = new ClassReader(file.getInputStream());
090                }
091            }
092            catch (IOException e) {
093                throw new RuntimeException(e);
094            }
095    
096            //TODO rewrite to one step
097            constructor = getMethodNode(reader, true, false);
098            invoke = getMethodNode(reader, false, false);
099            bridge = getMethodNode(reader, false, true);
100        }
101    
102        private void buildInvokeParams(ParametersBuilder builder) {
103            builder.addThis(oldLambdaType, false);
104    
105            Type[] types = Type.getArgumentTypes(invoke.desc);
106            for (Type type : types) {
107                builder.addNextParameter(type, false, null);
108            }
109        }
110    
111        public InlineResult doTransform(ConstructorInvocation invocation, LambdaFieldRemapper parentRemapper) {
112            ClassBuilder classBuilder = createClassBuilder();
113    
114            //TODO: public visibility for inline function
115            classBuilder.defineClass(null,
116                                     V1_6,
117                                     classAccess,
118                                     newLambdaType.getInternalName(),
119                                     signature,
120                                     superName,
121                                     interfaces
122            );
123    
124            // TODO: load synthetic class kind from the transformed class and write the same kind to the copy of that class here
125            // See AsmUtil.writeKotlinSyntheticClassAnnotation
126    
127            ParametersBuilder builder = ParametersBuilder.newBuilder();
128            Parameters parameters = getLambdaParameters(builder, invocation);
129    
130            MethodVisitor invokeVisitor = newMethod(classBuilder, invoke);
131            RegeneratedLambdaFieldRemapper
132                    remapper = new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(), parameters, invocation.getCapturedLambdasToInline(),
133                                                                  parentRemapper);
134            MethodInliner inliner = new MethodInliner(invoke, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), oldLambdaType,
135                                                      remapper, isSameModule);
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 static void extractParametersMapping(MethodNode constructor, ParametersBuilder builder, 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            while (cur != null) {
208                if (cur.getType() == AbstractInsnNode.FIELD_INSN) {
209                    FieldInsnNode fieldNode = (FieldInsnNode) cur;
210                    CapturedParamInfo info = builder.addCapturedParam(fieldNode.name, Type.getType(fieldNode.desc), false, null);
211    
212                    assert fieldNode.getPrevious() instanceof VarInsnNode : "Previous instruction should be VarInsnNode but was " + fieldNode.getPrevious();
213                    VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
214                    int varIndex = previous.var;
215                    LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
216                    if (lambdaInfo != null) {
217                        info.setLambda(lambdaInfo);
218                        capturedLambdas.add(lambdaInfo);
219                    }
220                }
221                cur = cur.getNext();
222            }
223    
224            //For all inlined lambdas add their captured parameters
225            //TODO: some of such parameters could be skipped - we should perform additional analysis
226            Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter
227            List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>();
228            for (LambdaInfo info : capturedLambdas) {
229                for (CapturedParamInfo var : info.getCapturedVars()) {
230                    CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(getNewFieldName(var.getFieldName()), var.getType(), true, var);
231                    recapturedParamInfo.setRecapturedFrom(info);
232                    allRecapturedParameters.add(var);
233                }
234                capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
235            }
236    
237            invocation.setAllRecapturedParameters(allRecapturedParameters);
238            invocation.setCapturedLambdasToInline(capturedLambdasToInline);
239        }
240    
241        @Nullable
242        public MethodNode getMethodNode(@NotNull ClassReader reader, final boolean findConstructor, final boolean findBridge) {
243            final MethodNode[] methodNode = new MethodNode[1];
244            reader.accept(new ClassVisitor(InlineCodegenUtil.API) {
245    
246                @Override
247                public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
248                    super.visit(version, access, name, signature, superName, interfaces);
249                    LambdaTransformer.this.classAccess = access;
250                    LambdaTransformer.this.signature = signature;
251                    LambdaTransformer.this.superName = superName;
252                    LambdaTransformer.this.interfaces = interfaces;
253                }
254    
255                @Override
256                public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
257                    boolean isConstructorMethod = "<init>".equals(name);
258                    boolean isBridge = (access & Opcodes.ACC_BRIDGE) != 0;
259                    if (findConstructor && isConstructorMethod || (!findConstructor && !isConstructorMethod && (isBridge == findBridge))) {
260                        assert methodNode[0] == null : "Wrong lambda/sam structure: " + methodNode[0].name + " conflicts with " + name;
261                        return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions);
262                    }
263                    return null;
264                }
265            }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
266    
267            if (methodNode[0] == null && !findBridge) {
268                throw new RuntimeException("Couldn't find operation method of lambda/sam class " + oldLambdaType.getInternalName() + ": findConstructor = " + findConstructor);
269            }
270    
271            return methodNode[0];
272        }
273    
274        public static String getNewFieldName(String oldName) {
275            if (oldName.equals("this$0")) {
276                //"this$0" couldn't clash and we should keep this name invariant for further transformations
277                return oldName;
278            }
279            return oldName + "$inlined";
280        }
281    }