• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 
5 package com.android.tools.r8.ir.desugar;
6 
7 import com.android.tools.r8.dex.Constants;
8 import com.android.tools.r8.graph.DexApplication.Builder;
9 import com.android.tools.r8.graph.DexCallSite;
10 import com.android.tools.r8.graph.DexEncodedMethod;
11 import com.android.tools.r8.graph.DexItemFactory;
12 import com.android.tools.r8.graph.DexMethod;
13 import com.android.tools.r8.graph.DexProgramClass;
14 import com.android.tools.r8.graph.DexProto;
15 import com.android.tools.r8.graph.DexString;
16 import com.android.tools.r8.graph.DexType;
17 import com.android.tools.r8.ir.code.BasicBlock;
18 import com.android.tools.r8.ir.code.IRCode;
19 import com.android.tools.r8.ir.code.Instruction;
20 import com.android.tools.r8.ir.code.InstructionListIterator;
21 import com.android.tools.r8.ir.code.InvokeCustom;
22 import com.android.tools.r8.ir.code.InvokeDirect;
23 import com.android.tools.r8.ir.code.MemberType;
24 import com.android.tools.r8.ir.code.MoveType;
25 import com.android.tools.r8.ir.code.NewInstance;
26 import com.android.tools.r8.ir.code.StaticGet;
27 import com.android.tools.r8.ir.code.Value;
28 import com.android.tools.r8.ir.conversion.IRConverter;
29 import java.util.ArrayList;
30 import java.util.IdentityHashMap;
31 import java.util.List;
32 import java.util.ListIterator;
33 import java.util.Map;
34 
35 /**
36  * Lambda desugaring rewriter.
37  *
38  * Performs lambda instantiation point matching,
39  * lambda class generation, and instruction patching.
40  */
41 public class LambdaRewriter {
42   private static final String METAFACTORY_TYPE_DESCR = "Ljava/lang/invoke/LambdaMetafactory;";
43   private static final String CALLSITE_TYPE_DESCR = "Ljava/lang/invoke/CallSite;";
44   private static final String LOOKUP_TYPE_DESCR = "Ljava/lang/invoke/MethodHandles$Lookup;";
45   private static final String METHODTYPE_TYPE_DESCR = "Ljava/lang/invoke/MethodType;";
46   private static final String METHODHANDLE_TYPE_DESCR = "Ljava/lang/invoke/MethodHandle;";
47   private static final String OBJECT_ARRAY_TYPE_DESCR = "[Ljava/lang/Object;";
48   private static final String SERIALIZABLE_TYPE_DESCR = "Ljava/io/Serializable;";
49   private static final String SERIALIZED_LAMBDA_TYPE_DESCR = "Ljava/lang/invoke/SerializedLambda;";
50 
51   private static final String METAFACTORY_METHOD_NAME = "metafactory";
52   private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
53   private static final String DESERIALIZE_LAMBDA_METHOD_NAME = "$deserializeLambda$";
54 
55   static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
56   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
57   static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
58 
59   final IRConverter converter;
60   final DexItemFactory factory;
61 
62   final DexMethod metafactoryMethod;
63   final DexMethod objectInitMethod;
64 
65   final DexMethod metafactoryAltMethod;
66   final DexType serializableType;
67 
68   final DexString constructorName;
69   final DexString classConstructorName;
70   final DexString instanceFieldName;
71 
72   final DexString deserializeLambdaMethodName;
73   final DexProto deserializeLambdaMethodProto;
74 
75   // Maps call sites seen so far to inferred lambda descriptor. It is intended
76   // to help avoid re-matching call sites we already seen. Note that same call
77   // site may match one or several lambda classes.
78   //
79   // NOTE: synchronize concurrent access on `knownCallSites`.
80   private final Map<DexCallSite, LambdaDescriptor> knownCallSites = new IdentityHashMap<>();
81 
82   // Maps lambda class type into lambda class representation. Since lambda class
83   // type uniquely defines lambda class, effectively canonicalizes lambda classes.
84   // NOTE: synchronize concurrent access on `knownLambdaClasses`.
85   private final Map<DexType, LambdaClass> knownLambdaClasses = new IdentityHashMap<>();
86 
87   // Checks if the type starts with lambda-class prefix.
hasLambdaClassPrefix(DexType clazz)88   public static boolean hasLambdaClassPrefix(DexType clazz) {
89     return clazz.getName().startsWith(LAMBDA_CLASS_NAME_PREFIX);
90   }
91 
LambdaRewriter(IRConverter converter)92   public LambdaRewriter(IRConverter converter) {
93     assert converter != null;
94     this.converter = converter;
95     this.factory = converter.application.dexItemFactory;
96 
97     DexType metafactoryType = factory.createType(METAFACTORY_TYPE_DESCR);
98     DexType callSiteType = factory.createType(CALLSITE_TYPE_DESCR);
99     DexType lookupType = factory.createType(LOOKUP_TYPE_DESCR);
100     DexType methodTypeType = factory.createType(METHODTYPE_TYPE_DESCR);
101     DexType methodHandleType = factory.createType(METHODHANDLE_TYPE_DESCR);
102     DexType objectArrayType = factory.createType(OBJECT_ARRAY_TYPE_DESCR);
103 
104     this.metafactoryMethod = factory.createMethod(metafactoryType,
105         factory.createProto(callSiteType, lookupType, factory.stringType, methodTypeType,
106             methodTypeType, methodHandleType, methodTypeType),
107         factory.createString(METAFACTORY_METHOD_NAME));
108 
109     this.metafactoryAltMethod = factory.createMethod(metafactoryType,
110         factory.createProto(callSiteType, lookupType, factory.stringType, methodTypeType,
111             objectArrayType),
112         factory.createString(METAFACTORY_ALT_METHOD_NAME));
113 
114     this.constructorName = factory.createString(Constants.INSTANCE_INITIALIZER_NAME);
115     DexProto initProto = factory.createProto(factory.voidType);
116     this.objectInitMethod = factory.createMethod(factory.objectType, initProto, constructorName);
117     this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
118     this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
119     this.serializableType = factory.createType(SERIALIZABLE_TYPE_DESCR);
120 
121     this.deserializeLambdaMethodName = factory.createString(DESERIALIZE_LAMBDA_METHOD_NAME);
122     this.deserializeLambdaMethodProto = factory.createProto(
123         factory.objectType, factory.createType(SERIALIZED_LAMBDA_TYPE_DESCR));
124   }
125 
126   /**
127    * Detect and desugar lambdas and method references found in the code.
128    *
129    * NOTE: this method can be called concurrently for several different methods.
130    */
desugarLambdas(DexEncodedMethod encodedMethod, IRCode code)131   public void desugarLambdas(DexEncodedMethod encodedMethod, IRCode code) {
132     DexType currentType = encodedMethod.method.holder;
133     ListIterator<BasicBlock> blocks = code.listIterator();
134     while (blocks.hasNext()) {
135       BasicBlock block = blocks.next();
136       InstructionListIterator instructions = block.listIterator();
137       while (instructions.hasNext()) {
138         Instruction instruction = instructions.next();
139         if (instruction.isInvokeCustom()) {
140           LambdaDescriptor descriptor = inferLambdaDescriptor(
141               instruction.asInvokeCustom().getCallSite());
142           if (descriptor == LambdaDescriptor.MATCH_FAILED) {
143             continue;
144           }
145 
146           // We have a descriptor, get or create lambda class.
147           LambdaClass lambdaClass = getOrCreateLambdaClass(descriptor, currentType);
148           assert lambdaClass != null;
149 
150           // We rely on patch performing its work in a way which
151           // keeps both `instructions` and `blocks` iterators in
152           // valid state so that we can continue iteration.
153           patchInstruction(lambdaClass, code, blocks, instructions);
154         }
155       }
156     }
157   }
158 
159   /** Remove lambda deserialization methods. */
removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes)160   public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
161     for (DexProgramClass clazz : classes) {
162       // Search for a lambda deserialization method and remove it if found.
163       DexEncodedMethod[] directMethods = clazz.directMethods;
164       if (directMethods != null) {
165         int methodCount = directMethods.length;
166         for (int i = 0; i < methodCount; i++) {
167           DexEncodedMethod encoded = directMethods[i];
168           DexMethod method = encoded.method;
169           if (method.name == deserializeLambdaMethodName &&
170               method.proto == deserializeLambdaMethodProto) {
171             assert encoded.accessFlags.isStatic();
172             assert encoded.accessFlags.isPrivate();
173             assert encoded.accessFlags.isSynthetic();
174 
175             DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
176             System.arraycopy(directMethods, 0, newMethods, 0, i);
177             System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
178             clazz.directMethods = newMethods;
179 
180             // We assume there is only one such method in the class.
181             break;
182           }
183         }
184       }
185     }
186   }
187 
188   /**
189    * Adjust accessibility of referenced application symbols or
190    * creates necessary accessors.
191    */
adjustAccessibility()192   public void adjustAccessibility() {
193     // For each lambda class perform necessary adjustment of the
194     // referenced symbols to make them accessible. This can result in
195     // method access relaxation or creation of accessor method.
196     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
197       lambdaClass.target.ensureAccessibility();
198     }
199   }
200 
201   /** Generates lambda classes and adds them to the builder. */
synthesizeLambdaClasses(Builder builder)202   public void synthesizeLambdaClasses(Builder builder) {
203     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
204       DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
205       converter.optimizeSynthesizedClass(synthesizedClass);
206       builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
207     }
208   }
209 
210   // Matches invoke-custom instruction operands to infer lambda descriptor
211   // corresponding to this lambda invocation point.
212   //
213   // Returns the lambda descriptor or `MATCH_FAILED`.
inferLambdaDescriptor(DexCallSite callSite)214   private LambdaDescriptor inferLambdaDescriptor(DexCallSite callSite) {
215     // We check the map before and after inferring lambda descriptor to minimize time
216     // spent in synchronized block. As a result we may throw away calculated descriptor
217     // in rare case when another thread has same call site processed concurrently,
218     // but this is a low price to pay comparing to making whole method synchronous.
219     LambdaDescriptor descriptor = getKnown(knownCallSites, callSite);
220     return descriptor != null ? descriptor
221         : putIfAbsent(knownCallSites, callSite, LambdaDescriptor.infer(this, callSite));
222   }
223 
isInMainDexList(DexType type)224   private boolean isInMainDexList(DexType type) {
225     return converter.application.mainDexList.contains(type);
226   }
227 
228   // Returns a lambda class corresponding to the lambda descriptor and context,
229   // creates the class if it does not yet exist.
getOrCreateLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom)230   private LambdaClass getOrCreateLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom) {
231     DexType lambdaClassType = LambdaClass.createLambdaClassType(this, accessedFrom, descriptor);
232     // We check the map twice to to minimize time spent in synchronized block.
233     LambdaClass lambdaClass = getKnown(knownLambdaClasses, lambdaClassType);
234     if (lambdaClass == null) {
235       lambdaClass = putIfAbsent(knownLambdaClasses, lambdaClassType,
236           new LambdaClass(this, accessedFrom, lambdaClassType, descriptor));
237     }
238     if (isInMainDexList(accessedFrom)) {
239       lambdaClass.addToMainDexList.set(true);
240     }
241     return lambdaClass;
242   }
243 
getKnown(Map<K, V> map, K key)244   private <K, V> V getKnown(Map<K, V> map, K key) {
245     synchronized (map) {
246       return map.get(key);
247     }
248   }
249 
putIfAbsent(Map<K, V> map, K key, V value)250   private <K, V> V putIfAbsent(Map<K, V> map, K key, V value) {
251     synchronized (map) {
252       V known = map.get(key);
253       if (known != null) {
254         return known;
255       }
256       map.put(key, value);
257       return value;
258     }
259   }
260 
261   // Patches invoke-custom instruction to create or get an instance
262   // of the generated lambda class.
patchInstruction(LambdaClass lambdaClass, IRCode code, ListIterator<BasicBlock> blocks, InstructionListIterator instructions)263   private void patchInstruction(LambdaClass lambdaClass, IRCode code,
264       ListIterator<BasicBlock> blocks, InstructionListIterator instructions) {
265     assert lambdaClass != null;
266     assert instructions != null;
267     assert instructions.peekPrevious().isInvokeCustom();
268 
269     // Move to the previous instruction, must be InvokeCustom
270     InvokeCustom invoke = instructions.previous().asInvokeCustom();
271 
272     // The value representing new lambda instance: we reuse the
273     // value from the original invoke-custom instruction, and thus
274     // all its usages.
275     Value lambdaInstanceValue = invoke.outValue();
276     if (lambdaInstanceValue == null) {
277       // The out value might be empty in case it was optimized out.
278       lambdaInstanceValue = code.createValue(MoveType.OBJECT);
279     }
280 
281     // For stateless lambdas we replace InvokeCustom instruction with StaticGet
282     // reading the value of INSTANCE field created for singleton lambda class.
283     if (lambdaClass.isStateless()) {
284       instructions.replaceCurrentInstruction(
285           new StaticGet(MemberType.OBJECT, lambdaInstanceValue, lambdaClass.instanceField));
286       // Note that since we replace one throwing operation with another we don't need
287       // to have any special handling for catch handlers.
288       return;
289     }
290 
291     // For stateful lambdas we always create a new instance since we need to pass
292     // captured values to the constructor.
293     //
294     // We replace InvokeCustom instruction with a new NewInstance instruction
295     // instantiating lambda followed by InvokeDirect instruction calling a
296     // constructor on it.
297     //
298     //    original:
299     //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
300     //
301     //    result:
302     //      NewInstance   rResult <-  LambdaClass
303     //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
304     NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
305     instructions.replaceCurrentInstruction(newInstance);
306 
307     List<Value> arguments = new ArrayList<>();
308     arguments.add(lambdaInstanceValue);
309     arguments.addAll(invoke.arguments()); // Optional captures.
310     InvokeDirect constructorCall = new InvokeDirect(
311         lambdaClass.constructor, null /* no return value */, arguments);
312     instructions.add(constructorCall);
313 
314     // If we don't have catch handlers we are done.
315     if (!constructorCall.getBlock().hasCatchHandlers()) {
316       return;
317     }
318 
319     // Move the iterator back to position it between the two instructions, split
320     // the block between the two instructions, and copy the catch handlers.
321     instructions.previous();
322     assert instructions.peekNext().isInvokeDirect();
323     BasicBlock currentBlock = newInstance.getBlock();
324     BasicBlock nextBlock = instructions.split(code, blocks);
325     assert !instructions.hasNext();
326     nextBlock.copyCatchHandlers(code, blocks, currentBlock);
327   }
328 }
329