| // 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); |
| } |
| } |