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 }