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 com.intellij.util.ArrayUtil; 022 import org.jetbrains.annotations.NotNull; 023 import org.jetbrains.jet.OutputFile; 024 import org.jetbrains.jet.codegen.AsmUtil; 025 import org.jetbrains.jet.codegen.ClassBuilder; 026 import org.jetbrains.jet.codegen.FieldInfo; 027 import org.jetbrains.jet.codegen.StackValue; 028 import org.jetbrains.jet.codegen.state.GenerationState; 029 import org.jetbrains.jet.codegen.state.JetTypeMapper; 030 import org.jetbrains.org.objectweb.asm.*; 031 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 032 import org.jetbrains.org.objectweb.asm.tree.*; 033 034 import java.io.IOException; 035 import java.util.*; 036 037 public class AnonymousObjectTransformer { 038 039 protected final GenerationState state; 040 041 protected final JetTypeMapper typeMapper; 042 043 private MethodNode constructor; 044 045 private final InliningContext inliningContext; 046 047 private final Type oldObjectType; 048 049 private final Type newLambdaType; 050 051 private final ClassReader reader; 052 053 private final boolean isSameModule; 054 055 private final Map<String, List<String>> fieldNames = new HashMap<String, List<String>>(); 056 057 public AnonymousObjectTransformer( 058 @NotNull String objectInternalName, 059 @NotNull InliningContext inliningContext, 060 boolean isSameModule, 061 @NotNull Type newLambdaType 062 ) { 063 this.isSameModule = isSameModule; 064 this.state = inliningContext.state; 065 this.typeMapper = state.getTypeMapper(); 066 this.inliningContext = inliningContext; 067 this.oldObjectType = Type.getObjectType(objectInternalName); 068 this.newLambdaType = newLambdaType; 069 070 //try to find just compiled classes then in dependencies 071 try { 072 OutputFile outputFile = state.getFactory().get(objectInternalName + ".class"); 073 if (outputFile != null) { 074 reader = new ClassReader(outputFile.asByteArray()); 075 } else { 076 VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), objectInternalName); 077 if (file == null) { 078 throw new RuntimeException("Couldn't find virtual file for " + objectInternalName); 079 } 080 reader = new ClassReader(file.getInputStream()); 081 } 082 } 083 catch (IOException e) { 084 throw new RuntimeException(e); 085 } 086 } 087 088 private void buildInvokeParamsFor(@NotNull ParametersBuilder builder, @NotNull MethodNode node) { 089 builder.addThis(oldObjectType, false); 090 091 Type[] types = Type.getArgumentTypes(node.desc); 092 for (Type type : types) { 093 builder.addNextParameter(type, false, null); 094 } 095 } 096 097 @NotNull 098 public InlineResult doTransform(@NotNull ConstructorInvocation invocation, @NotNull FieldRemapper parentRemapper) { 099 ClassBuilder classBuilder = createClassBuilder(); 100 final List<MethodNode> methodsToTransform = new ArrayList<MethodNode>(); 101 reader.accept(new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) { 102 103 @Override 104 public void visitOuterClass(@NotNull String owner, String name, String desc) { 105 InliningContext parent = inliningContext.getParent(); 106 assert parent != null : "Context for transformer should have parent one: " + inliningContext; 107 108 //we don't write owner info for lamdbas and SAMs just only for objects 109 if (parent.isRoot() || parent.isInliningLambdaRootContext()) { 110 //TODO: think about writing method info - there is some problem with new constructor desc calculation 111 super.visitOuterClass(inliningContext.getParent().getClassNameToInline(), null, null); 112 return; 113 } 114 115 super.visitOuterClass(owner, name, desc); 116 } 117 118 @Override 119 public MethodVisitor visitMethod( 120 int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions 121 ) { 122 MethodNode node = new MethodNode(access, name, desc, signature, exceptions); 123 if (name.equals("<init>")){ 124 if (constructor != null) 125 throw new RuntimeException("Lambda, SAM or anonymous object should have only one constructor"); 126 127 constructor = node; 128 } else { 129 methodsToTransform.add(node); 130 } 131 return node; 132 } 133 134 @Override 135 public FieldVisitor visitField( 136 int access, @NotNull String name, @NotNull String desc, String signature, Object value 137 ) { 138 addUniqueField(name); 139 if (InlineCodegenUtil.isCapturedFieldName(name)) { 140 return null; 141 } else { 142 return super.visitField(access, name, desc, signature, value); 143 } 144 } 145 }, ClassReader.SKIP_FRAMES); 146 147 ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder(); 148 ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder(); 149 extractParametersMappingAndPatchConstructor(constructor, allCapturedParamBuilder, constructorParamBuilder, invocation); 150 151 InlineResult result = InlineResult.create(); 152 for (MethodNode next : methodsToTransform) { 153 MethodVisitor visitor = newMethod(classBuilder, next); 154 InlineResult funResult = inlineMethod(invocation, parentRemapper, visitor, next, allCapturedParamBuilder); 155 result.addAllClassesToRemove(funResult); 156 } 157 158 InlineResult constructorResult = 159 generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, invocation, parentRemapper); 160 161 result.addAllClassesToRemove(constructorResult); 162 163 classBuilder.done(); 164 165 invocation.setNewLambdaType(newLambdaType); 166 return result; 167 } 168 169 @NotNull 170 private InlineResult inlineMethod( 171 @NotNull ConstructorInvocation invocation, 172 @NotNull FieldRemapper parentRemapper, 173 @NotNull MethodVisitor resultVisitor, 174 @NotNull MethodNode sourceNode, 175 @NotNull ParametersBuilder capturedBuilder 176 ) { 177 178 Parameters parameters = getMethodParametersWithCaptured(capturedBuilder, sourceNode); 179 180 RegeneratedLambdaFieldRemapper remapper = 181 new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(), 182 parameters, invocation.getCapturedLambdasToInline(), 183 parentRemapper); 184 185 MethodInliner inliner = new MethodInliner(sourceNode, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), 186 remapper, isSameModule, "Transformer for " + invocation.getOwnerInternalName()); 187 InlineResult result = inliner.doInline(resultVisitor, new LocalVarRemapper(parameters, 0), false); 188 resultVisitor.visitMaxs(-1, -1); 189 return result; 190 } 191 192 private InlineResult generateConstructorAndFields( 193 @NotNull ClassBuilder classBuilder, 194 @NotNull ParametersBuilder allCapturedBuilder, 195 @NotNull ParametersBuilder constructorInlineBuilder, 196 @NotNull ConstructorInvocation invocation, 197 @NotNull FieldRemapper parentRemapper 198 ) { 199 List<Type> descTypes = new ArrayList<Type>(); 200 201 Parameters constructorParams = constructorInlineBuilder.buildParameters(); 202 int [] capturedIndexes = new int [constructorParams.totalSize()]; 203 int index = 0; 204 int size = 0; 205 206 //complex processing cause it could have super constructor call params 207 for (ParameterInfo info : constructorParams) { 208 if (!info.isSkipped()) { //not inlined 209 if (info.isCaptured() || info instanceof CapturedParamInfo) { 210 capturedIndexes[index] = size; 211 index++; 212 } 213 214 if (size != 0) { //skip this 215 descTypes.add(info.getType()); 216 } 217 size += info.getType().getSize(); 218 } 219 } 220 221 List<Pair<String, Type>> capturedFieldsToGenerate = new ArrayList<Pair<String, Type>>(); 222 for (CapturedParamInfo capturedParamInfo : allCapturedBuilder.listCaptured()) { 223 if (capturedParamInfo.getLambda() == null) { //not inlined 224 capturedFieldsToGenerate.add(new Pair<String, Type>(capturedParamInfo.getNewFieldName(), capturedParamInfo.getType())); 225 } 226 } 227 228 String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, descTypes.toArray(new Type[descTypes.size()])); 229 230 MethodVisitor constructorVisitor = classBuilder.newMethod(null, 231 AsmUtil.NO_FLAG_PACKAGE_PRIVATE, 232 "<init>", constructorDescriptor, 233 null, ArrayUtil.EMPTY_STRING_ARRAY); 234 235 //initialize captured fields 236 List<FieldInfo> fields = AsmUtil.transformCapturedParams(capturedFieldsToGenerate, newLambdaType); 237 int paramIndex = 0; 238 InstructionAdapter capturedFieldInitializer = new InstructionAdapter(constructorVisitor); 239 for (FieldInfo fieldInfo : fields) { 240 AsmUtil.genAssignInstanceFieldFromParam(fieldInfo, capturedIndexes[paramIndex], capturedFieldInitializer); 241 paramIndex++; 242 } 243 244 //then transform constructor 245 Parameters constructorParameters = constructorInlineBuilder.buildParameters(); 246 247 RegeneratedLambdaFieldRemapper remapper = 248 new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(), 249 constructorParameters, invocation.getCapturedLambdasToInline(), 250 parentRemapper); 251 252 MethodInliner inliner = new MethodInliner(constructor, constructorParameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), 253 remapper, isSameModule, "Transformer for constructor of " + invocation.getOwnerInternalName()); 254 InlineResult result = inliner.doInline(capturedFieldInitializer, new LocalVarRemapper(constructorParameters, 0), false); 255 constructorVisitor.visitMaxs(-1, -1); 256 257 AsmUtil.genClosureFields(capturedFieldsToGenerate, classBuilder); 258 //TODO for inline method make public class 259 invocation.setNewConstructorDescriptor(constructorDescriptor); 260 return result; 261 } 262 263 @NotNull 264 private Parameters getMethodParametersWithCaptured( 265 @NotNull ParametersBuilder capturedBuilder, 266 @NotNull MethodNode sourceNode 267 ) { 268 ParametersBuilder builder = ParametersBuilder.newBuilder(); 269 buildInvokeParamsFor(builder, sourceNode); 270 for (CapturedParamInfo param : capturedBuilder.listCaptured()) { 271 builder.addCapturedParamCopy(param); 272 } 273 return builder.buildParameters(); 274 } 275 276 @NotNull 277 private ClassBuilder createClassBuilder() { 278 return new RemappingClassBuilder(state.getFactory().forLambdaInlining(newLambdaType, inliningContext.getRoot().call.getCallElement().getContainingFile()), 279 new TypeRemapper(inliningContext.typeMapping)); 280 } 281 282 @NotNull 283 private static MethodVisitor newMethod(@NotNull ClassBuilder builder, @NotNull MethodNode original) { 284 return builder.newMethod( 285 null, 286 original.access, 287 original.name, 288 original.desc, 289 original.signature, 290 original.exceptions.toArray(new String [original.exceptions.size()]) 291 ); 292 } 293 294 private void extractParametersMappingAndPatchConstructor( 295 @NotNull MethodNode constructor, 296 @NotNull ParametersBuilder capturedParamBuilder, 297 @NotNull ParametersBuilder constructorParamBuilder, 298 @NotNull final ConstructorInvocation invocation 299 ) { 300 301 CapturedParamOwner owner = new CapturedParamOwner() { 302 @Override 303 public Type getType() { 304 return Type.getObjectType(invocation.getOwnerInternalName()); 305 } 306 }; 307 308 List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter 309 List<CapturedParamInfo> capturedToInline = new ArrayList<CapturedParamInfo>(); 310 Map<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline(); 311 Set<Integer> capturedParams = new HashSet<Integer>(); 312 313 //load captured parameters and patch instruction list (NB: there is also could be object fields) 314 AbstractInsnNode cur = constructor.instructions.getFirst(); 315 while (cur != null) { 316 if (cur instanceof FieldInsnNode) { 317 FieldInsnNode fieldNode = (FieldInsnNode) cur; 318 if (fieldNode.getOpcode() == Opcodes.PUTFIELD && InlineCodegenUtil.isCapturedFieldName(fieldNode.name)) { 319 320 boolean isPrevVarNode = fieldNode.getPrevious() instanceof VarInsnNode; 321 boolean isPrevPrevVarNode = isPrevVarNode && fieldNode.getPrevious().getPrevious() instanceof VarInsnNode; 322 323 if (isPrevPrevVarNode) { 324 VarInsnNode node = (VarInsnNode) fieldNode.getPrevious().getPrevious(); 325 if (node.var == 0) { 326 VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious(); 327 int varIndex = previous.var; 328 LambdaInfo lambdaInfo = indexToLambda.get(varIndex); 329 CapturedParamInfo info = capturedParamBuilder.addCapturedParam(owner, fieldNode.name, Type.getType(fieldNode.desc), lambdaInfo != null, null); 330 if (lambdaInfo != null) { 331 info.setLambda(lambdaInfo); 332 capturedLambdas.add(lambdaInfo); 333 capturedToInline.add(info); 334 } 335 capturedParams.add(varIndex); 336 337 constructor.instructions.remove(previous.getPrevious()); 338 constructor.instructions.remove(previous); 339 AbstractInsnNode temp = cur; 340 cur = cur.getNext(); 341 constructor.instructions.remove(temp); 342 continue; 343 } 344 } 345 } 346 } 347 cur = cur.getNext(); 348 } 349 350 constructorParamBuilder.addThis(oldObjectType, false); 351 Type [] types = Type.getArgumentTypes(invocation.getDesc()); 352 for (Type type : types) { 353 LambdaInfo info = indexToLambda.get(constructorParamBuilder.getNextValueParameterIndex()); 354 ParameterInfo parameterInfo = constructorParamBuilder.addNextParameter(type, info != null, null); 355 parameterInfo.setLambda(info); 356 if (capturedParams.contains(parameterInfo.getIndex())) { 357 parameterInfo.setCaptured(true); 358 } else { 359 //otherwise it's super constructor parameter 360 } 361 } 362 363 //For all inlined lambdas add their captured parameters 364 //TODO: some of such parameters could be skipped - we should perform additional analysis 365 Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter 366 List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>(); 367 for (LambdaInfo info : capturedLambdas) { 368 for (CapturedParamInfo var : info.getCapturedVars()) { 369 CapturedParamInfo recapturedParamInfo = capturedParamBuilder.addCapturedParam(var, 370 getNewFieldName(var.getOriginalFieldName())); 371 StackValue composed = StackValue.composed(StackValue.local(0, oldObjectType), 372 StackValue.field(var.getType(), 373 oldObjectType, /*TODO owner type*/ 374 recapturedParamInfo.getNewFieldName(), false) 375 ); 376 recapturedParamInfo.setRemapValue(composed); 377 allRecapturedParameters.add(var); 378 379 constructorParamBuilder.addCapturedParam(var, recapturedParamInfo.getNewFieldName()).setRemapValue(composed); 380 } 381 capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info); 382 } 383 384 //HACK: in inlinining into constructor we access inlined lambda with field access not local var but this lambda added no general params not captured one, 385 //so we need to add them to captured params 386 for (CapturedParamInfo info : capturedToInline) { 387 constructorParamBuilder.addCapturedParamCopy(info); 388 } 389 390 invocation.setAllRecapturedParameters(allRecapturedParameters); 391 invocation.setCapturedLambdasToInline(capturedLambdasToInline); 392 } 393 394 @NotNull 395 public String getNewFieldName(@NotNull String oldName) { 396 if (InlineCodegenUtil.THIS$0.equals(oldName)) { 397 //"this$0" couldn't clash and we should keep this name invariant for further transformations 398 return oldName; 399 } 400 return addUniqueField(oldName + "$inlined"); 401 } 402 403 @NotNull 404 private String addUniqueField(@NotNull String name) { 405 List<String> existNames = fieldNames.get(name); 406 if (existNames == null) { 407 existNames = new LinkedList<String>(); 408 fieldNames.put(name, existNames); 409 } 410 String suffix = existNames.isEmpty() ? "" : "$" + existNames.size(); 411 String newName = name + suffix; 412 existNames.add(newName); 413 return newName; 414 } 415 }