blob: f6222864c07973ff80a15051766132a8b47f5745 [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.ir.conversion;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.passes.AdaptClassStringsRewriter;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPassCollection;
import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRemover;
import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRewriter;
import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer;
import com.android.tools.r8.ir.conversion.passes.DexItemBasedConstStringRemover;
import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
import com.android.tools.r8.ir.conversion.passes.InitClassRemover;
import com.android.tools.r8.ir.conversion.passes.OriginalFieldWitnessRemover;
import com.android.tools.r8.ir.conversion.passes.StoreStoreFenceToInvokeRewriter;
import com.android.tools.r8.ir.conversion.passes.StringSwitchConverter;
import com.android.tools.r8.ir.conversion.passes.StringSwitchRemover;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.lightir.IR2LirConverter;
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.lightir.LirStrategy;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
import com.android.tools.r8.naming.RecordInvokeDynamicInvokeCustomRewriter;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialR8SubCompilationConfiguration;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ThrowingAction;
import com.android.tools.r8.utils.timing.Timing;
import com.android.tools.r8.utils.timing.TimingMerger;
import com.android.tools.r8.verticalclassmerging.IncompleteVerticalClassMergerBridgeCode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class LirConverter {
// Processing is done and no further uses of the meta-data should arise.
private static final BytecodeMetadataProvider noMetadata = BytecodeMetadataProvider.empty();
public static void enterLirSupportedPhase(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
throws ExecutionException {
assert appView.testing().canUseLir(appView);
assert appView.testing().isPreLirPhase();
appView.testing().enterLirSupportedPhase();
CodeRewriterPassCollection codeRewriterPassCollection =
new CodeRewriterPassCollection(
new ConstResourceNumberRewriter(appView),
new StringSwitchConverter(appView),
new IdentifierNameStringMarker(appView));
// Only used for checking assertions.
ProtoReferences protoReferences =
InternalOptions.assertionsEnabled() ? new ProtoReferences(appView.dexItemFactory()) : null;
// Convert code objects to LIR.
ThreadUtils.processItems(
appView.appInfo().classes(),
clazz -> {
clazz.forEachProgramMethodMatching(
method -> method.hasCode() && !appView.isCfByteCodePassThrough(method),
method -> {
assert !method.getDefinition().getCode().hasExplicitCodeLens();
// TODO(b/414965524): Remove the need for checking processed and move the handling
// synchronized methods in DEX to a "CodeRewriterPass" as IR rewriting.
if (method.getDefinition().isProcessed()) {
assert (appView.options().partialSubCompilationConfiguration != null
&& appView.options().partialSubCompilationConfiguration.isR8())
|| (appView.options().protoShrinking().enableGeneratedMessageLiteShrinking
&& protoReferences.isDynamicMethod(method.getDefinition()));
method.getDefinition().markNotProcessed();
}
IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
codeRewriterPassCollection.run(
code, null, null, Timing.empty(), null, appView.options());
LirCode<Integer> lirCode =
IR2LirConverter.translate(
code,
BytecodeMetadataProvider.empty(),
LirStrategy.getDefaultStrategy().getEncodingStrategy(),
appView.options());
method.setCode(lirCode, appView);
});
},
appView.options().getThreadingModule(),
executorService);
// Conversion to LIR via IR will allocate type elements.
// They are not needed after construction so remove them again.
appView.dexItemFactory().clearTypeElementsCache();
}
public static void rewriteLirWithLens(
AppView<? extends AppInfoWithClassHierarchy> appView,
Timing timing,
ExecutorService executorService)
throws ExecutionException {
rewriteLirWithLens(
appView,
timing,
executorService,
() -> appView.clearCodeRewritings(executorService, timing));
}
public static <T extends Throwable> void rewriteLirWithLens(
AppView<? extends AppInfoWithClassHierarchy> appView,
Timing timing,
ExecutorService executorService,
ThrowingAction<T> onChangedAction)
throws ExecutionException, T {
assert appView.testing().canUseLir(appView);
assert appView.testing().isSupportedLirPhase();
assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
assert verifyLirOnly(appView);
GraphLens graphLens = appView.graphLens();
assert graphLens.isNonIdentityLens();
assert appView.codeLens().isAppliedLens()
|| appView.codeLens().isClearCodeRewritingLens()
|| appView.codeLens().isIdentityLens();
MemberRebindingIdentityLens memberRebindingIdentityLens =
graphLens.asNonIdentityLens().find(GraphLens::isMemberRebindingIdentityLens);
assert memberRebindingIdentityLens != null;
if (graphLens == memberRebindingIdentityLens
&& memberRebindingIdentityLens.getPrevious().isAppliedLens()) {
// Nothing to rewrite.
return;
}
timing.begin("LIR->LIR@" + graphLens.getClass().getName());
rewriteLirWithUnappliedLens(appView, executorService);
timing.end();
onChangedAction.execute();
}
private static void rewriteLirWithUnappliedLens(
AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService)
throws ExecutionException {
LensCodeRewriterUtils rewriterUtils = new LensCodeRewriterUtils(appView, true);
ThreadUtils.processItems(
appView.appInfo().classes(),
clazz ->
clazz.forEachProgramMethodMatching(
m -> m.hasCode() && m.getCode().isLirCode(),
m ->
rewriteLirMethodWithLens(
m, appView, appView.graphLens(), rewriterUtils, Timing.empty())),
appView.options().getThreadingModule(),
executorService);
// Clear the reference type cache after conversion to reduce memory pressure.
appView.dexItemFactory().clearTypeElementsCache();
}
public static void rewriteLirMethodWithLens(
ProgramMethod method,
AppView<?> appView,
GraphLens graphLens,
LensCodeRewriterUtils rewriterUtils,
Timing timing) {
LirCode<Integer> lirCode = method.getDefinition().getCode().asLirCode();
LirCode<Integer> rewrittenLirCode =
lirCode.rewriteWithLens(method, appView, graphLens, rewriterUtils, timing);
if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
method.setCode(rewrittenLirCode, appView);
}
}
public static void finalizeLirToOutputFormat(
AppView<? extends AppInfoWithClassHierarchy> appView,
Timing timing,
ExecutorService executorService)
throws ExecutionException {
assert appView.testing().canUseLir(appView);
assert appView.testing().isSupportedLirPhase();
assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
assert verifyLirOnly(appView);
appView.testing().exitLirSupportedPhase();
DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
String output = appView.options().isGeneratingClassFiles() ? "CF" : "DEX";
timing.begin("LIR->IR->" + output);
CodeRewriterPassCollection codeRewriterPassCollection =
new CodeRewriterPassCollection(
new AdaptClassStringsRewriter(appView),
new ConstResourceNumberRemover(appView),
new StoreStoreFenceToInvokeRewriter(appView),
new OriginalFieldWitnessRemover(appView),
// Must run before DexItemBasedConstStringRemover.
new StringSwitchRemover(appView),
new DexItemBasedConstStringRemover(appView),
new InitClassRemover(appView),
new RecordInvokeDynamicInvokeCustomRewriter(appView),
new FilledNewArrayRewriter(appView));
TimingMerger merger = timing.beginMerger("LirToOutputFormatConverter", executorService);
Collection<Collection<Timing>> timings =
ThreadUtils.processItemsWithResults(
appView.appInfo().classes(),
clazz -> {
Collection<Timing> threadTimings = new ArrayList<>();
clazz.forEachProgramMethodMatching(
DexEncodedMethod::hasCode,
m -> {
Timing threadTiming = Timing.create(m.toSourceString(), appView.options());
finalizeLirMethodToOutputFormat(
m, deadCodeRemover, appView, codeRewriterPassCollection, threadTiming);
threadTimings.add(threadTiming.end());
});
return threadTimings;
},
appView.options().getThreadingModule(),
executorService);
timings.forEach(merger::add);
merger.end();
timing.end();
// Clear the reference type cache after conversion to reduce memory pressure.
appView.dexItemFactory().clearTypeElementsCache();
// At this point all code has been mapped according to the graph lens.
assert appView.graphLens().isMemberRebindingIdentityLens()
&& appView.graphLens().asMemberRebindingIdentityLens().getPrevious() == appView.codeLens();
}
private static void finalizeLirMethodToOutputFormat(
ProgramMethod method,
DeadCodeRemover deadCodeRemover,
AppView<? extends AppInfoWithClassHierarchy> appView,
CodeRewriterPassCollection codeRewriterPassCollection,
Timing threadTiming) {
Code code = method.getDefinition().getCode();
if (!(code instanceof LirCode)) {
return;
}
IRConverter.printMethod(method, "LIR before output format", appView.options());
IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
assert irCode.verifyInvokeInterface(appView);
String previous = IRConverter.printMethodIR(irCode, "IR from LIR", "", appView.options());
Pair<Boolean, String> result =
codeRewriterPassCollection.run(
irCode, null, null, threadTiming, previous, appView.options());
boolean changed = result.getFirst();
previous = result.getSecond();
if (appView.options().isGeneratingDex()) {
ConstantCanonicalizer constantCanonicalizer =
new ConstantCanonicalizer(appView, method, irCode);
if (changed) {
constantCanonicalizer.canonicalize();
}
new DexConstantOptimizer(appView, constantCanonicalizer).run(irCode, threadTiming);
}
// During processing optimization info may cause previously live code to become dead.
// E.g., we may now have knowledge that an invoke does not have side effects.
// Thus, we re-run the dead-code remover now as it is assumed complete by CF/DEX finalization.
deadCodeRemover.run(irCode, threadTiming);
previous = IRConverter.printMethodIR(irCode, "IR before finalize", previous, appView.options());
MethodConversionOptions conversionOptions = irCode.getConversionOptions();
assert !conversionOptions.isGeneratingLir();
IRFinalizer<?> finalizer = conversionOptions.getFinalizer(deadCodeRemover, appView);
method.setCode(finalizer.finalizeCode(irCode, noMetadata, threadTiming, previous), appView);
IRConverter.printMethod(method, "Finalized output format", appView.options());
}
public static void finalizeClasspathLirToOutputFormat(
AppView<? extends AppInfoWithClassHierarchy> appView,
Timing timing,
ExecutorService executorService)
throws ExecutionException {
InternalOptions options = appView.options();
if (options.partialSubCompilationConfiguration == null) {
return;
}
timing.begin("Commit classpath classes to program");
R8PartialR8SubCompilationConfiguration subCompilationConfiguration =
options.partialSubCompilationConfiguration.asR8();
subCompilationConfiguration.commitDexingOutputClasses(appView);
timing.end();
String output = appView.options().isGeneratingClassFiles() ? "CF" : "DEX";
timing.begin("LIR->IR->" + output + " (D8)");
AppInfo d8AppInfo =
AppInfo.createInitialAppInfo(
appView.app(),
GlobalSyntheticsStrategy.forNonSynthesizing(),
appView.appInfo().getClassToFeatureSplitMap());
AppView<AppInfo> d8AppView = AppView.createForD8(d8AppInfo);
d8AppView.setNamingLens(appView.getNamingLens());
DeadCodeRemover deadCodeRemover = new DeadCodeRemover(d8AppView);
CodeRewriterPassCollection codeRewriterPassCollection =
new CodeRewriterPassCollection(
// Must run before DexItemBasedConstStringRemover.
new StringSwitchRemover(d8AppView),
new DexItemBasedConstStringRemover(d8AppView),
new FilledNewArrayRewriter(d8AppView));
ThreadUtils.processItems(
subCompilationConfiguration.getDexingOutputClasses(),
clazz ->
clazz.forEachProgramMethod(
m ->
finalizeClasspathLirMethodToOutputFormat(
m, d8AppView, deadCodeRemover, codeRewriterPassCollection)),
options.getThreadingModule(),
executorService);
timing.end();
// Clear the reference type cache after conversion to reduce memory pressure.
appView.dexItemFactory().clearTypeElementsCache();
}
private static void finalizeClasspathLirMethodToOutputFormat(
ProgramMethod method,
AppView<AppInfo> appView,
DeadCodeRemover deadCodeRemover,
CodeRewriterPassCollection codeRewriterPassCollection) {
Code code = method.getDefinition().getCode();
if (!(code instanceof LirCode)) {
return;
}
IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
MethodConversionOptions conversionOptions = irCode.getConversionOptions();
assert conversionOptions.isGeneratingDex();
codeRewriterPassCollection.run(irCode, null, null, Timing.empty(), null, appView.options());
deadCodeRemover.run(irCode, Timing.empty());
IRFinalizer<?> finalizer = conversionOptions.getFinalizer(deadCodeRemover, appView);
method.setCode(finalizer.finalizeCode(irCode, noMetadata, Timing.empty()), appView);
}
public static boolean verifyLirOnly(AppView<? extends AppInfoWithClassHierarchy> appView) {
for (DexProgramClass clazz : appView.appInfo().classes()) {
for (DexEncodedMethod method : clazz.methods(DexEncodedMethod::hasCode)) {
assert method.getCode().isLirCode()
|| method.getCode().isSharedCodeObject()
|| method.getCode() instanceof IncompleteVerticalClassMergerBridgeCode
|| appView.isCfByteCodePassThrough(method)
|| appView.options().skipIR;
}
}
return true;
}
}