blob: 17bfc2ba101e44d08d1e054511c2074f47b6e6a3 [file] [log] [blame]
// Copyright (c) 2020, 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.horizontalclassmerging;
import static com.android.tools.r8.ir.conversion.ExtraUnusedParameter.computeExtraUnusedParameters;
import com.android.tools.r8.classmerging.ClassMergerGraphLens;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.FieldLookupResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
import com.android.tools.r8.ir.conversion.ExtraParameter;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class HorizontalClassMergerGraphLens extends ClassMergerGraphLens {
private final Map<DexMethod, ExtraConstantIntParameter> methodExtraParameters;
private final HorizontallyMergedClasses mergedClasses;
private HorizontalClassMergerGraphLens(
AppView<?> appView,
HorizontallyMergedClasses mergedClasses,
Map<DexMethod, ExtraConstantIntParameter> methodExtraParameters,
BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
BidirectionalManyToOneMap<DexMethod, DexMethod> methodMap,
BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
super(
appView,
fieldMap,
methodMap.getForwardMap(),
mergedClasses.getBidirectionalMap(),
newMethodSignatures);
this.methodExtraParameters = methodExtraParameters;
this.mergedClasses = mergedClasses;
}
DexMethod getNextMethodToInvoke(DexMethod method) {
DexMethod nextMethod = methodMap.apply(method);
return nextMethod != null ? nextMethod : method;
}
@Override
public DexType getPreviousClassType(DexType type) {
return type;
}
@Override
public boolean isHorizontalClassMergerGraphLens() {
return true;
}
@Override
public HorizontalClassMergerGraphLens asHorizontalClassMergerGraphLens() {
return this;
}
@Override
protected Iterable<DexType> internalGetOriginalTypes(DexType previous) {
return IterableUtils.prependSingleton(previous, mergedClasses.getSourcesFor(previous));
}
@Override
protected MethodLookupResult internalLookupMethod(
DexMethod reference,
DexMethod context,
InvokeType type,
GraphLens codeLens,
LookupMethodContinuation continuation) {
if (this == codeLens) {
// We sometimes create code objects that have the HorizontalClassMergerGraphLens as code lens.
// When using this lens as a code lens there is no lens that will insert the rebound reference
// since the MemberRebindingIdentityLens is an ancestor of the HorizontalClassMergerGraphLens.
// We therefore use the reference itself as the rebound reference here, which is safe since
// the code objects created during horizontal class merging are guaranteed not to contain
// any non-rebound method references.
// TODO(b/315284255): Actually guarantee the above!
MethodLookupResult lookupResult =
MethodLookupResult.builder(this, codeLens)
.setReboundReference(reference)
.setReference(reference)
.setType(type)
.build();
return continuation.lookupMethod(lookupResult);
}
return super.internalLookupMethod(reference, context, type, codeLens, continuation);
}
/**
* If an overloaded constructor is requested, add the constructor id as a parameter to the
* constructor. Otherwise return the lookup on the underlying graph lens.
*/
@Override
public MethodLookupResult internalDescribeLookupMethod(
MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
if (!previous.hasReboundReference()) {
return super.internalDescribeLookupMethod(previous, context, codeLens);
}
assert previous.hasReboundReference();
return super.internalDescribeLookupMethod(previous, context, codeLens);
}
@Override
protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
FieldLookupResult lookup = super.internalDescribeLookupField(previous);
if (lookup.getReference().isIdenticalTo(previous.getReference())) {
return lookup;
}
DexType newFieldType = lookup.getReference().getType();
DexType preciseNewFieldType = getNextType(previous.getReference().getType());
return FieldLookupResult.builder(this)
.setReference(lookup.getReference())
.setReboundReference(lookup.getReboundReference())
.setReadCastType(
preciseNewFieldType.isNotIdenticalTo(newFieldType) ? preciseNewFieldType : null)
.setWriteCastType(previous.getRewrittenWriteCastType(this::getNextType))
.build();
}
@Override
protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
RewrittenPrototypeDescription prototypeChanges,
DexMethod previousMethod,
DexMethod newMethod) {
if (newMethod.getArity() > previousMethod.getArity()) {
assert dexItemFactory().isConstructor(previousMethod);
RewrittenPrototypeDescription collisionResolution =
RewrittenPrototypeDescription.createForExtraParameters(
computeExtraUnusedParameters(previousMethod, newMethod));
ExtraConstantIntParameter extraParameter = methodExtraParameters.get(previousMethod);
if (extraParameter != null) {
List<ExtraParameter> extraParameters =
(List<ExtraParameter>) collisionResolution.getExtraParameters();
extraParameters.set(0, extraParameter);
}
return prototypeChanges.combine(collisionResolution);
}
assert newMethod.getArity() == previousMethod.getArity();
return prototypeChanges;
}
@Override
public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
DexMethod newMethod, GraphLens codeLens) {
if (this == codeLens) {
return getIdentityLens().lookupPrototypeChangesForMethodDefinition(newMethod, codeLens);
}
DexMethod previousMethod = getPreviousMethodSignature(newMethod);
RewrittenPrototypeDescription lookup =
getPrevious().lookupPrototypeChangesForMethodDefinition(previousMethod, codeLens);
if (newMethod.getArity() > previousMethod.getArity()) {
assert dexItemFactory().isConstructor(previousMethod);
RewrittenPrototypeDescription collisionResolution =
RewrittenPrototypeDescription.createForExtraParameters(
computeExtraUnusedParameters(previousMethod, newMethod));
return lookup.combine(collisionResolution);
}
return lookup;
}
public static class Builder
extends BuilderBase<HorizontalClassMergerGraphLens, HorizontallyMergedClasses> {
private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField>
newFieldSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> methodMap =
BidirectionalManyToOneHashMap.newIdentityHashMap();
private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
newMethodSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
private final Map<DexMethod, ExtraConstantIntParameter> methodExtraParameters =
new IdentityHashMap<>();
private final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> pendingMethodMapUpdates =
BidirectionalManyToOneHashMap.newIdentityHashMap();
private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField>
pendingNewFieldSignatureUpdates =
BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
pendingNewMethodSignatureUpdates =
BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
Builder() {}
@Override
public HorizontalClassMergerGraphLens build(
AppView<?> appView, HorizontallyMergedClasses mergedClasses) {
assert pendingMethodMapUpdates.isEmpty();
assert pendingNewFieldSignatureUpdates.isEmpty();
assert pendingNewMethodSignatureUpdates.isEmpty();
assert newMethodSignatures.values().stream()
.allMatch(
value -> {
assert newMethodSignatures.getKeys(value).size() == 1
|| newMethodSignatures.hasExplicitRepresentativeKey(value);
return true;
});
return new HorizontalClassMergerGraphLens(
appView,
mergedClasses,
methodExtraParameters,
newFieldSignatures,
methodMap,
newMethodSignatures);
}
@Override
public Set<DexMethod> getOriginalMethodReferences(DexMethod method) {
return methodMap.getKeys(method);
}
DexMethod getRenamedMethodSignature(DexMethod method) {
assert newMethodSignatures.containsKey(method);
return newMethodSignatures.get(method);
}
void recordNewFieldSignature(DexField oldFieldSignature, DexField newFieldSignature) {
newFieldSignatures.put(oldFieldSignature, newFieldSignature);
}
void recordNewFieldSignature(
Iterable<DexField> oldFieldSignatures,
DexField newFieldSignature,
DexField representative) {
assert Streams.stream(oldFieldSignatures)
.anyMatch(oldFieldSignature -> oldFieldSignature.isNotIdenticalTo(newFieldSignature));
assert Streams.stream(oldFieldSignatures).noneMatch(newFieldSignatures::containsValue);
assert Iterables.contains(oldFieldSignatures, representative);
for (DexField oldFieldSignature : oldFieldSignatures) {
recordNewFieldSignature(oldFieldSignature, newFieldSignature);
}
newFieldSignatures.setRepresentative(newFieldSignature, representative);
}
@Override
public void fixupField(DexField oldFieldSignature, DexField newFieldSignature) {
fixupOriginalMemberSignatures(
oldFieldSignature,
newFieldSignature,
newFieldSignatures,
pendingNewFieldSignatureUpdates);
}
void mapMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
methodMap.put(oldMethodSignature, newMethodSignature);
}
void moveMethod(DexMethod from, DexMethod to) {
moveMethod(from, to, false);
}
void moveMethod(DexMethod from, DexMethod to, boolean isRepresentative) {
mapMethod(from, to);
recordNewMethodSignature(from, to, isRepresentative);
}
void moveMethods(Iterable<ProgramMethod> methods, DexMethod to) {
moveMethods(methods, to, null);
}
@SuppressWarnings("ReferenceEquality")
void moveMethods(Iterable<ProgramMethod> methods, DexMethod to, ProgramMethod representative) {
for (ProgramMethod from : methods) {
boolean isRepresentative = representative != null && from == representative;
moveMethod(from.getReference(), to, isRepresentative);
}
}
void recordNewMethodSignature(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
recordNewMethodSignature(oldMethodSignature, newMethodSignature, false);
}
void recordNewMethodSignature(
DexMethod oldMethodSignature, DexMethod newMethodSignature, boolean isRepresentative) {
newMethodSignatures.put(oldMethodSignature, newMethodSignature);
if (isRepresentative) {
newMethodSignatures.setRepresentative(newMethodSignature, oldMethodSignature);
}
}
@Override
public void fixupMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
fixupMethodMap(oldMethodSignature, newMethodSignature);
fixupOriginalMemberSignatures(
oldMethodSignature,
newMethodSignature,
newMethodSignatures,
pendingNewMethodSignatureUpdates);
}
private void fixupMethodMap(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
Set<DexMethod> originalMethodSignatures = methodMap.getKeys(oldMethodSignature);
if (originalMethodSignatures.isEmpty()) {
pendingMethodMapUpdates.put(oldMethodSignature, newMethodSignature);
} else {
pendingMethodMapUpdates.put(originalMethodSignatures, newMethodSignature);
}
}
private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
void fixupOriginalMemberSignatures(
R oldMemberSignature,
R newMemberSignature,
MutableBidirectionalManyToOneRepresentativeMap<R, R> newMemberSignatures,
MutableBidirectionalManyToOneRepresentativeMap<R, R> pendingNewMemberSignatureUpdates) {
Set<R> oldMemberSignatures = newMemberSignatures.getKeys(oldMemberSignature);
if (oldMemberSignatures.isEmpty()) {
pendingNewMemberSignatureUpdates.put(oldMemberSignature, newMemberSignature);
} else {
pendingNewMemberSignatureUpdates.put(oldMemberSignatures, newMemberSignature);
R representative = newMemberSignatures.getRepresentativeKey(oldMemberSignature);
if (representative != null) {
pendingNewMemberSignatureUpdates.setRepresentative(newMemberSignature, representative);
}
}
}
@Override
public void commitPendingUpdates() {
// Commit pending method map updates.
methodMap.removeAll(pendingMethodMapUpdates.keySet());
pendingMethodMapUpdates.forEachManyToOneMapping(methodMap::put);
pendingMethodMapUpdates.clear();
// Commit pending original field and method signatures updates.
commitPendingNewMemberSignatureUpdates(newFieldSignatures, pendingNewFieldSignatureUpdates);
commitPendingNewMemberSignatureUpdates(newMethodSignatures, pendingNewMethodSignatureUpdates);
}
private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
void commitPendingNewMemberSignatureUpdates(
MutableBidirectionalManyToOneRepresentativeMap<R, R> newMemberSignatures,
MutableBidirectionalManyToOneRepresentativeMap<R, R> pendingNewMemberSignatureUpdates) {
newMemberSignatures.removeAll(pendingNewMemberSignatureUpdates.keySet());
newMemberSignatures.putAll(pendingNewMemberSignatureUpdates);
pendingNewMemberSignatureUpdates.clear();
}
/**
* One way mapping from one constructor to another. This is used for synthesized constructors,
* where many constructors are merged into a single constructor. The synthesized constructor
* therefore does not have a unique reverse constructor.
*/
void mapMergedConstructor(
DexMethod from, DexMethod to, ExtraConstantIntParameter extraParameter) {
mapMethod(from, to);
if (extraParameter != null) {
methodExtraParameters.put(from, extraParameter);
}
}
}
}