blob: 8364488a3524bb8047df6d05afc2d6a60e9657d6 [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.optimize.compose;
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.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
import java.util.function.Consumer;
/**
* A partial call graph that stores call edges to @Composable functions. By processing all the call
* sites of a given @Composable function we can reapply arguent propagation for the @Composable
* function.
*/
public class ComposableCallGraph {
private final ProgramMethodMap<ComposableCallGraphNode> nodes;
public ComposableCallGraph(ProgramMethodMap<ComposableCallGraphNode> nodes) {
this.nodes = nodes;
}
public static Builder builder(AppView<AppInfoWithLiveness> appView) {
return new Builder(appView);
}
public static ComposableCallGraph empty() {
return new ComposableCallGraph(ProgramMethodMap.empty());
}
public void forEachNode(Consumer<ComposableCallGraphNode> consumer) {
nodes.forEachValue(consumer);
}
public ProgramMethodMap<ComposableCallGraphNode> getNodes() {
return nodes;
}
public boolean isEmpty() {
return nodes.isEmpty();
}
public static class Builder {
private final AppView<AppInfoWithLiveness> appView;
private final ProgramMethodMap<ComposableCallGraphNode> nodes = ProgramMethodMap.create();
Builder(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
}
public ComposableCallGraph build() {
createCallGraphNodesForComposableFunctions();
if (!nodes.isEmpty()) {
addCallEdgesToComposableFunctions();
}
return new ComposableCallGraph(nodes);
}
private void createCallGraphNodesForComposableFunctions() {
ComposeReferences rewrittenComposeReferences =
appView
.getComposeReferences()
.rewrittenWithLens(appView.graphLens(), GraphLens.getIdentityLens());
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.forEachProgramDirectMethodMatching(
method -> method.annotations().hasAnnotation(rewrittenComposeReferences.composableType),
method -> {
// TODO(b/302483644): Don't include kept @Composable functions, since we can't
// optimize them anyway.
assert method.getAccessFlags().isStatic();
nodes.put(method, new ComposableCallGraphNode(method, true));
});
}
}
// TODO(b/302483644): Parallelize identification of @Composable call sites.
private void addCallEdgesToComposableFunctions() {
// Code is fully rewritten so no need to lens rewrite in registry.
assert appView.graphLens().isMemberRebindingIdentityLens();
assert appView.codeLens() == appView.graphLens().asNonIdentityLens().getPrevious();
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.forEachProgramMethodMatching(
DexEncodedMethod::hasCode,
method -> {
Code code = method.getDefinition().getCode();
// TODO(b/302483644): Leverage LIR code constant pool for efficient checking.
// TODO(b/302483644): Maybe remove the possibility of CF/DEX at this point.
assert code.isLirCode()
|| code.isCfCode()
|| code.isDexCode()
|| code.isDefaultInstanceInitializerCode()
|| code.isThrowNullCode();
code.registerCodeReferences(
method,
new UseRegistry<>(appView, method) {
private final AppView<AppInfoWithLiveness> appViewWithLiveness =
appView.withLiveness();
@Override
public void registerInvokeStatic(DexMethod method) {
ProgramMethod resolvedMethod =
appViewWithLiveness
.appInfo()
.unsafeResolveMethodDueToDexFormat(method)
.getResolvedProgramMethod();
if (resolvedMethod == null) {
return;
}
ComposableCallGraphNode callee = nodes.get(resolvedMethod);
if (callee == null || !callee.isComposable()) {
// Only record calls to Composable functions.
return;
}
ComposableCallGraphNode caller =
nodes.computeIfAbsent(
getContext(), context -> new ComposableCallGraphNode(context, false));
callee.addCaller(caller);
}
@Override
public void registerInitClass(DexType type) {}
@Override
public void registerInvokeDirect(DexMethod method) {}
@Override
public void registerInvokeInterface(DexMethod method) {}
@Override
public void registerInvokeSuper(DexMethod method) {}
@Override
public void registerInvokeVirtual(DexMethod method) {}
@Override
public void registerInstanceFieldRead(DexField field) {}
@Override
public void registerInstanceFieldWrite(DexField field) {}
@Override
public void registerStaticFieldRead(DexField field) {}
@Override
public void registerStaticFieldWrite(DexField field) {}
@Override
public void registerTypeReference(DexType type) {}
});
});
}
}
}
}