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