Inline lambda methods into synthetic accessors
Bug: b/302376570
Change-Id: Ibf28924ab03a7e66d8b957ab8df4e7a3f5a27a81
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 70eb14e..96ceebf 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -825,7 +825,6 @@
internal.passthroughDexCode = true;
// Assert some of R8 optimizations are disabled.
- assert !internal.inlinerOptions().enableInlining;
assert !internal.enableClassInlining;
assert !internal.enableEnumValueOptimization;
assert !internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index b782e91..5079e73 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -223,8 +223,6 @@
internal.mainDexListConsumer = mainDexListConsumer;
internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
internal.minimalMainDex = internal.debug;
- internal.enableEnumValueOptimization = false;
- internal.inlinerOptions().enableInlining = false;
assert internal.retainCompileTimeAnnotations;
internal.retainCompileTimeAnnotations = false;
return internal;
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 1372ccb..912ef55 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -193,7 +193,6 @@
assert !internal.passthroughDexCode;
// Assert some of R8 optimizations are disabled.
- assert !internal.inlinerOptions().enableInlining;
assert !internal.enableClassInlining;
assert !internal.enableEnumValueOptimization;
assert !internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 58011d6..0c9cb62 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -184,6 +184,10 @@
unset(Constants.ACC_PRIVATE);
}
+ public boolean wasPrivate() {
+ return wasSet(Constants.ACC_PRIVATE);
+ }
+
public boolean isProtected() {
return isSet(Constants.ACC_PROTECTED);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index ba6564d..5422c8a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -219,9 +219,12 @@
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
assumeInserter = new AssumeInserter(appViewWithLiveness);
this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
- this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
+ this.inliner =
+ options.inlinerOptions().isEnabled()
+ ? new Inliner(appViewWithLiveness, this, lensCodeRewriter)
+ : null;
this.classInliner =
- options.enableClassInlining && options.inlinerOptions().enableInlining
+ options.enableClassInlining && inliner != null
? new ClassInliner(appViewWithLiveness, inliner)
: null;
this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
@@ -638,7 +641,7 @@
previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
- if (!isDebugMode && options.inlinerOptions().enableInlining && inliner != null) {
+ if (inliner != null && !isDebugMode) {
timing.begin("Inlining");
inliner.performInlining(code.context(), code, feedback, methodProcessor, timing);
timing.end();
@@ -716,8 +719,6 @@
timing.begin("Inline classes");
// Class inliner should work before lambda merger, so if it inlines the
// lambda, it does not get collected by merger.
- assert options.inlinerOptions().enableInlining;
- assert inliner != null;
classInliner.processMethodCode(
context, code, feedback, methodProcessor, methodProcessingContext);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
index 1e890ec..16dfc8b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
@@ -59,12 +59,24 @@
public CallSiteInformation createCallSiteInformation(
AppView<AppInfoWithLiveness> appView, MethodProcessorWithWave methodProcessor) {
- // Don't leverage single/dual call site information when we are not tree shaking.
- return appView.options().isShrinking()
+ return needsCallSiteInformation(appView)
? new CallGraphBasedCallSiteInformation(appView, this, methodProcessor)
: CallSiteInformation.empty();
}
+ private boolean needsCallSiteInformation(AppView<AppInfoWithLiveness> appView) {
+ InternalOptions options = appView.options();
+ if (options.debug) {
+ return false;
+ }
+ if (options.isOptimizing() && options.isShrinking()) {
+ return true;
+ }
+ // When we are neither optimizing nor shrinking, we still allow inlining of javac synthetic
+ // lambda methods into R8 generated accessor methods.
+ return options.isGeneratingDex();
+ }
+
public ProgramMethodSet extractLeaves() {
return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
index f89b900..36fe467 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.synthesis.SyntheticItems;
@@ -19,6 +20,7 @@
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
public abstract class CallSiteInformation {
@@ -94,16 +96,32 @@
this.methodProcessor = methodProcessor;
InternalOptions options = appView.options();
- ProgramMethodSet pinned =
- MethodOverridesCollector.findAllMethodsAndOverridesThatMatches(
- appView,
- ImmediateProgramSubtypingInfo.create(appView),
- appView.appInfo().classes(),
- method -> {
+ Predicate<ProgramMethod> pinnedPredicate;
+ if (options.isOptimizing() && options.isShrinking()) {
+ ProgramMethodSet pinnedMethods =
+ MethodOverridesCollector.findAllMethodsAndOverridesThatMatches(
+ appView,
+ ImmediateProgramSubtypingInfo.create(appView),
+ appView.appInfo().classes(),
+ method -> {
+ KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+ return !keepInfo.isClosedWorldReasoningAllowed(options, method)
+ || keepInfo.isPinned(options, method);
+ });
+ pinnedPredicate = pinnedMethods::contains;
+ } else {
+ pinnedPredicate =
+ method -> {
+ MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
+ if (optimizationInfo.shouldSingleCallerInlineIntoSyntheticLambdaAccessor()) {
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
- return !keepInfo.isClosedWorldReasoningAllowed(options)
- || keepInfo.isPinned(options);
- });
+ assert keepInfo.isClosedWorldReasoningAllowed(options, method);
+ assert !keepInfo.isPinned(options, method);
+ return false;
+ }
+ return true;
+ };
+ }
for (Node node : graph.getNodes()) {
ProgramMethod method = node.getProgramMethod();
@@ -111,7 +129,7 @@
// For non-pinned methods and methods that override library methods we do not know the exact
// number of call sites.
- if (pinned.contains(method)) {
+ if (pinnedPredicate.test(method)) {
continue;
}
@@ -128,7 +146,7 @@
int numberOfCallSites = node.getNumberOfCallSites();
if (numberOfCallSites == 1) {
- if (!appView.getKeepInfo(method).isSingleCallerInliningAllowed(options)) {
+ if (!appView.getKeepInfo(method).isSingleCallerInliningAllowed(options, method)) {
continue;
}
if (methodProcessor.isPostMethodProcessor()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
index 5ab36a6..c0705e4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
@@ -59,7 +59,7 @@
}
if (appViewWithLiveness
.getKeepInfo(callee)
- .isCodeReplacementAllowed(appViewWithLiveness.options())) {
+ .isCodeReplacementAllowed(appViewWithLiveness.options(), callee)) {
// Since the code of the callee may be replaced, we cannot inline it into the caller, and we
// also cannot collect any optimization info for the method. Therefore, we drop the call edge
// to reduce the total number of call graph edges, which should lead to fewer call graph
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index fcd5428..20f1dda 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.ir.desugar;
+import static com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer.emptyForcefullyMovedLambdaMethodConsumer;
+import static com.android.tools.r8.ir.desugar.lambda.SyntheticLambdaAccessorMethodConsumer.emptySyntheticLambdaAccessorMethodConsumer;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -30,6 +34,7 @@
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
import com.android.tools.r8.ir.desugar.lambda.LambdaDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.lambda.SyntheticLambdaAccessorMethodConsumer;
import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceDesugaringEventConsumer;
@@ -51,6 +56,7 @@
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Class that gets notified for structural changes made as a result of desugaring (e.g., the
@@ -93,6 +99,8 @@
ProfileCollectionAdditions profileCollectionAdditions,
BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
+ Function<LambdaClass, SyntheticLambdaAccessorMethodConsumer>
+ syntheticLambdaAccessorMethodConsumer,
BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
SyntheticAdditions additions,
BiConsumer<ProgramMethod, ProgramMethod> companionMethodConsumer) {
@@ -101,6 +109,7 @@
appView,
lambdaClassConsumer,
constantDynamicClassConsumer,
+ syntheticLambdaAccessorMethodConsumer,
twrCloseResourceMethodConsumer,
additions,
companionMethodConsumer);
@@ -436,7 +445,9 @@
synthesizedLambdaClasses.sort(Comparator.comparing(LambdaClass::getType));
for (LambdaClass lambdaClass : synthesizedLambdaClasses) {
lambdaClass.target.ensureAccessibilityIfNeeded(
- classConverterResultBuilder, needsProcessing);
+ classConverterResultBuilder,
+ emptySyntheticLambdaAccessorMethodConsumer(),
+ needsProcessing);
lambdaClass.getLambdaProgramClass().forEachProgramMethod(needsProcessing);
}
synthesizedLambdaClasses.clear();
@@ -474,6 +485,8 @@
// synthetic items.
private final BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer;
private final BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer;
+ private final Function<LambdaClass, SyntheticLambdaAccessorMethodConsumer>
+ syntheticLambdaAccessorMethodConsumer;
private final BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer;
private final SyntheticAdditions additions;
@@ -488,12 +501,15 @@
AppView<? extends AppInfoWithClassHierarchy> appView,
BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
+ Function<LambdaClass, SyntheticLambdaAccessorMethodConsumer>
+ syntheticLambdaAccessorMethodConsumer,
BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
SyntheticAdditions additions,
BiConsumer<ProgramMethod, ProgramMethod> onCompanionMethodCallback) {
this.appView = appView;
this.lambdaClassConsumer = lambdaClassConsumer;
this.constantDynamicClassConsumer = constantDynamicClassConsumer;
+ this.syntheticLambdaAccessorMethodConsumer = syntheticLambdaAccessorMethodConsumer;
this.twrCloseResourceMethodConsumer = twrCloseResourceMethodConsumer;
this.additions = additions;
this.onCompanionMethodCallback = onCompanionMethodCallback;
@@ -789,7 +805,10 @@
LambdaClass lambdaClass = entry.getKey();
ProgramMethod context = entry.getValue();
- lambdaClass.target.ensureAccessibilityIfNeeded();
+ lambdaClass.target.ensureAccessibilityIfNeeded(
+ emptyForcefullyMovedLambdaMethodConsumer(),
+ syntheticLambdaAccessorMethodConsumer.apply(lambdaClass),
+ emptyConsumer());
// Populate set of types with serialized lambda method for removal.
if (lambdaClass.descriptor.interfaces.contains(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 6812690..aa3cbe9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.desugar;
-import static com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer.emptyForcefullyMovedLambdaMethodConsumer;
-import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
import static com.android.tools.r8.utils.DesugarUtils.appendFullyQualifiedHolderToMethodName;
import com.android.tools.r8.dex.Constants;
@@ -33,6 +31,7 @@
import com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring.DesugarInvoke;
+import com.android.tools.r8.ir.desugar.lambda.SyntheticLambdaAccessorMethodConsumer;
import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
import java.util.ArrayList;
import java.util.List;
@@ -285,14 +284,18 @@
}
private boolean canAccessModifyLambdaImplMethod() {
- MethodHandleType invokeType = descriptor.implHandle.type;
return appView.options().canAccessModifyLambdaImplementationMethods(appView)
- && !isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared()
- && (invokeType.isInvokeDirect() || invokeType.isInvokeStatic())
- && descriptor.delegatesToLambdaImplMethod(appView.dexItemFactory())
+ && canAccessModifyLambdaImplMethodInD8()
&& !desugaring.isDirectTargetedLambdaImplementationMethod(descriptor.implHandle);
}
+ public boolean canAccessModifyLambdaImplMethodInD8() {
+ MethodHandleType invokeType = descriptor.implHandle.type;
+ return !isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared()
+ && (invokeType.isInvokeDirect() || invokeType.isInvokeStatic())
+ && descriptor.delegatesToLambdaImplMethod(appView.dexItemFactory());
+ }
+
@SuppressWarnings("ReferenceEquality")
private Target createLambdaImplMethodTarget(ProgramMethod accessedFrom) {
DexMethodHandle implHandle = descriptor.implHandle;
@@ -478,18 +481,19 @@
// Ensure access of the referenced symbol(s).
abstract ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer);
- public final void ensureAccessibilityIfNeeded() {
- ensureAccessibilityIfNeeded(emptyForcefullyMovedLambdaMethodConsumer(), emptyConsumer());
- }
-
// Ensure access of the referenced symbol(s).
public final void ensureAccessibilityIfNeeded(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
if (!hasEnsuredAccessibility) {
- ensureAccessibility(forcefullyMovedLambdaMethodConsumer, needsProcessingConsumer);
+ ensureAccessibility(
+ forcefullyMovedLambdaMethodConsumer,
+ syntheticLambdaAccessorMethodConsumer,
+ needsProcessingConsumer);
hasEnsuredAccessibility = true;
}
}
@@ -534,6 +538,7 @@
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
return null;
}
@@ -552,6 +557,7 @@
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// We already found the static method to be called, just relax its accessibility.
MethodAccessFlags flags = target.getAccessFlags();
@@ -580,6 +586,7 @@
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// For all instantiation points for which the compiler creates lambda$
// methods, it creates these methods in the same class/interface.
@@ -659,6 +666,7 @@
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
return null;
}
@@ -680,6 +688,7 @@
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// When compiling with whole program optimization, check that we are not inplace modifying.
// For all instantiation points for which the compiler creates lambda$
@@ -782,6 +791,7 @@
@Override
ProgramMethod ensureAccessibility(
ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+ SyntheticLambdaAccessorMethodConsumer syntheticLambdaAccessorMethodConsumer,
Consumer<ProgramMethod> needsProcessingConsumer) {
// Create a static accessor with proper accessibility.
DexProgramClass accessorClass = appView.definitionForProgramType(callTarget.holder);
@@ -811,6 +821,8 @@
.disableAndroidApiLevelCheck()
.build());
accessorClass.addDirectMethod(accessorMethod.getDefinition());
+ syntheticLambdaAccessorMethodConsumer.acceptSyntheticLambdaAccessorMethod(
+ accessorMethod, implMethod);
needsProcessingConsumer.accept(accessorMethod);
return accessorMethod;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index d92a51a..5ba70be 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -32,18 +32,18 @@
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.utils.Box;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.objectweb.asm.Opcodes;
public class LambdaInstructionDesugaring implements CfInstructionDesugaring {
private final AppView<?> appView;
private final Set<DexMethod> directTargetedLambdaImplementationMethods =
- Sets.newIdentityHashSet();
+ ConcurrentHashMap.newKeySet();
public boolean isDirectTargetedLambdaImplementationMethod(DexMethodHandle implMethod) {
return implMethod.type.isInvokeDirect()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/SyntheticLambdaAccessorMethodConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/SyntheticLambdaAccessorMethodConsumer.java
new file mode 100644
index 0000000..c499cbb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/SyntheticLambdaAccessorMethodConsumer.java
@@ -0,0 +1,16 @@
+// 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.desugar.lambda;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface SyntheticLambdaAccessorMethodConsumer {
+
+ void acceptSyntheticLambdaAccessorMethod(ProgramMethod accessor, DexMethod target);
+
+ static SyntheticLambdaAccessorMethodConsumer emptySyntheticLambdaAccessorMethodConsumer() {
+ return (accessor, target) -> {};
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index a3881fc..a0bfb2d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -524,7 +524,7 @@
ProgramMethod singleTarget,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
DexMethod singleTargetReference = singleTarget.getReference();
- if (!appView.getKeepInfo(singleTarget).isInliningAllowed(options)) {
+ if (!appView.getKeepInfo(singleTarget).isInliningAllowed(options, singleTarget)) {
whyAreYouNotInliningReporter.reportPinned();
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a2940f7..17eb9a8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -156,7 +156,7 @@
@SuppressWarnings("ReferenceEquality")
public ConstraintWithTarget computeInliningConstraint(IRCode code) {
InternalOptions options = appView.options();
- if (!options.inlinerOptions().enableInlining) {
+ if (!options.inlinerOptions().isEnabled()) {
return ConstraintWithTarget.NEVER;
}
ProgramMethod method = code.context();
@@ -165,7 +165,7 @@
return ConstraintWithTarget.NEVER;
}
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
- if (!keepInfo.isInliningAllowed(options) && !keepInfo.isClassInliningAllowed(options)) {
+ if (!keepInfo.isInliningAllowed(options, method) && !keepInfo.isClassInliningAllowed(options)) {
return ConstraintWithTarget.NEVER;
}
@@ -1342,7 +1342,8 @@
singleCallerInlinedMethodsForClass.removeIf(
(callee, caller) -> {
boolean convertToAbstractOrThrowNullMethod =
- callee.getDefinition().belongsToVirtualPool();
+ callee.getDefinition().belongsToVirtualPool()
+ && !callee.getAccessFlags().wasPrivate();
if (callee.getDefinition().getGenericSignature().hasSignature()) {
// TODO(b/203188583): Enable pruning of methods with generic signatures. For this
// to work we need to pass in a seed to GenericSignatureContextBuilder.create in
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index b348230..6f8b997 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -195,6 +195,11 @@
}
@Override
+ public boolean shouldSingleCallerInlineIntoSyntheticLambdaAccessor() {
+ return false;
+ }
+
+ @Override
public boolean mayHaveSideEffects() {
return UNKNOWN_MAY_HAVE_SIDE_EFFECTS;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index c23876d..4edfa65 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -26,7 +26,8 @@
enum InlinePreference {
MultiCallerInline,
ForceInline,
- Default
+ Default,
+ SingleCallerInlineIntoSyntheticLambdaAccessor
}
public boolean isDefault() {
@@ -108,6 +109,8 @@
public abstract boolean forceInline();
+ public abstract boolean shouldSingleCallerInlineIntoSyntheticLambdaAccessor();
+
public abstract boolean mayHaveSideEffects();
/** Context sensitive version of {@link #mayHaveSideEffects()}. */
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
index 727328a..1ec1914 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
@@ -241,6 +241,11 @@
}
@Override
+ public boolean shouldSingleCallerInlineIntoSyntheticLambdaAccessor() {
+ throw new Unreachable();
+ }
+
+ @Override
public boolean returnValueHasBeenPropagated() {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index f8c1f07..16d1013 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -598,6 +598,11 @@
}
@Override
+ public boolean shouldSingleCallerInlineIntoSyntheticLambdaAccessor() {
+ return inlining == InlinePreference.SingleCallerInlineIntoSyntheticLambdaAccessor;
+ }
+
+ @Override
public boolean mayHaveSideEffects() {
return isFlagSet(MAY_HAVE_SIDE_EFFECT_FLAG);
}
@@ -803,6 +808,12 @@
inlining = InlinePreference.Default;
}
+ public void markSingleCallerInlineIntoSyntheticLambdaAccessor() {
+ assert inlining == InlinePreference.Default
+ || inlining == InlinePreference.SingleCallerInlineIntoSyntheticLambdaAccessor;
+ inlining = InlinePreference.SingleCallerInlineIntoSyntheticLambdaAccessor;
+ }
+
void setMultiCallerMethod() {
if (inlining == InlinePreference.Default) {
inlining = InlinePreference.MultiCallerInline;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 063b69b..20741e0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -287,7 +287,6 @@
classesWithSingleCallerInlinedInstanceInitializers, executorService, timing);
classesWithSingleCallerInlinedInstanceInitializers = null;
- // TODO(b/296030319): Also publish the computed optimization information for fields.
PrunedItems prunedItems =
new ArgumentPropagatorOptimizationInfoPopulator(
appView, converter, fieldStates, methodStates, postMethodProcessorBuilder)
@@ -313,7 +312,9 @@
public void onMethodPruned(ProgramMethod method) {
if (codeScanner != null) {
MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
- assert methodState == null || method.getDefinition().belongsToDirectPool();
+ assert methodState == null
+ || method.getDefinition().belongsToDirectPool()
+ || method.getAccessFlags().wasPrivate();
}
assert effectivelyUnusedArgumentsAnalysis != null;
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java
index 205bd4b..26b85ed 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java
@@ -48,7 +48,7 @@
singleCallerMethodCandidates.removeIf(
(callee, caller) ->
callee.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
- || !appView.getKeepInfo(callee).isSingleCallerInliningAllowed(options)
+ || !appView.getKeepInfo(callee).isSingleCallerInliningAllowed(options, callee)
|| !AndroidApiLevelUtils.isApiSafeForInlining(caller, callee, appView.options()));
return traceInstructions(singleCallerMethodCandidates, executorService);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index cb5e723..915973c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
@@ -121,6 +122,8 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
+import com.android.tools.r8.ir.desugar.lambda.SyntheticLambdaAccessorMethodConsumer;
+import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -4288,6 +4291,7 @@
profileCollectionAdditions,
lambdaCallback,
this::recordConstantDynamicSynthesizingContext,
+ this::recordSyntheticLambdaAccessorMethod,
this::recordTwrCloseResourceMethodSynthesizingContext,
additions,
(method, companion) -> {
@@ -4350,6 +4354,33 @@
}
}
+ private SyntheticLambdaAccessorMethodConsumer recordSyntheticLambdaAccessorMethod(
+ LambdaClass lambdaClass) {
+ return (accessor, target) -> recordSyntheticLambdaAccessorMethod(lambdaClass, accessor, target);
+ }
+
+ private void recordSyntheticLambdaAccessorMethod(
+ LambdaClass lambdaClass, ProgramMethod accessor, DexMethod target) {
+ assert mode.isInitialTreeShaking();
+ if (options.debug) {
+ return;
+ }
+ if (options.isOptimizing() && options.isShrinking()) {
+ return;
+ }
+ if (!lambdaClass.canAccessModifyLambdaImplMethodInD8()) {
+ return;
+ }
+ ProgramMethod targetMethod = asProgramMethodOrNull(appView.definitionFor(target));
+ if (targetMethod != null && targetMethod.getHolder() == accessor.getHolder()) {
+ MutableMethodOptimizationInfo optimizationInfo =
+ targetMethod.getDefinition().getMutableOptimizationInfo();
+ optimizationInfo.markSingleCallerInlineIntoSyntheticLambdaAccessor();
+ } else {
+ assert false;
+ }
+ }
+
private void recordTwrCloseResourceMethodSynthesizingContext(
ProgramMethod closeMethod, ProgramMethod context) {
synchronized (synthesizingContexts) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 50c76a6..4b13a61 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.KeepAnnotationCollectionInfo.RetentionInfo;
import com.android.tools.r8.shaking.KeepInfo.Builder;
import com.android.tools.r8.shaking.KeepReason.ReflectiveUseFrom;
@@ -158,6 +159,13 @@
return !isOptimizationAllowed(configuration) || !isShrinkingAllowed(configuration);
}
+ public boolean isPinned(GlobalKeepInfoConfiguration configuration, ProgramMethod method) {
+ if (method.getOptimizationInfo().shouldSingleCallerInlineIntoSyntheticLambdaAccessor()) {
+ return false;
+ }
+ return isPinned(configuration);
+ }
+
/**
* True if an item may have its name minified/changed.
*
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index f121490..e5ed3e7 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -128,6 +128,14 @@
return isOptimizationAllowed(configuration) && internalIsClosedWorldReasoningAllowed();
}
+ public boolean isClosedWorldReasoningAllowed(
+ GlobalKeepInfoConfiguration configuration, ProgramMethod method) {
+ if (method.getOptimizationInfo().shouldSingleCallerInlineIntoSyntheticLambdaAccessor()) {
+ return true;
+ }
+ return isClosedWorldReasoningAllowed(configuration);
+ }
+
boolean internalIsClosedWorldReasoningAllowed() {
return allowClosedWorldReasoning;
}
@@ -138,6 +146,14 @@
: internalIsCodeReplacementAllowed();
}
+ public boolean isCodeReplacementAllowed(
+ GlobalKeepInfoConfiguration configuration, ProgramMethod method) {
+ if (method.getOptimizationInfo().shouldSingleCallerInlineIntoSyntheticLambdaAccessor()) {
+ return false;
+ }
+ return isCodeReplacementAllowed(configuration);
+ }
+
boolean internalIsCodeReplacementAllowed() {
return allowCodeReplacement;
}
@@ -150,7 +166,11 @@
return allowConstantArgumentOptimization;
}
- public boolean isInliningAllowed(GlobalKeepInfoConfiguration configuration) {
+ public boolean isInliningAllowed(
+ GlobalKeepInfoConfiguration configuration, ProgramMethod method) {
+ if (method.getOptimizationInfo().shouldSingleCallerInlineIntoSyntheticLambdaAccessor()) {
+ return true;
+ }
return isOptimizationAllowed(configuration) && internalIsInliningAllowed();
}
@@ -225,7 +245,11 @@
return allowReturnTypeStrengthening;
}
- public boolean isSingleCallerInliningAllowed(GlobalKeepInfoConfiguration configuration) {
+ public boolean isSingleCallerInliningAllowed(
+ GlobalKeepInfoConfiguration configuration, ProgramMethod method) {
+ if (method.getOptimizationInfo().shouldSingleCallerInlineIntoSyntheticLambdaAccessor()) {
+ return true;
+ }
return isOptimizationAllowed(configuration)
&& isShrinkingAllowed(configuration)
&& internalIsSingleCallerInliningAllowed();
@@ -747,7 +771,6 @@
public Joiner disallowParameterAnnotationsRemoval(RetentionInfo retention) {
builder.getParameterAnnotationsInfo().destructiveJoinAnyTypeInfo(retention);
- builder.self();
return self();
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 0952033..20d9edc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -356,7 +356,6 @@
}
public void disableGlobalOptimizations() {
- inlinerOptions.enableInlining = false;
enableClassInlining = false;
enableDevirtualization = false;
enableEnumUnboxing = false;
@@ -1874,6 +1873,13 @@
this.options = options;
}
+ public boolean isEnabled() {
+ // Note that this is deliberately enabled in release mode even when optimizations and
+ // shrinking are disabled, since we still allow inlining of javac synthetic lambda methods
+ // into their R8 generated accessor methods.
+ return enableInlining && !options.debug;
+ }
+
public static void disableInlining(InternalOptions options) {
options.inlinerOptions().enableInlining = false;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/InlineIntoSyntheticLambdaAccessorMethodTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/InlineIntoSyntheticLambdaAccessorMethodTest.java
new file mode 100644
index 0000000..6b97e55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/InlineIntoSyntheticLambdaAccessorMethodTest.java
@@ -0,0 +1,74 @@
+// 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.desugar.lambdas;
+
+import static com.android.tools.r8.ir.desugar.LambdaClass.R8_LAMBDA_ACCESSOR_METHOD_PREFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineIntoSyntheticLambdaAccessorMethodTest extends TestBase {
+
+ @Parameter(0)
+ public boolean debug;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, debug: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimesAndAllApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addDontObfuscate()
+ .addDontOptimize()
+ .addDontShrink()
+ .applyIf(debug, TestCompilerBuilder::debug)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject lambdaMethodSubject =
+ mainClassSubject.uniqueMethodWithFinalName("lambda$main$0");
+ assertThat(lambdaMethodSubject, isPresentIf(debug));
+
+ MethodSubject accessorMethodSubject =
+ mainClassSubject.uniqueMethodThatMatches(
+ method -> method.getFinalName().startsWith(R8_LAMBDA_ACCESSOR_METHOD_PREFIX));
+ assertThat(accessorMethodSubject, isPresent());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new Thread(() -> System.out.println("Running!"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
index e78aece..d2c3545 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -109,7 +109,7 @@
parameters.isCfRuntime(), this::checkNoOutputSynthetics, this::checkOneOutputSynthetic)
.inspectStackTrace(
stackTrace -> {
- int frames = parameters.isCfRuntime() ? 3 : 5;
+ int frames = parameters.isCfRuntime() ? 3 : 4;
checkRawStackTraceFrameCount(stackTrace, frames, "Expected nothing to be inlined");
checkExpectedStackTrace(stackTrace);
});
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
index f391aff..f8a5171 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -299,12 +299,6 @@
null))
.build());
}
- builder.add(
- ExternalStartupMethod.builder()
- .setMethodReference(
- Reference.methodFromMethod(
- B.class.getDeclaredMethod("lambda$synthesize$0", Object.class)))
- .build());
}
builder.add(
ExternalStartupClass.builder().setClassReference(Reference.classFromClass(C.class)).build(),