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