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