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