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