blob: 954fcba5e973ca8ffa3b43edba0f3fe7faf325d9 [file] [log] [blame]
// Copyright (c) 2022, 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.experimental.startup;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.LazyBox;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class NonEmptyStartupOrder extends StartupOrder {
private final LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems;
// Sets to allow efficient querying without boxing.
private final Set<DexType> nonSyntheticStartupClasses = Sets.newIdentityHashSet();
private final Set<DexType> syntheticStartupClasses = Sets.newIdentityHashSet();
NonEmptyStartupOrder(LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems) {
assert !startupItems.isEmpty();
this.startupItems = startupItems;
for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
if (startupItem.isSynthetic()) {
assert startupItem.isStartupClass();
syntheticStartupClasses.add(startupItem.asStartupClass().getReference());
} else {
DexReference reference =
startupItem.apply(StartupClass::getReference, StartupMethod::getReference);
nonSyntheticStartupClasses.add(reference.getContextType());
}
}
}
@Override
public boolean contains(DexType type, SyntheticItems syntheticItems) {
return syntheticItems.isSyntheticClass(type)
? containsSyntheticClass(type, syntheticItems)
: containsNonSyntheticClass(type);
}
private boolean containsNonSyntheticClass(DexType type) {
return nonSyntheticStartupClasses.contains(type);
}
private boolean containsSyntheticClass(DexType type, SyntheticItems syntheticItems) {
assert syntheticItems.isSyntheticClass(type);
return Iterables.any(
syntheticItems.getSynthesizingContextTypes(type),
this::containsSyntheticClassesSynthesizedFrom);
}
private boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
return syntheticStartupClasses.contains(synthesizingContextType);
}
@Override
public Collection<StartupItem<DexType, DexMethod, ?>> getItems() {
return startupItems;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public StartupOrder rewrittenWithLens(GraphLens graphLens) {
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
new LinkedHashSet<>(startupItems.size());
for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
if (startupItem.isStartupClass()) {
StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
rewrittenStartupItems.add(
StartupClass.dexBuilder()
.setClassReference(graphLens.lookupType(startupClass.getReference()))
.setSynthetic(startupItem.isSynthetic())
.build());
} else {
assert !startupItem.isSynthetic();
StartupMethod<DexType, DexMethod> startupMethod = startupItem.asStartupMethod();
// TODO(b/238173796): This should account for one-to-many mappings. e.g., when a bridge is
// created.
rewrittenStartupItems.add(
StartupMethod.dexBuilder()
.setMethodReference(
graphLens.getRenamedMethodSignature(startupMethod.getReference()))
.build());
}
}
return createNonEmpty(rewrittenStartupItems);
}
/**
* This is called to process the startup order before computing the startup layouts.
*
* <p>This processing makes two key changes to the startup order:
*
* <ul>
* <li>Synthetic startup classes on the form "SLcom/example/SyntheticContext;" represents that
* any method of any synthetic class that have been synthesized from SyntheticContext has
* been executed. This pass removes such entries from the startup order, and replaces them
* by all the methods from all of the synthetics that have been synthesized from
* SyntheticContext.
* <li>Moreover, this inserts a StartupClass event for all supertypes of a given class next to
* the class in the startup order. This ensures that the classes from the super hierarchy
* will be laid out close to their subclasses, at the point where the subclasses are used
* during startup.
* <p>Note that this normally follows from the trace already, except that the class
* initializers of interfaces are not executed when a subclass is used.
* </ul>
*/
@Override
public StartupOrder toStartupOrderForWriting(AppView<?> appView) {
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
new LinkedHashSet<>(startupItems.size());
Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
appView.getSyntheticItems().computeSyntheticContextsToSyntheticClasses(appView);
for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
addStartupItem(
startupItem, rewrittenStartupItems, syntheticContextsToSyntheticClasses, appView);
}
assert rewrittenStartupItems.stream().noneMatch(StartupItem::isSynthetic);
return createNonEmpty(rewrittenStartupItems);
}
private static void addStartupItem(
StartupItem<DexType, DexMethod, ?> startupItem,
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
AppView<?> appView) {
if (startupItem.isSynthetic()) {
assert startupItem.isStartupClass();
StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
List<DexProgramClass> syntheticClassesForContext =
syntheticContextsToSyntheticClasses.getOrDefault(
startupClass.getReference(), Collections.emptyList());
for (DexProgramClass clazz : syntheticClassesForContext) {
addClassAndParentClasses(clazz, rewrittenStartupItems, appView);
addAllMethods(clazz, rewrittenStartupItems);
}
} else {
if (startupItem.isStartupClass()) {
addClassAndParentClasses(
startupItem.asStartupClass().getReference(), rewrittenStartupItems, appView);
} else {
rewrittenStartupItems.add(startupItem);
}
}
}
private static boolean addClass(
DexProgramClass clazz,
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems) {
return rewrittenStartupItems.add(
StartupClass.dexBuilder().setClassReference(clazz.getType()).build());
}
private static void addClassAndParentClasses(
DexType type,
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
AppView<?> appView) {
DexProgramClass definition = appView.app().programDefinitionFor(type);
if (definition != null) {
addClassAndParentClasses(definition, rewrittenStartupItems, appView);
}
}
private static void addClassAndParentClasses(
DexProgramClass clazz,
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
AppView<?> appView) {
if (addClass(clazz, rewrittenStartupItems)) {
addParentClasses(clazz, rewrittenStartupItems, appView);
}
}
private static void addParentClasses(
DexProgramClass clazz,
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
AppView<?> appView) {
clazz.forEachImmediateSupertype(
supertype -> addClassAndParentClasses(supertype, rewrittenStartupItems, appView));
}
private static void addAllMethods(
DexProgramClass clazz,
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems) {
clazz.forEachProgramMethod(
method ->
rewrittenStartupItems.add(
StartupMethod.dexBuilder().setMethodReference(method.getReference()).build()));
}
@Override
public StartupOrder withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) {
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
new LinkedHashSet<>(startupItems.size());
LazyBox<Set<DexType>> contextsOfLiveSynthetics =
new LazyBox<>(
() -> computeContextsOfLiveSynthetics(prunedItems.getPrunedApp(), syntheticItems));
for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
// Only prune non-synthetic classes, since the pruning of a class does not imply that all
// classes synthesized from it have been pruned.
if (startupItem.isSynthetic()) {
assert startupItem.isStartupClass();
StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
if (contextsOfLiveSynthetics.computeIfAbsent().contains(startupClass.getReference())) {
rewrittenStartupItems.add(startupClass);
}
} else {
DexReference reference =
startupItem.apply(StartupClass::getReference, StartupMethod::getReference);
if (!prunedItems.isRemoved(reference)) {
rewrittenStartupItems.add(startupItem);
}
}
}
return createNonEmpty(rewrittenStartupItems);
}
private Set<DexType> computeContextsOfLiveSynthetics(
DexApplication app, SyntheticItems syntheticItems) {
Set<DexType> contextsOfLiveSynthetics = Sets.newIdentityHashSet();
for (DexProgramClass clazz : app.classes()) {
if (syntheticItems.isSyntheticClass(clazz)) {
contextsOfLiveSynthetics.addAll(
syntheticItems.getSynthesizingContextTypes(clazz.getType()));
}
}
return contextsOfLiveSynthetics;
}
private StartupOrder createNonEmpty(
LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems) {
if (startupItems.isEmpty()) {
assert false;
return empty();
}
return new NonEmptyStartupOrder(startupItems);
}
}