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 }