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 info;
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 info, boolean isSameModule, Type newLambdaType) {
071 this.isSameModule = isSameModule;
072 this.state = info.state;
073 this.typeMapper = state.getTypeMapper();
074 this.info = info;
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 void doTransform(ConstructorInvocation invocation) {
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 ParametersBuilder builder = ParametersBuilder.newBuilder();
124 Parameters parameters = getLambdaParameters(builder, invocation);
125
126 MethodVisitor invokeVisitor = newMethod(classBuilder, invoke);
127 RegeneratedLambdaFieldRemapper
128 remapper = new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(), parameters, invocation.getCapturedLambdasToInline());
129 MethodInliner inliner = new MethodInliner(invoke, parameters, info.subInline(info.nameGenerator.subGenerator("lambda")), oldLambdaType,
130 remapper, isSameModule);
131 inliner.doInline(invokeVisitor, new VarRemapper.ParamRemapper(parameters, 0), remapper, false);
132 invokeVisitor.visitMaxs(-1, -1);
133
134 generateConstructorAndFields(classBuilder, builder, invocation);
135
136 if (bridge != null) {
137 MethodVisitor invokeBridge = newMethod(classBuilder, bridge);
138 bridge.accept(new MethodVisitor(ASM4, invokeBridge) {
139 @Override
140 public void visitMethodInsn(int opcode, String owner, String name, String desc) {
141 if (owner.equals(oldLambdaType.getInternalName())) {
142 super.visitMethodInsn(opcode, newLambdaType.getInternalName(), name, desc);
143 } else {
144 super.visitMethodInsn(opcode, owner, name, desc);
145 }
146 }
147 });
148 }
149
150 classBuilder.done();
151
152 invocation.setNewLambdaType(newLambdaType);
153 }
154
155 private void generateConstructorAndFields(@NotNull ClassBuilder classBuilder, @NotNull ParametersBuilder builder, @NotNull ConstructorInvocation invocation) {
156 List<CapturedParamInfo> infos = builder.buildCaptured();
157 List<Pair<String, Type>> newConstructorSignature = new ArrayList<Pair<String, Type>>();
158 for (CapturedParamInfo capturedParamInfo : infos) {
159 if (capturedParamInfo.getLambda() == null) { //not inlined
160 newConstructorSignature.add(new Pair<String, Type>(capturedParamInfo.getFieldName(), capturedParamInfo.getType()));
161 }
162 }
163
164 List<FieldInfo> fields = AsmUtil.transformCapturedParams(newConstructorSignature, newLambdaType);
165
166 AsmUtil.genClosureFields(newConstructorSignature, classBuilder);
167
168 //TODO for inline method make public class
169 Method newConstructor = ClosureCodegen.generateConstructor(classBuilder, fields, null, Type.getObjectType(superName), state, AsmUtil.NO_FLAG_PACKAGE_PRIVATE);
170 invocation.setNewConstructorDescriptor(newConstructor.getDescriptor());
171 }
172
173 private Parameters getLambdaParameters(ParametersBuilder builder, ConstructorInvocation invocation) {
174 buildInvokeParams(builder);
175 extractParametersMapping(constructor, builder, invocation);
176 return builder.buildParameters();
177 }
178
179 private ClassBuilder createClassBuilder() {
180 return new RemappingClassBuilder(state.getFactory().forLambdaInlining(newLambdaType, info.call.getCallElement().getContainingFile()),
181 new TypeRemapper(info.typeMapping));
182 }
183
184 private static MethodVisitor newMethod(ClassBuilder builder, MethodNode original) {
185 return builder.newMethod(
186 null,
187 original.access,
188 original.name,
189 original.desc,
190 original.signature,
191 null //TODO: change signature to list
192 );
193 }
194
195 private static void extractParametersMapping(MethodNode constructor, ParametersBuilder builder, ConstructorInvocation invocation) {
196 Map<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline();
197
198 AbstractInsnNode cur = constructor.instructions.getFirst();
199 cur = cur.getNext(); //skip super call
200 List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter
201 while (cur != null) {
202 if (cur.getType() == AbstractInsnNode.FIELD_INSN) {
203 FieldInsnNode fieldNode = (FieldInsnNode) cur;
204 CapturedParamInfo info = builder.addCapturedParam(fieldNode.name, Type.getType(fieldNode.desc), false, null);
205
206 assert fieldNode.getPrevious() instanceof VarInsnNode : "Previous instruction should be VarInsnNode but was " + fieldNode.getPrevious();
207 VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
208 int varIndex = previous.var;
209 LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
210 if (lambdaInfo != null) {
211 info.setLambda(lambdaInfo);
212 capturedLambdas.add(lambdaInfo);
213 }
214 }
215 cur = cur.getNext();
216 }
217
218 //For all inlined lambdas add their captured parameters
219 //TODO: some of such parameters could be skipped - we should perform additional analysis
220 Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter
221 List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>();
222 for (LambdaInfo info : capturedLambdas) {
223 for (CapturedParamInfo var : info.getCapturedVars()) {
224 CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(getNewFieldName(var.getFieldName()), var.getType(), true, var);
225 recapturedParamInfo.setRecapturedFrom(info);
226 allRecapturedParameters.add(var);
227 }
228 capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
229 }
230
231 invocation.setAllRecapturedParameters(allRecapturedParameters);
232 invocation.setCapturedLambdasToInline(capturedLambdasToInline);
233 }
234
235 @Nullable
236 public MethodNode getMethodNode(@NotNull ClassReader reader, final boolean findConstructor, final boolean findBridge) {
237 final MethodNode[] methodNode = new MethodNode[1];
238 reader.accept(new ClassVisitor(InlineCodegenUtil.API) {
239
240 @Override
241 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
242 super.visit(version, access, name, signature, superName, interfaces);
243 LambdaTransformer.this.classAccess = access;
244 LambdaTransformer.this.signature = signature;
245 LambdaTransformer.this.superName = superName;
246 LambdaTransformer.this.interfaces = interfaces;
247 }
248
249 @Override
250 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
251 boolean isConstructorMethod = "<init>".equals(name);
252 boolean isBridge = (access & Opcodes.ACC_BRIDGE) != 0;
253 if (findConstructor && isConstructorMethod || (!findConstructor && !isConstructorMethod && (isBridge == findBridge))) {
254 assert methodNode[0] == null : "Wrong lambda/sam structure: " + methodNode[0].name + " conflicts with " + name;
255 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions);
256 }
257 return null;
258 }
259 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
260
261 if (methodNode[0] == null && !findBridge) {
262 throw new RuntimeException("Couldn't find operation method of lambda/sam class " + oldLambdaType.getInternalName() + ": findConstructor = " + findConstructor);
263 }
264
265 return methodNode[0];
266 }
267
268 public static String getNewFieldName(String oldName) {
269 return oldName + "$inlined";
270 }
271 }