Add an API to provide information about synthetics.
The synthetic information API can be used to track synthetic
intermediates during builds to ensure that subsequent any
non-intermediate merge step can provide the full set of synthetic
intermediates in the compilation unit.
Bug: b/241351268
Change-Id: Ibcb8aec7ce59c5729ea1308809175e0336c79575
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index c57bc22..52735a5 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -324,6 +324,8 @@
timing.end(); // post-converter
+ reportSyntheticInformation(appView);
+
if (options.isGeneratingClassFiles()) {
new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp);
} else {
@@ -342,6 +344,15 @@
}
}
+ private static void reportSyntheticInformation(AppView<?> appView) {
+ SyntheticInfoConsumer consumer = appView.options().getSyntheticInfoConsumer();
+ if (consumer == null || !appView.options().intermediate) {
+ return;
+ }
+ appView.getSyntheticItems().reportSyntheticsInformation(consumer);
+ consumer.finished();
+ }
+
private static void initializeAssumeInfoCollection(AppView<AppInfo> appView) {
AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 13c12a2..d1495c2 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -90,6 +90,7 @@
private List<GlobalSyntheticsResourceProvider> globalSyntheticsResourceProviders =
new ArrayList<>();
private DesugarGraphConsumer desugarGraphConsumer = null;
+ private SyntheticInfoConsumer syntheticInfoConsumer = null;
private StringConsumer desugaredLibraryKeepRuleConsumer = null;
private String synthesizedClassPrefix = "";
private boolean enableMainDexListCheck = true;
@@ -268,6 +269,21 @@
return self();
}
+ /** Get the consumer that will receive information about compiler synthesized classes. */
+ public SyntheticInfoConsumer getSyntheticInfoConsumer() {
+ return syntheticInfoConsumer;
+ }
+
+ /**
+ * Set the consumer that will receive information about compiler synthesized classes.
+ *
+ * <p>Setting the consumer will clear any previously set consumer.
+ */
+ public Builder setSyntheticInfoConsumer(SyntheticInfoConsumer syntheticInfoConsumer) {
+ this.syntheticInfoConsumer = syntheticInfoConsumer;
+ return self();
+ }
+
/**
* Add a collection of startup profile providers that should be used for distributing the
* program classes in dex.
@@ -456,6 +472,7 @@
getIncludeClassesChecksum(),
getDexClassChecksumFilter(),
getDesugarGraphConsumer(),
+ getSyntheticInfoConsumer(),
desugaredLibraryKeepRuleConsumer,
desugaredLibrarySpecification,
getAssertionsConfiguration(),
@@ -479,6 +496,7 @@
private final boolean intermediate;
private final GlobalSyntheticsConsumer globalSyntheticsConsumer;
+ private final SyntheticInfoConsumer syntheticInfoConsumer;
private final DesugarGraphConsumer desugarGraphConsumer;
private final StringConsumer desugaredLibraryKeepRuleConsumer;
private final DesugaredLibrarySpecification desugaredLibrarySpecification;
@@ -549,6 +567,7 @@
boolean encodeChecksum,
BiPredicate<String, Long> dexClassChecksumFilter,
DesugarGraphConsumer desugarGraphConsumer,
+ SyntheticInfoConsumer syntheticInfoConsumer,
StringConsumer desugaredLibraryKeepRuleConsumer,
DesugaredLibrarySpecification desugaredLibrarySpecification,
List<AssertionsConfiguration> assertionsConfiguration,
@@ -590,6 +609,7 @@
classConflictResolver);
this.intermediate = intermediate;
this.globalSyntheticsConsumer = globalSyntheticsConsumer;
+ this.syntheticInfoConsumer = syntheticInfoConsumer;
this.desugarGraphConsumer = desugarGraphConsumer;
this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -606,6 +626,7 @@
super(printHelp, printVersion);
intermediate = false;
globalSyntheticsConsumer = null;
+ syntheticInfoConsumer = null;
desugarGraphConsumer = null;
desugaredLibraryKeepRuleConsumer = null;
desugaredLibrarySpecification = null;
@@ -641,6 +662,7 @@
internal.intermediate = intermediate;
internal.retainCompileTimeAnnotations = intermediate;
internal.setGlobalSyntheticsConsumer(globalSyntheticsConsumer);
+ internal.setSyntheticInfoConsumer(syntheticInfoConsumer);
internal.desugarGraphConsumer = desugarGraphConsumer;
internal.mainDexKeepRules = mainDexKeepRules;
internal.proguardMapConsumer = proguardMapConsumer;
diff --git a/src/main/java/com/android/tools/r8/SyntheticInfoConsumer.java b/src/main/java/com/android/tools/r8/SyntheticInfoConsumer.java
new file mode 100644
index 0000000..2bfc5b2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/SyntheticInfoConsumer.java
@@ -0,0 +1,34 @@
+// 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;
+
+@Keep
+public interface SyntheticInfoConsumer {
+
+ /**
+ * Callback with information about a compiler synthesized class.
+ *
+ * <p>This callback is only used in intermediate mode builds where the compiler may synthesize
+ * intermediate classes that can be de-duplicated and merged in a later non-incremental step.
+ *
+ * <p>This callback will always be called before the synthetic class is included in the data to
+ * any compiler outputs. Thus, it is safe to assume the information provided here is present when
+ * any compiler output consumers are called for the synthetic class. E.g., in the callbacks of
+ * {@link ClassFileConsumer} or {@link DexFilePerClassFileConsumer}.
+ *
+ * <p>Note: this callback may be called on multiple threads.
+ *
+ * <p>Note: this callback places no guarantees on order of calls or on duplicate calls.
+ *
+ * @param data Information about the synthetic class.
+ */
+ void acceptSyntheticInfo(SyntheticInfoConsumerData data);
+
+ /**
+ * Callback indicating no more synthetics will be generated for the active compilation unit.
+ *
+ * <p>Note: this callback places no other guarantees on number of calls or on which threads.
+ */
+ void finished();
+}
diff --git a/src/main/java/com/android/tools/r8/SyntheticInfoConsumerData.java b/src/main/java/com/android/tools/r8/SyntheticInfoConsumerData.java
new file mode 100644
index 0000000..be28596
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/SyntheticInfoConsumerData.java
@@ -0,0 +1,17 @@
+// 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;
+
+import com.android.tools.r8.references.ClassReference;
+
+/** Information about a compiler synthesized class. */
+@Keep
+public interface SyntheticInfoConsumerData {
+
+ /** Get the reference for the compiler synthesized class. */
+ ClassReference getSyntheticClass();
+
+ /** Get the reference for the context that gave rise to the synthesized class. */
+ ClassReference getSynthesizingContextClass();
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 44f53b1..efe5d62 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -66,9 +66,6 @@
public class SyntheticFinalization {
- // TODO(b/237413146): Implement a non-quadratic grouping algorithm.
- private static final int GROUP_COUNT_THRESHOLD = 10;
-
public static class Result {
public final CommittedItems commit;
public final NonIdentityGraphLens lens;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 49746f4..3bbb9ae 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -6,6 +6,8 @@
import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.SyntheticInfoConsumer;
+import com.android.tools.r8.SyntheticInfoConsumerData;
import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.errors.Unreachable;
@@ -38,6 +40,8 @@
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.Box;
@@ -1169,4 +1173,40 @@
return new SyntheticFinalization(appView.options(), this, committed)
.computeFinalSynthetics(appView, timing);
}
+
+ public void reportSyntheticsInformation(SyntheticInfoConsumer consumer) {
+ assert isFinalized();
+ Map<DexType, DexType> seen = new IdentityHashMap<>();
+ committed.forEachItem(
+ ref -> {
+ DexType holder = ref.getHolder();
+ DexType context = ref.getContext().getSynthesizingContextType();
+ DexType old = seen.put(holder, context);
+ assert old == null || old == context;
+ if (old == null) {
+ consumer.acceptSyntheticInfo(new SyntheticInfoConsumerDataImpl(holder, context));
+ }
+ });
+ }
+
+ private static class SyntheticInfoConsumerDataImpl implements SyntheticInfoConsumerData {
+
+ private final DexType holder;
+ private final DexType context;
+
+ public SyntheticInfoConsumerDataImpl(DexType holder, DexType context) {
+ this.holder = holder;
+ this.context = context;
+ }
+
+ @Override
+ public ClassReference getSyntheticClass() {
+ return Reference.classFromDescriptor(holder.toDescriptorString());
+ }
+
+ @Override
+ public ClassReference getSynthesizingContextClass() {
+ return Reference.classFromDescriptor(context.toDescriptorString());
+ }
+ }
}
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 d3f4b4c..0d7a71c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.SourceFileProvider;
import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.SyntheticInfoConsumer;
import com.android.tools.r8.Version;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.cf.CfVersion;
@@ -181,6 +182,7 @@
public ProgramClassConflictResolver programClassConflictResolver = null;
private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
+ private SyntheticInfoConsumer syntheticInfoConsumer = null;
public DataResourceConsumer dataResourceConsumer;
public FeatureSplitConfiguration featureSplitConfiguration;
@@ -517,6 +519,14 @@
this.globalSyntheticsConsumer = globalSyntheticsConsumer;
}
+ public void setSyntheticInfoConsumer(SyntheticInfoConsumer syntheticInfoConsumer) {
+ this.syntheticInfoConsumer = syntheticInfoConsumer;
+ }
+
+ public SyntheticInfoConsumer getSyntheticInfoConsumer() {
+ return syntheticInfoConsumer;
+ }
+
public boolean isDesugaredLibraryCompilation() {
return machineDesugaredLibrarySpecification.isLibraryCompilation();
}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
index 7a10578..60ca0c6 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
@@ -80,8 +80,17 @@
return PostStartupMockClass.class;
}
+ public static Path getProjectRoot() {
+ String userDirProperty = System.getProperty("user.dir");
+ if (userDirProperty.endsWith("d8_r8/test")) {
+ return Paths.get(userDirProperty).getParent().getParent();
+ }
+ return Paths.get("");
+ }
+
public Path getJava8RuntimeJar() {
- return Paths.get("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar");
+ return getProjectRoot()
+ .resolve(Paths.get("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar"));
}
public List<String> getKeepMainRules(Class<?> clazz) {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index f4ef14b..c2d4a30 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest;
import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
-import com.android.tools.r8.compilerapi.dexconsumers.PerClassSyntheticContextsTest;
import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest;
import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
@@ -24,6 +23,7 @@
import com.android.tools.r8.compilerapi.mockdata.PostStartupMockClass;
import com.android.tools.r8.compilerapi.sourcefile.CustomSourceFileTest;
import com.android.tools.r8.compilerapi.startupprofile.StartupProfileApiTest;
+import com.android.tools.r8.compilerapi.syntheticscontexts.SyntheticContextsConsumerTest;
import com.android.tools.r8.compilerapi.testsetup.ApiTestingSetUpTest;
import com.android.tools.r8.compilerapi.wrappers.CommandLineParserTest;
import com.android.tools.r8.compilerapi.wrappers.EnableMissingLibraryApiModelingTest;
@@ -60,7 +60,7 @@
StartupProfileApiTest.ApiTest.class,
ClassConflictResolverTest.ApiTest.class,
ProguardKeepRuleDiagnosticsApiTest.ApiTest.class,
- PerClassSyntheticContextsTest.ApiTest.class);
+ SyntheticContextsConsumerTest.ApiTest.class);
private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/dexconsumers/PerClassSyntheticContextsTest.java b/src/test/java/com/android/tools/r8/compilerapi/syntheticscontexts/SyntheticContextsConsumerTest.java
similarity index 73%
rename from src/test/java/com/android/tools/r8/compilerapi/dexconsumers/PerClassSyntheticContextsTest.java
rename to src/test/java/com/android/tools/r8/compilerapi/syntheticscontexts/SyntheticContextsConsumerTest.java
index 7172b17..f596594 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/dexconsumers/PerClassSyntheticContextsTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/syntheticscontexts/SyntheticContextsConsumerTest.java
@@ -1,9 +1,9 @@
// 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.compilerapi.dexconsumers;
+package com.android.tools.r8.compilerapi.syntheticscontexts;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
@@ -11,6 +11,8 @@
import com.android.tools.r8.D8Command;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.SyntheticInfoConsumer;
+import com.android.tools.r8.SyntheticInfoConsumerData;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.compilerapi.CompilerApiTest;
import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
@@ -20,12 +22,13 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import org.junit.Test;
-public class PerClassSyntheticContextsTest extends CompilerApiTestRunner {
+public class SyntheticContextsConsumerTest extends CompilerApiTestRunner {
- public PerClassSyntheticContextsTest(TestParameters parameters) {
+ public SyntheticContextsConsumerTest(TestParameters parameters) {
super(parameters);
}
@@ -60,10 +63,7 @@
new ApiTest(ApiTest.PARAMETERS)
.run(
outputs.get(backport.getDescriptor()),
- context -> {
- // TODO(b/241351268): This should be the UsesBackport class as context.
- assertNull(context);
- });
+ context -> assertEquals(descriptor(UsesBackport.class), context));
}
public static class UsesBackport {
@@ -79,11 +79,26 @@
}
public void run(byte[] input, Consumer<String> syntheticContext) throws Exception {
+ Map<String, String> synthetic2context = new ConcurrentHashMap<>();
D8.run(
D8Command.builder()
.addClassProgramData(input, Origin.unknown())
.addLibraryFiles(getJava8RuntimeJar())
.setMinApiLevel(1)
+ .setSyntheticInfoConsumer(
+ new SyntheticInfoConsumer() {
+ @Override
+ public void acceptSyntheticInfo(SyntheticInfoConsumerData data) {
+ synthetic2context.put(
+ data.getSyntheticClass().getDescriptor(),
+ data.getSynthesizingContextClass().getDescriptor());
+ }
+
+ @Override
+ public void finished() {
+ // nothing to finish up.
+ }
+ })
.setProgramConsumer(
new DexFilePerClassFileConsumer() {
@@ -93,8 +108,7 @@
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
- // TODO(b/241351268): Inform the caller of the context once possible.
- syntheticContext.accept(null);
+ syntheticContext.accept(synthetic2context.get(primaryClassDescriptor));
}
@Override