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