blob: cda8f4eeda7f06e02bee7b0b45a334bcebe0b519 [file] [log] [blame]
// Copyright (c) 2023, 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.conversion;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class PrimaryR8IRConverter extends IRConverter {
private final Timing timing;
public PrimaryR8IRConverter(AppView<? extends AppInfoWithClassHierarchy> appView, Timing timing) {
super(appView);
this.timing = timing;
}
public void optimize(AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
throws ExecutionException, IOException {
timing.begin("Create IR");
try {
DexApplication application =
internalOptimize(appView.withLiveness(), executorService).asDirect();
AppInfoWithClassHierarchy newAppInfo =
appView.appInfo().rebuildWithClassHierarchy(previous -> application);
appView.withClassHierarchy().setAppInfo(newAppInfo);
} finally {
timing.end();
}
}
private DexApplication internalOptimize(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
throws ExecutionException {
// Desugaring happens in the enqueuer.
assert instructionDesugaring.isEmpty();
workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService);
// The process is in two phases in general.
// 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
// reprocessing IR code of methods, e.g., outlining, double-inlining, class staticizer, etc.
// - a side effect is candidates for those optimizations are identified.
// 2) Revisit DexEncodedMethods for the collected candidates.
printPhase("Primary optimization pass");
GraphLens graphLensForPrimaryOptimizationPass = appView.graphLens();
// Setup optimizations for the primary optimization pass.
appView.withArgumentPropagator(
argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing));
enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
if (fieldAccessAnalysis != null) {
fieldAccessAnalysis.fieldAssignmentTracker().initialize();
}
// Process the application identifying outlining candidates.
OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
PostMethodProcessor.Builder postMethodProcessorBuilder =
new PostMethodProcessor.Builder(graphLensForPrimaryOptimizationPass);
{
timing.begin("Build primary method processor");
MethodProcessorEventConsumer eventConsumer =
MethodProcessorEventConsumer.createForR8(appView);
PrimaryMethodProcessor primaryMethodProcessor =
PrimaryMethodProcessor.create(
appView.withLiveness(), eventConsumer, executorService, timing);
timing.end();
timing.begin("IR conversion phase 1");
assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
primaryMethodProcessor.forEachMethod(
(method, methodProcessingContext) ->
processDesugaredMethod(
method, feedback, primaryMethodProcessor, methodProcessingContext),
this::waveStart,
this::waveDone,
timing,
executorService);
lastWaveDone(postMethodProcessorBuilder, executorService);
eventConsumer.finished(appView);
assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
timing.end();
}
// The field access info collection is not maintained during IR processing.
appView.appInfo().withLiveness().getFieldAccessInfoCollection().destroyAccessContexts();
// Assure that no more optimization feedback left after primary processing.
assert feedback.noUpdatesLeft();
appView.setAllCodeProcessed();
// All the code has been processed so the rewriting required by the lenses is done everywhere,
// we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
// lenses with code rewriting are added.
appView.clearCodeRewritings();
// Commit synthetics from the primary optimization pass.
commitPendingSyntheticItems(appView);
// Post processing:
// 1) Second pass for methods whose collected call site information become more precise.
// 2) Second inlining pass for dealing with double inline callers.
printPhase("Post optimization pass");
// Analyze the data collected by the argument propagator, use the analysis result to update
// the parameter optimization infos, and rewrite the application.
// TODO(b/199237357): Automatically rewrite state when lens changes.
enumUnboxer.rewriteWithLens();
outliner.rewriteWithLens();
appView.withArgumentPropagator(
argumentPropagator ->
argumentPropagator.tearDownCodeScanner(
this, postMethodProcessorBuilder, executorService, timing));
if (libraryMethodOverrideAnalysis != null) {
libraryMethodOverrideAnalysis.finish();
}
if (!options.debug) {
new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
.run(executorService, feedback, timing);
}
outliner.rewriteWithLens();
enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback);
appView.unboxedEnums().checkEnumsUnboxed(appView);
GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
outliner.rewriteWithLens();
{
timing.begin("IR conversion phase 2");
MethodProcessorEventConsumer eventConsumer =
MethodProcessorEventConsumer.createForR8(appView);
PostMethodProcessor postMethodProcessor =
timing.time(
"Build post method processor",
() ->
postMethodProcessorBuilder.build(
appView, eventConsumer, executorService, timing));
if (postMethodProcessor != null) {
assert !options.debug;
assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
timing.begin("Process code");
postMethodProcessor.forEachMethod(
(method, methodProcessingContext) ->
processDesugaredMethod(
method, feedback, postMethodProcessor, methodProcessingContext),
feedback,
executorService,
timing);
timing.end();
timing.time("Update visible optimization info", feedback::updateVisibleOptimizationInfo);
eventConsumer.finished(appView);
assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
}
timing.end();
}
enumUnboxer.unsetRewriter();
// All the code that should be impacted by the lenses inserted between phase 1 and phase 2
// have now been processed and rewritten, we clear code lens rewriting so that the class
// staticizer and phase 3 does not perform again the rewriting.
appView.clearCodeRewritings();
// Commit synthetics before creating a builder (otherwise the builder will not include the
// synthetics.)
commitPendingSyntheticItems(appView);
// Build a new application with jumbo string info.
Builder<?> builder = appView.appInfo().app().builder();
builder.setHighestSortingString(highestSortingString);
if (serviceLoaderRewriter != null) {
processSimpleSynthesizeMethods(
serviceLoaderRewriter.getSynthesizedServiceLoadMethods(), executorService);
}
if (instanceInitializerOutliner != null) {
processSimpleSynthesizeMethods(
instanceInitializerOutliner.getSynthesizedMethods(), executorService);
}
if (assertionErrorTwoArgsConstructorRewriter != null) {
processSimpleSynthesizeMethods(
assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService);
}
// Update optimization info for all synthesized methods at once.
feedback.updateVisibleOptimizationInfo();
// TODO(b/127694949): Adapt to PostOptimization.
outliner.performOutlining(this, feedback, executorService, timing);
clearDexMethodCompilationState();
if (identifierNameStringMarker != null) {
identifierNameStringMarker.decoupleIdentifierNameStringsInFields(executorService);
}
// Assure that no more optimization feedback left after post processing.
assert feedback.noUpdatesLeft();
return builder.build();
}
private void clearDexMethodCompilationState() {
appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
}
private void clearDexMethodCompilationState(DexProgramClass clazz) {
clazz.forEachMethod(DexEncodedMethod::markNotProcessed);
}
private static void commitPendingSyntheticItems(AppView<AppInfoWithLiveness> appView) {
if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
appView.setAppInfo(
appView
.appInfo()
.rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
}
}
private void waveStart(ProgramMethodSet wave) {
onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
}
public void waveDone(ProgramMethodSet wave, ExecutorService executorService)
throws ExecutionException {
delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
delayedOptimizationFeedback.updateVisibleOptimizationInfo();
fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
appView.withArgumentPropagator(ArgumentPropagator::publishDelayedReprocessingCriteria);
if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
}
enumUnboxer.updateEnumUnboxingCandidatesInfo();
assert delayedOptimizationFeedback.noUpdatesLeft();
if (onWaveDoneActions != null) {
onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
onWaveDoneActions = null;
}
if (!prunedMethodsInWave.isEmpty()) {
appView.pruneItems(
PrunedItems.builder()
.setRemovedMethods(prunedMethodsInWave)
.setPrunedApp(appView.appInfo().app())
.build(),
executorService);
prunedMethodsInWave.clear();
}
}
private void lastWaveDone(
PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService)
throws ExecutionException {
if (inliner != null) {
inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
}
// Ensure determinism of method-to-reprocess set.
appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
}
}