blob: d6d698e5dc8393b401b9eeaf6e910570f5ee57ba [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
/**
* Lambda desugaring rewriter.
*
* <p>Performs lambda instantiation point matching, lambda class generation, and instruction
* patching.
*/
public class LambdaRewriter {
// Public for testing.
public static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
public static final String LAMBDA_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
private static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
private final AppView<? extends AppInfoWithClassHierarchy> appView;
final DexString instanceFieldName;
final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
// Maps call sites seen so far to inferred lambda descriptor. It is intended
// to help avoid re-matching call sites we already seen. Note that same call
// site may match one or several lambda classes.
//
// NOTE: synchronize concurrent access on `knownCallSites`.
private final Map<DexCallSite, LambdaDescriptor> knownCallSites = new IdentityHashMap<>();
// Maps lambda class type into lambda class representation. Since lambda class
// type uniquely defines lambda class, effectively canonicalizes lambda classes.
// NOTE: synchronize concurrent access on `knownLambdaClasses`.
private final Map<DexType, LambdaClass> knownLambdaClasses = new IdentityHashMap<>();
// Checks if the type starts with lambda-class prefix.
public static boolean hasLambdaClassPrefix(DexType clazz) {
return clazz.getName().startsWith(LAMBDA_CLASS_NAME_PREFIX);
}
public LambdaRewriter(AppView<?> appView) {
assert appView.appInfo().hasClassHierarchy()
: "Lambda desugaring is not available without class hierarchy.";
this.appView = appView.withClassHierarchy();
this.instanceFieldName = getFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
}
public AppView<?> getAppView() {
return appView;
}
public AppInfoWithClassHierarchy getAppInfo() {
return appView.appInfo();
}
public DexItemFactory getFactory() {
return getAppView().dexItemFactory();
}
public Map<DexEncodedField, Set<DexEncodedMethod>> getWritesWithContexts(
DexProgramClass synthesizedLambdaClass) {
// Record that the static fields on each lambda class are only written inside the static
// initializer of the lambdas.
Map<DexEncodedField, Set<DexEncodedMethod>> writesWithContexts = new IdentityHashMap<>();
DexEncodedMethod clinit = synthesizedLambdaClass.getClassInitializer();
if (clinit != null) {
for (DexEncodedField field : synthesizedLambdaClass.staticFields()) {
writesWithContexts.put(field, ImmutableSet.of(clinit));
}
}
return writesWithContexts;
}
private void synthesizeAccessibilityBridgesForLambdaClassesD8(
Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
Set<DexEncodedMethod> nonDexAccessibilityBridges = Sets.newIdentityHashSet();
for (LambdaClass lambdaClass : lambdaClasses) {
// This call may cause originalMethodSignatures to be updated.
DexEncodedMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
if (accessibilityBridge != null && !accessibilityBridge.getCode().isDexCode()) {
nonDexAccessibilityBridges.add(accessibilityBridge);
}
}
if (!nonDexAccessibilityBridges.isEmpty()) {
converter.processMethodsConcurrently(nonDexAccessibilityBridges, executorService);
}
}
/**
* Detect and desugar lambdas and method references found in the code.
*
* <p>NOTE: this method can be called concurrently for several different methods.
*/
public void desugarLambdas(DexEncodedMethod encodedMethod, IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
DexType currentType = encodedMethod.holder();
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
InstructionListIterator instructions = block.listIterator(code);
while (instructions.hasNext()) {
Instruction instruction = instructions.next();
if (instruction.isInvokeCustom()) {
InvokeCustom invoke = instruction.asInvokeCustom();
LambdaDescriptor descriptor =
inferLambdaDescriptor(invoke.getCallSite(), encodedMethod.holder());
if (descriptor == LambdaDescriptor.MATCH_FAILED) {
continue;
}
// We have a descriptor, get the lambda class. In D8, we synthesize the lambda classes
// during IR processing, and therefore we may need to create it now.
LambdaClass lambdaClass =
getAppView().enableWholeProgramOptimizations()
? getKnownLambdaClass(descriptor, currentType)
: getOrCreateLambdaClass(descriptor, currentType);
assert lambdaClass != null;
// We rely on patch performing its work in a way which
// keeps both `instructions` and `blocks` iterators in
// valid state so that we can continue iteration.
patchInstruction(invoke, lambdaClass, code, blocks, instructions, affectedValues);
}
}
}
if (!affectedValues.isEmpty()) {
new TypeAnalysis(getAppView()).narrowing(affectedValues);
}
assert code.isConsistentSSA();
}
/** Remove lambda deserialization methods. */
public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
for (DexProgramClass clazz : classes) {
clazz.removeDirectMethod(getFactory().deserializeLambdaMethod);
}
}
/** Generates lambda classes and adds them to the builder. */
public void finalizeLambdaDesugaringForD8(
Builder<?> builder, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
synthesizeAccessibilityBridgesForLambdaClassesD8(
knownLambdaClasses.values(), converter, executorService);
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
getAppInfo().addSynthesizedClass(synthesizedClass);
builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
}
optimizeSynthesizedClasses(converter, executorService);
}
private void optimizeSynthesizedClasses(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
converter.optimizeSynthesizedClasses(
knownLambdaClasses.values().stream()
.map(LambdaClass::getOrCreateLambdaClass)
.collect(ImmutableSet.toImmutableSet()),
executorService);
}
public Set<DexCallSite> getDesugaredCallSites() {
synchronized (knownCallSites) {
return knownCallSites.keySet();
}
}
// Matches invoke-custom instruction operands to infer lambda descriptor
// corresponding to this lambda invocation point.
//
// Returns the lambda descriptor or `MATCH_FAILED`.
private LambdaDescriptor inferLambdaDescriptor(DexCallSite callSite, DexType invocationContext) {
// We check the map before and after inferring lambda descriptor to minimize time
// spent in synchronized block. As a result we may throw away calculated descriptor
// in rare case when another thread has same call site processed concurrently,
// but this is a low price to pay comparing to making whole method synchronous.
LambdaDescriptor descriptor = getKnown(knownCallSites, callSite);
return descriptor != null
? descriptor
: putIfAbsent(
knownCallSites,
callSite,
LambdaDescriptor.infer(callSite, getAppInfo(), invocationContext));
}
private boolean isInMainDexList(DexType type) {
return getAppInfo().isInMainDexList(type);
}
// Returns a lambda class corresponding to the lambda descriptor and context,
// creates the class if it does not yet exist.
public LambdaClass getOrCreateLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom) {
DexType lambdaClassType = LambdaClass.createLambdaClassType(this, accessedFrom, descriptor);
// We check the map twice to to minimize time spent in synchronized block.
LambdaClass lambdaClass = getKnown(knownLambdaClasses, lambdaClassType);
if (lambdaClass == null) {
lambdaClass =
putIfAbsent(
knownLambdaClasses,
lambdaClassType,
new LambdaClass(this, accessedFrom, lambdaClassType, descriptor));
if (getAppView().options().isDesugaredLibraryCompilation()) {
DexType rewrittenType =
getAppView().rewritePrefix.rewrittenType(accessedFrom, getAppView());
if (rewrittenType == null) {
rewrittenType =
getAppView()
.options()
.desugaredLibraryConfiguration
.getEmulateLibraryInterface()
.get(accessedFrom);
}
if (rewrittenType != null) {
addRewritingPrefix(accessedFrom, rewrittenType, lambdaClassType);
}
}
}
lambdaClass.addSynthesizedFrom(getAppView().definitionFor(accessedFrom).asProgramClass());
if (isInMainDexList(accessedFrom)) {
lambdaClass.addToMainDexList.set(true);
}
return lambdaClass;
}
private LambdaClass getKnownLambdaClass(LambdaDescriptor descriptor, DexType accessedFrom) {
DexType lambdaClassType = LambdaClass.createLambdaClassType(this, accessedFrom, descriptor);
return getKnown(knownLambdaClasses, lambdaClassType);
}
private void addRewritingPrefix(DexType type, DexType rewritten, DexType lambdaClassType) {
String javaName = lambdaClassType.toString();
String typeString = type.toString();
String actualPrefix = typeString.substring(0, typeString.lastIndexOf('.'));
String rewrittenString = rewritten.toString();
String actualRewrittenPrefix = rewrittenString.substring(0, rewrittenString.lastIndexOf('.'));
assert javaName.startsWith(actualPrefix);
getAppView()
.rewritePrefix
.rewriteType(
lambdaClassType,
getFactory()
.createType(
DescriptorUtils.javaTypeToDescriptor(
actualRewrittenPrefix + javaName.substring(actualPrefix.length()))));
}
private static <K, V> V getKnown(Map<K, V> map, K key) {
synchronized (map) {
return map.get(key);
}
}
private static <K, V> V putIfAbsent(Map<K, V> map, K key, V value) {
synchronized (map) {
V known = map.get(key);
if (known != null) {
return known;
}
map.put(key, value);
return value;
}
}
// Patches invoke-custom instruction to create or get an instance
// of the generated lambda class.
private void patchInstruction(
InvokeCustom invoke,
LambdaClass lambdaClass,
IRCode code,
ListIterator<BasicBlock> blocks,
InstructionListIterator instructions,
Set<Value> affectedValues) {
assert lambdaClass != null;
assert instructions != null;
// The value representing new lambda instance: we reuse the
// value from the original invoke-custom instruction, and thus
// all its usages.
Value lambdaInstanceValue = invoke.outValue();
if (lambdaInstanceValue == null) {
// The out value might be empty in case it was optimized out.
lambdaInstanceValue =
code.createValue(
TypeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), getAppView()));
} else {
affectedValues.add(lambdaInstanceValue);
}
// For stateless lambdas we replace InvokeCustom instruction with StaticGet
// reading the value of INSTANCE field created for singleton lambda class.
if (lambdaClass.isStateless()) {
instructions.replaceCurrentInstruction(
new StaticGet(lambdaInstanceValue, lambdaClass.lambdaField));
// Note that since we replace one throwing operation with another we don't need
// to have any special handling for catch handlers.
return;
}
// For stateful lambdas we always create a new instance since we need to pass
// captured values to the constructor.
//
// We replace InvokeCustom instruction with a new NewInstance instruction
// instantiating lambda followed by InvokeDirect instruction calling a
// constructor on it.
//
// original:
// Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
//
// result:
// NewInstance rResult <- LambdaClass
// Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
lambdaInstanceValue.setType(
lambdaInstanceValue.getType().asReferenceType().asDefinitelyNotNull());
NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
instructions.replaceCurrentInstruction(newInstance);
List<Value> arguments = new ArrayList<>();
arguments.add(lambdaInstanceValue);
arguments.addAll(invoke.arguments()); // Optional captures.
InvokeDirect constructorCall =
new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
instructions.add(constructorCall);
constructorCall.setPosition(newInstance.getPosition());
// If we don't have catch handlers we are done.
if (!constructorCall.getBlock().hasCatchHandlers()) {
return;
}
// Move the iterator back to position it between the two instructions, split
// the block between the two instructions, and copy the catch handlers.
instructions.previous();
assert instructions.peekNext().isInvokeDirect();
BasicBlock currentBlock = newInstance.getBlock();
BasicBlock nextBlock = instructions.split(code, blocks);
assert !instructions.hasNext();
nextBlock.copyCatchHandlers(code, blocks, currentBlock, getAppView().options());
}
}