blob: 10731fb468aa97888fdb34ffa35dd1b920507f27 [file] [log] [blame]
// Copyright (c) 2024, 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.optimize.singlecaller;
import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Deque;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class SingleCallerInliner {
private final AppView<AppInfoWithLiveness> appView;
public SingleCallerInliner(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
}
public void runIfNecessary(ExecutorService executorService, Timing timing)
throws ExecutionException {
if (shouldRun()) {
timing.begin("SingleCallerInliner");
run(executorService);
timing.end();
}
}
private boolean shouldRun() {
InternalOptions options = appView.options();
return !options.debug
&& !options.intermediate
&& options.isOptimizing()
&& options.isShrinking();
}
public void run(ExecutorService executorService) throws ExecutionException {
ProgramMethodMap<ProgramMethod> singleCallerMethods =
new SingleCallerScanner(appView).getSingleCallerMethods(executorService);
Inliner inliner = new SingleCallerInlinerImpl(appView, singleCallerMethods);
processCallees(inliner, singleCallerMethods, executorService);
performInlining(inliner, singleCallerMethods, executorService);
pruneItems(singleCallerMethods, executorService);
}
private void processCallees(
Inliner inliner,
ProgramMethodMap<ProgramMethod> singleCallerMethods,
ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processItems(
singleCallerMethods.streamKeys()::forEach,
callee -> {
IRCode code = callee.buildIR(appView);
getSimpleFeedback()
.markProcessed(callee.getDefinition(), inliner.computeInliningConstraint(code));
// TODO(b/325199754): Do not tamper with the code API level.
if (callee.getDefinition().getApiLevelForCode().isNotSetApiLevel()) {
callee
.getDefinition()
.setApiLevelForCode(
appView.apiLevelCompute().computeInitialMinApiLevel(appView.options()));
}
},
appView.options().getThreadingModule(),
executorService);
}
private void performInlining(
Inliner inliner,
ProgramMethodMap<ProgramMethod> singleCallerMethods,
ExecutorService executorService)
throws ExecutionException {
CallSiteInformation callSiteInformation = createCallSiteInformation(singleCallerMethods);
Deque<ProgramMethodSet> waves = SingleCallerWaves.buildWaves(appView, singleCallerMethods);
while (!waves.isEmpty()) {
ProgramMethodSet wave = waves.removeFirst();
OneTimeMethodProcessor methodProcessor =
OneTimeMethodProcessor.create(wave, MethodProcessorEventConsumer.empty(), appView);
methodProcessor.setCallSiteInformation(callSiteInformation);
methodProcessor.forEachWaveWithExtension(
(method, methodProcessingContext) -> {
// TODO(b/325199754): Do not tamper with the code API level.
if (method.getDefinition().getApiLevelForCode().isNotSetApiLevel()) {
method
.getDefinition()
.setApiLevelForCode(
appView.apiLevelCompute().computeInitialMinApiLevel(appView.options()));
}
IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
inliner.performInlining(
method, code, getSimpleFeedback(), methodProcessor, Timing.empty());
CodeRewriter.removeAssumeInstructions(appView, code);
LirCode<Integer> lirCode =
new IRToLirFinalizer(appView)
.finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
method.setCode(lirCode, appView);
// Recompute inlining constraints if this method is also a single caller callee.
if (singleCallerMethods.containsKey(method)) {
getSimpleFeedback()
.markProcessed(method.getDefinition(), inliner.computeInliningConstraint(code));
}
},
appView.options().getThreadingModule(),
executorService);
}
}
private CallSiteInformation createCallSiteInformation(
ProgramMethodMap<ProgramMethod> singleCallerMethods) {
return new CallSiteInformation() {
@Override
public boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context) {
return singleCallerMethods.containsKey(method);
}
@Override
public boolean hasSingleCallSite(ProgramMethod method) {
return singleCallerMethods.containsKey(method);
}
@Override
public boolean isMultiCallerInlineCandidate(ProgramMethod method) {
return false;
}
@Override
public void unsetCallSiteInformation(ProgramMethod method) {
throw new Unreachable();
}
};
}
private void pruneItems(
ProgramMethodMap<ProgramMethod> singleCallerMethods, ExecutorService executorService)
throws ExecutionException {
PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
singleCallerMethods.forEach(
(callee, caller) -> {
if (callee.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()) {
prunedItemsBuilder.addFullyInlinedMethod(callee.getReference(), caller);
callee.getHolder().removeMethod(callee.getReference());
}
});
PrunedItems prunedItems = prunedItemsBuilder.build();
appView.pruneItems(prunedItems, executorService, Timing.empty());
}
private static class SingleCallerInlinerImpl extends Inliner {
private final ProgramMethodMap<ProgramMethod> singleCallerMethods;
SingleCallerInlinerImpl(
AppView<AppInfoWithLiveness> appView, ProgramMethodMap<ProgramMethod> singleCallerMethods) {
super(appView);
this.singleCallerMethods = singleCallerMethods;
}
@Override
public DefaultInliningOracle createDefaultOracle(
ProgramMethod method,
MethodProcessor methodProcessor,
int inliningInstructionAllowance,
InliningReasonStrategy inliningReasonStrategy) {
return new DefaultInliningOracle(
appView, inliningReasonStrategy, method, methodProcessor, inliningInstructionAllowance) {
@Override
public InlineResult computeInlining(
IRCode code,
InvokeMethod invoke,
SingleResolutionResult<?> resolutionResult,
ProgramMethod singleTarget,
ProgramMethod context,
ClassInitializationAnalysis classInitializationAnalysis,
InliningIRProvider inliningIRProvider,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (!singleCallerMethods.containsKey(singleTarget)) {
return null;
}
return super.computeInlining(
code,
invoke,
resolutionResult,
singleTarget,
context,
classInitializationAnalysis,
inliningIRProvider,
whyAreYouNotInliningReporter);
}
};
}
@Override
public WhyAreYouNotInliningReporter createWhyAreYouNotInliningReporter(
ProgramMethod singleTarget, ProgramMethod context) {
return NopWhyAreYouNotInliningReporter.getInstance();
}
}
}