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 }