blob: ee459b4928340d86ee81a4cb660a5ae1a49cd5e5 [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.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfInvokeDynamic;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfNew;
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
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.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import java.util.function.Supplier;
import org.objectweb.asm.Opcodes;
/**
* 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_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
public static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
private final AppView<?> appView;
final DexString instanceFieldName;
private final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
private final Set<DexMethod> forcefullyMovedMethods = Sets.newIdentityHashSet();
// 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 List<LambdaClass> knownLambdaClasses = new ArrayList<>();
public LambdaRewriter(AppView<?> appView) {
this.appView = appView;
this.instanceFieldName = appView.dexItemFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
}
void forcefullyMoveMethod(DexMethod from, DexMethod to) {
lensBuilder.move(from, to);
forcefullyMovedMethods.add(from);
}
public Set<DexMethod> getForcefullyMovedMethods() {
return forcefullyMovedMethods;
}
private void synthesizeAccessibilityBridgesForLambdaClassesD8(
Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
SortedProgramMethodSet nonDexAccessibilityBridges = SortedProgramMethodSet.create();
List<LambdaClass> sortedLambdaClasses = new ArrayList<>(lambdaClasses);
sortedLambdaClasses.sort((x, y) -> x.type.compareTo(y.type));
for (LambdaClass lambdaClass : sortedLambdaClasses) {
// This call may cause originalMethodSignatures to be updated.
ProgramMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
if (accessibilityBridge != null
&& !accessibilityBridge.getDefinition().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 int desugarLambdas(ProgramMethod method, AppInfoWithClassHierarchy appInfo) {
return desugarLambdas(
method,
callsite -> {
LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callsite, appInfo, method);
if (descriptor == null) {
return null;
}
return createLambdaClass(descriptor, method);
});
}
// Same as above, but where lambdas are always known to exist for the call sites.
public static int desugarLambdas(
ProgramMethod method, Function<DexCallSite, LambdaClass> callSites) {
CfCode code = method.getDefinition().getCode().asCfCode();
List<CfInstruction> instructions = code.getInstructions();
Supplier<List<CfInstruction>> lazyNewInstructions =
Suppliers.memoize(() -> new ArrayList<>(instructions));
int replaced = 0;
int maxTemp = 0;
int newInstructionsDelta = 0;
for (int i = 0; i < instructions.size(); i++) {
CfInstruction instruction = instructions.get(i);
if (instruction instanceof CfInvokeDynamic) {
LambdaClass lambdaClass = callSites.apply(((CfInvokeDynamic) instruction).getCallSite());
if (lambdaClass == null) {
continue;
}
int newInstructionsIndex = i + newInstructionsDelta;
if (lambdaClass.isStateless()) {
CfFieldInstruction getStaticLambdaInstance =
new CfFieldInstruction(
Opcodes.GETSTATIC, lambdaClass.lambdaField, lambdaClass.lambdaField);
lazyNewInstructions.get().set(newInstructionsIndex, getStaticLambdaInstance);
} else {
List<CfInstruction> replacement = new ArrayList<>();
int arguments = lambdaClass.descriptor.captures.size();
int temp = code.getMaxLocals();
for (int j = arguments - 1; j >= 0; j--) {
ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
replacement.add(new CfStore(type, temp));
temp += type.requiredRegisters();
}
maxTemp = Math.max(temp, maxTemp);
replacement.add(new CfNew(lambdaClass.type));
replacement.add(new CfStackInstruction(Opcode.Dup));
for (int j = 0; j < arguments; j++) {
ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
temp -= type.requiredRegisters();
replacement.add(new CfLoad(type, temp));
}
replacement.add(new CfInvoke(Opcodes.INVOKESPECIAL, lambdaClass.constructor, false));
List<CfInstruction> newInstructions = lazyNewInstructions.get();
newInstructions.remove(newInstructionsIndex);
newInstructions.addAll(newInstructionsIndex, replacement);
newInstructionsDelta += replacement.size() - 1;
}
++replaced;
}
}
if (maxTemp > 0) {
assert maxTemp > code.getMaxLocals();
code.setMaxLocals(maxTemp);
}
if (replaced > 0) {
code.setInstructions(lazyNewInstructions.get());
}
return replaced;
}
/** Remove lambda deserialization methods. */
public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
for (DexProgramClass clazz : classes) {
clazz.removeMethod(appView.dexItemFactory().deserializeLambdaMethod);
}
}
/** Generates lambda classes and adds them to the builder. */
public void finalizeLambdaDesugaringForD8(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
synthesizeAccessibilityBridgesForLambdaClassesD8(
knownLambdaClasses, converter, executorService);
fixup();
optimizeSynthesizedClasses(converter, executorService);
}
private void optimizeSynthesizedClasses(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
converter.optimizeSynthesizedClasses(
knownLambdaClasses.stream()
.map(LambdaClass::getLambdaProgramClass)
.collect(ImmutableSet.toImmutableSet()),
executorService);
}
// Creates a lambda class corresponding to the lambda descriptor and context.
public LambdaClass createLambdaClass(LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
Box<LambdaClass> box = new Box<>();
DexProgramClass clazz =
appView
.getSyntheticItems()
.createClass(
SyntheticNaming.SyntheticKind.LAMBDA,
accessedFrom.getHolder(),
appView.dexItemFactory(),
builder ->
box.set(new LambdaClass(builder, appView, this, accessedFrom, descriptor)));
// Immediately set the actual program class on the lambda.
LambdaClass lambdaClass = box.get();
lambdaClass.setClass(clazz);
synchronized (knownLambdaClasses) {
knownLambdaClasses.add(lambdaClass);
}
return lambdaClass;
}
public Collection<LambdaClass> getKnownLambdaClasses() {
return Collections.unmodifiableList(knownLambdaClasses);
}
public NestedGraphLens fixup() {
LambdaRewriterLens lens = lensBuilder.build(appView.graphLens(), appView.dexItemFactory());
if (lens == null) {
return null;
}
for (DexProgramClass clazz : appView.appInfo().classes()) {
EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
if (enclosingMethod != null) {
if (enclosingMethod.getEnclosingMethod() != null) {
DexMethod mappedEnclosingMethod = lens.lookupMethod(enclosingMethod.getEnclosingMethod());
if (mappedEnclosingMethod != enclosingMethod.getEnclosingMethod()) {
clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingMethod));
}
} else {
assert enclosingMethod.getEnclosingClass() != null;
DexType mappedEnclosingClass = lens.lookupType(enclosingMethod.getEnclosingClass());
if (mappedEnclosingClass != enclosingMethod.getEnclosingClass()) {
clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingClass));
}
}
}
}
// Return lens without method map (but still retaining originalMethodSignatures), as the
// generated lambdas classes are generated with the an invoke to the new method, so no
// code rewriting is required.
return lens.withoutMethodMap();
}
static class LambdaRewriterLens extends NestedGraphLens {
LambdaRewriterLens(
Map<DexType, DexType> typeMap,
Map<DexMethod, DexMethod> methodMap,
BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
GraphLens previousLens,
DexItemFactory dexItemFactory) {
super(
typeMap,
methodMap,
fieldMap,
originalMethodSignatures,
previousLens,
dexItemFactory);
}
@Override
protected boolean isLegitimateToHaveEmptyMappings() {
return true;
}
private LambdaRewriterLens withoutMethodMap() {
methodMap.clear();
return this;
}
public static LambdaRewriterLens.Builder builder() {
return new LambdaRewriterLens.Builder();
}
public static class Builder extends NestedGraphLens.Builder {
public LambdaRewriterLens build(GraphLens previousLens, DexItemFactory dexItemFactory) {
if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
return null;
}
assert typeMap.isEmpty();
assert fieldMap.isEmpty();
return new LambdaRewriterLens(
typeMap,
methodMap,
fieldMap,
originalMethodSignatures,
previousLens,
dexItemFactory);
}
}
}
}