Reland "Update global synthetics API to support per-file global output."
This reverts commit e00706123c1e8e8144135588a837cb680e207032.
Bug: b/230445931
Change-Id: Ibae969a14f2bcfbd82997fd82cba039fc727121c
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 61aea99..7cb5133 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -185,9 +185,9 @@
() ->
AppInfo.createInitialAppInfo(
app,
- options.isGeneratingDexFilePerClassFile()
- ? GlobalSyntheticsStrategy.forPerFileMode()
- : GlobalSyntheticsStrategy.forSingleOutputMode(),
+ options.isGeneratingDexIndexed()
+ ? GlobalSyntheticsStrategy.forSingleOutputMode()
+ : GlobalSyntheticsStrategy.forPerFileMode(),
applicationReader.readMainDexClasses(app)));
return timing.time("Create app-view", () -> AppView.createForD8(appInfo, typeRewriter, timing));
}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
index ea8ad11..1f982ce 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.references.ClassReference;
+
/**
* Consumer receiving the data representing global synthetics for the program.
*
@@ -25,7 +27,23 @@
* <p>The encoding of the global synthetics is compiler internal and may vary between compiler
* versions. The data received here is thus only valid as inputs to the same compiler version.
*
- * @param bytes Opaque encoding of the global synthetics for the program.
+ * <p>The context class is the class for which the global synthetic data is needed. If compiling
+ * in DexIndexed mode, the context class will be null.
+ *
+ * <p>The accept method will be called at most once for a given context class (any only once at
+ * all for a DexIndexed mode compilation). The global data for that class may be the same as for
+ * other context classes, but it will be provided for each context.
+ *
+ * @param data Opaque encoding of the global synthetics for the program.
+ * @param context The class giving rise to the global synthetics. Null in DexIndexed mode.
+ * @param handler Diagnostics handler for reporting.
*/
- void accept(byte[] bytes);
+ void accept(ByteDataView data, ClassReference context, DiagnosticsHandler handler);
+
+ /**
+ * Callback indicating that no more global synthetics will be reported for the compilation unit.
+ *
+ * @param handler Diagnostics handler for reporting.
+ */
+ default void finished(DiagnosticsHandler handler) {}
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 1e62a10..cc04d01 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -52,7 +52,9 @@
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexConsumer;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexIndexedConsumer;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexPerFileConsumer;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OriginalSourceFiles;
import com.android.tools.r8.utils.PredicateUtils;
@@ -90,7 +92,7 @@
public Set<VirtualFile> globalSyntheticFiles;
public DexIndexedConsumer programConsumer;
- public InternalGlobalSyntheticsDexConsumer globalsSyntheticsConsumer;
+ public InternalGlobalSyntheticsProgramConsumer globalsSyntheticsConsumer;
private static class SortAnnotations extends MixedSectionCollection {
@@ -221,7 +223,11 @@
globalSyntheticFiles = new HashSet<>(files);
virtualFiles.addAll(globalSyntheticFiles);
globalsSyntheticsConsumer =
- new InternalGlobalSyntheticsDexConsumer(options.getGlobalSyntheticsConsumer());
+ options.isGeneratingDexFilePerClassFile()
+ ? new InternalGlobalSyntheticsDexPerFileConsumer(
+ options.getGlobalSyntheticsConsumer(), appView)
+ : new InternalGlobalSyntheticsDexIndexedConsumer(
+ options.getGlobalSyntheticsConsumer());
}
return virtualFiles;
}
@@ -362,7 +368,7 @@
merger.add(timings);
merger.end();
if (globalsSyntheticsConsumer != null) {
- globalsSyntheticsConsumer.finished(options.reporter);
+ globalsSyntheticsConsumer.finished(appView, namingLens);
}
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 7f8d075..3ac9bc61 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -169,12 +169,12 @@
}
if (!globalSyntheticClasses.isEmpty()) {
InternalGlobalSyntheticsCfConsumer globalsConsumer =
- new InternalGlobalSyntheticsCfConsumer(options.getGlobalSyntheticsConsumer());
+ new InternalGlobalSyntheticsCfConsumer(options.getGlobalSyntheticsConsumer(), appView);
for (DexProgramClass clazz : globalSyntheticClasses) {
writeClassCatchingErrors(
clazz, globalsConsumer, rewriter, markerString, sourceFileEnvironment);
}
- globalsConsumer.finished(options.reporter);
+ globalsConsumer.finished(appView, namingLens);
}
ApplicationWriter.supplyAdditionalConsumers(application, appView, namingLens, options);
}
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 34b8b82..f23e6cf 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
@@ -32,19 +33,24 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
+import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
@@ -223,12 +229,21 @@
this.globalSyntheticsStrategy = globalSyntheticsStrategy;
}
- public Set<DexType> getFinalGlobalSyntheticContexts(DexType globalSynthetic) {
+ public Map<DexType, Set<DexType>> getFinalGlobalSyntheticContexts(
+ AppView appView, NamingLens namingLens) {
assert isFinalized();
- assert isSynthetic(globalSynthetic);
- Set<DexType> contexts = committed.getContextsForGlobal(globalSynthetic);
- assert !contexts.isEmpty();
- return contexts;
+ DexItemFactory factory = appView.dexItemFactory();
+ ImmutableMap<DexType, Set<DexType>> globalContexts = committed.getGlobalContexts();
+ Map<DexType, Set<DexType>> rewritten = new IdentityHashMap<>(globalContexts.size());
+ globalContexts.forEach(
+ (global, contexts) -> {
+ Set<DexType> old =
+ rewritten.put(
+ namingLens.lookupType(global, factory),
+ SetUtils.mapIdentityHashSet(contexts, c -> namingLens.lookupType(c, factory)));
+ assert old == null;
+ });
+ return rewritten;
}
public static void collectSyntheticInputs(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
index 3945a6e..f818928 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -5,88 +5,147 @@
import static com.android.tools.r8.utils.FileUtils.GLOBAL_SYNTHETIC_EXTENSION;
+import com.android.tools.r8.ByteBufferProvider;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.Version;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.Reference;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
-public abstract class InternalGlobalSyntheticsProgramConsumer {
+public abstract class InternalGlobalSyntheticsProgramConsumer
+ implements ProgramConsumer, ByteBufferProvider {
public static final String COMPILER_INFO_ENTRY_NAME = "compilerinfo";
public static final String OUTPUT_KIND_ENTRY_NAME = "kind";
- private final GlobalSyntheticsConsumer consumer;
- private final List<Pair<String, byte[]>> content = new ArrayList<>();
+ // Builder for constructing a valid "globals" data payload.
+ private static class GlobalsFileBuilder {
- public InternalGlobalSyntheticsProgramConsumer(GlobalSyntheticsConsumer consumer) {
- this.consumer = consumer;
- }
+ private final Kind kind;
+ private final List<Pair<String, byte[]>> content = new ArrayList<>();
- public abstract Kind getKind();
-
- synchronized void addGlobalSynthetic(String descriptor, byte[] data) {
- add(getGlobalSyntheticFileName(descriptor), data);
- }
-
- private void add(String entryName, byte[] data) {
- content.add(new Pair<>(entryName, data));
- }
-
- public void finished(DiagnosticsHandler handler) {
- // Add meta information.
- add(COMPILER_INFO_ENTRY_NAME, Version.getVersionString().getBytes(StandardCharsets.UTF_8));
- add(OUTPUT_KIND_ENTRY_NAME, getKind().toString().getBytes(StandardCharsets.UTF_8));
-
- // Size estimate to avoid reallocation of the byte output array.
- final int zipHeaderOverhead = 500;
- final int zipEntryOverhead = 200;
- int estimatedZipSize =
- zipHeaderOverhead
- + ListUtils.fold(
- content,
- 0,
- (acc, pair) ->
- acc + pair.getFirst().length() + pair.getSecond().length + zipEntryOverhead);
- ByteArrayOutputStream baos = new ByteArrayOutputStream(estimatedZipSize);
- try (ZipOutputStream stream = new ZipOutputStream(baos)) {
- for (Pair<String, byte[]> pair : content) {
- ZipUtils.writeToZipStream(stream, pair.getFirst(), pair.getSecond(), ZipEntry.STORED);
- // Clear out the bytes to avoid three copies when converting the boas.
- pair.setSecond(null);
- }
- } catch (IOException e) {
- handler.error(new ExceptionDiagnostic(e));
+ public GlobalsFileBuilder(Kind kind) {
+ this.kind = kind;
}
- byte[] bytes = baos.toByteArray();
- consumer.accept(bytes);
+
+ public Kind getKind() {
+ return kind;
+ }
+
+ void addGlobalSynthetic(String descriptor, byte[] data) {
+ add(getGlobalSyntheticFileName(descriptor), data);
+ }
+
+ private void add(String entryName, byte[] data) {
+ content.add(new Pair<>(entryName, data));
+ }
+
+ public byte[] build() throws IOException {
+ // Add meta information.
+ add(COMPILER_INFO_ENTRY_NAME, Version.getVersionString().getBytes(StandardCharsets.UTF_8));
+ add(OUTPUT_KIND_ENTRY_NAME, getKind().toString().getBytes(StandardCharsets.UTF_8));
+
+ // Size estimate to avoid reallocation of the byte output array.
+ final int zipHeaderOverhead = 500;
+ final int zipEntryOverhead = 200;
+ int estimatedZipSize =
+ zipHeaderOverhead
+ + ListUtils.fold(
+ content,
+ 0,
+ (acc, pair) ->
+ acc + pair.getFirst().length() + pair.getSecond().length + zipEntryOverhead);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(estimatedZipSize);
+ try (ZipOutputStream stream = new ZipOutputStream(baos)) {
+ for (Pair<String, byte[]> pair : content) {
+ ZipUtils.writeToZipStream(stream, pair.getFirst(), pair.getSecond(), ZipEntry.STORED);
+ // Clear out the bytes to avoid three copies when converting the boas.
+ pair.setSecond(null);
+ }
+ }
+ return baos.toByteArray();
+ }
+
+ private static String getGlobalSyntheticFileName(String descriptor) {
+ assert descriptor != null && DescriptorUtils.isClassDescriptor(descriptor);
+ return DescriptorUtils.getClassBinaryNameFromDescriptor(descriptor)
+ + GLOBAL_SYNTHETIC_EXTENSION;
+ }
}
- private static String getGlobalSyntheticFileName(String descriptor) {
- assert descriptor != null && DescriptorUtils.isClassDescriptor(descriptor);
- return DescriptorUtils.getClassBinaryNameFromDescriptor(descriptor)
- + GLOBAL_SYNTHETIC_EXTENSION;
- }
-
- public static class InternalGlobalSyntheticsDexConsumer
+ public static class InternalGlobalSyntheticsDexIndexedConsumer
extends InternalGlobalSyntheticsProgramConsumer implements DexFilePerClassFileConsumer {
- public InternalGlobalSyntheticsDexConsumer(GlobalSyntheticsConsumer consumer) {
- super(consumer);
+ private final GlobalSyntheticsConsumer clientConsumer;
+ private final GlobalsFileBuilder builder = new GlobalsFileBuilder(Kind.DEX);
+
+ public InternalGlobalSyntheticsDexIndexedConsumer(GlobalSyntheticsConsumer clientConsumer) {
+ this.clientConsumer = clientConsumer;
}
@Override
- public Kind getKind() {
+ public synchronized void accept(
+ String primaryClassDescriptor,
+ ByteDataView data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ builder.addGlobalSynthetic(primaryClassDescriptor, data.copyByteData());
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ throw new Unreachable("Unexpected call to non-internal finished.");
+ }
+
+ @Override
+ public void finished(AppView<?> appView, NamingLens namingLens) {
+ byte[] bytes = null;
+ try {
+ bytes = builder.build();
+ } catch (IOException e) {
+ appView.reporter().error(new ExceptionDiagnostic(e));
+ }
+ if (bytes != null) {
+ clientConsumer.accept(ByteDataView.of(bytes), null, appView.reporter());
+ }
+ clientConsumer.finished(appView.reporter());
+ }
+
+ @Override
+ public boolean combineSyntheticClassesWithPrimaryClass() {
+ return false;
+ }
+ }
+
+ public static class InternalGlobalSyntheticsDexPerFileConsumer extends PerFileBase
+ implements DexFilePerClassFileConsumer {
+
+ public InternalGlobalSyntheticsDexPerFileConsumer(
+ GlobalSyntheticsConsumer consumer, AppView appView) {
+ super(consumer, appView);
+ }
+
+ @Override
+ Kind getKind() {
return Kind.DEX;
}
@@ -96,7 +155,7 @@
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
- addGlobalSynthetic(primaryClassDescriptor, data.copyByteData());
+ addGlobal(primaryClassDescriptor, data);
}
@Override
@@ -105,21 +164,89 @@
}
}
- public static class InternalGlobalSyntheticsCfConsumer
- extends InternalGlobalSyntheticsProgramConsumer implements ClassFileConsumer {
+ public static class InternalGlobalSyntheticsCfConsumer extends PerFileBase
+ implements ClassFileConsumer {
- public InternalGlobalSyntheticsCfConsumer(GlobalSyntheticsConsumer consumer) {
- super(consumer);
+ public InternalGlobalSyntheticsCfConsumer(GlobalSyntheticsConsumer consumer, AppView appView) {
+ super(consumer, appView);
}
@Override
- public Kind getKind() {
+ Kind getKind() {
return Kind.CF;
}
@Override
public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
- addGlobalSynthetic(descriptor, data.copyByteData());
+ addGlobal(descriptor, data);
}
}
+
+ private abstract static class PerFileBase extends InternalGlobalSyntheticsProgramConsumer {
+
+ private final AppView appView;
+ private final GlobalSyntheticsConsumer clientConsumer;
+ private final Map<DexType, byte[]> globalToBytes = new ConcurrentHashMap<>();
+
+ public PerFileBase(GlobalSyntheticsConsumer consumer, AppView appView) {
+ this.appView = appView;
+ this.clientConsumer = consumer;
+ }
+
+ abstract Kind getKind();
+
+ public final void finished(DiagnosticsHandler handler) {
+ throw new Unreachable("Unexpected call to non-internal finished.");
+ }
+
+ @Override
+ public void finished(AppView<?> appView, NamingLens namingLens) {
+ Map<DexType, Set<DexType>> globalsToContexts =
+ appView.getSyntheticItems().getFinalGlobalSyntheticContexts(appView, namingLens);
+ Map<DexType, Set<DexType>> contextToGlobals = new IdentityHashMap<>();
+ for (DexType globalType : globalToBytes.keySet()) {
+ // It would be good to assert that the global is a synthetic type, but the naming-lens
+ // is not applied to SyntheticItems in AppView.
+ Set<DexType> contexts = globalsToContexts.get(globalType);
+ // TODO(b/231598779): Contexts should never be null once fixed for records.
+ assert (contexts == null) == (globalType == appView.dexItemFactory().recordTagType);
+ if (contexts != null) {
+ assert !contexts.isEmpty();
+ for (DexType contextType : contexts) {
+ contextToGlobals
+ .computeIfAbsent(contextType, k -> SetUtils.newIdentityHashSet())
+ .add(globalType);
+ }
+ }
+ }
+ contextToGlobals.forEach(
+ (context, globals) -> {
+ GlobalsFileBuilder builder = new GlobalsFileBuilder(getKind());
+ globals.forEach(
+ global ->
+ builder.addGlobalSynthetic(
+ global.toDescriptorString(), globalToBytes.get(global)));
+ byte[] bytes = null;
+ try {
+ bytes = builder.build();
+ } catch (IOException e) {
+ appView.reporter().error(new ExceptionDiagnostic(e));
+ }
+ if (bytes != null) {
+ clientConsumer.accept(
+ ByteDataView.of(bytes),
+ Reference.classFromDescriptor(context.toDescriptorString()),
+ appView.reporter());
+ }
+ });
+ clientConsumer.finished(appView.reporter());
+ }
+
+ void addGlobal(String descriptor, ByteDataView data) {
+ DexType type = appView.dexItemFactory().createType(descriptor);
+ globalToBytes.put(type, data.copyByteData());
+ }
+ }
+
+ public abstract void finished(AppView<?> appView, NamingLens namingLens);
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
index b999875..184e984 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
@@ -7,18 +7,26 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.OutputMode;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -41,18 +49,26 @@
return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
}
- private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+ private void setupTestCompileBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
testBuilder
.addProgramClasses(Main.class, TestClass.class)
.addLibraryClasses(LibraryClass.class)
.addDefaultRuntimeLibrary(parameters)
.setMinApi(parameters.getApiLevel())
- .addAndroidBuildVersion()
.apply(ApiModelingTestHelper::enableStubbingOfClasses)
.apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
.apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
}
+ private void setupTestRuntimeBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+ testBuilder.setMinApi(parameters.getApiLevel()).addAndroidBuildVersion();
+ }
+
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+ setupTestCompileBuilder(testBuilder);
+ setupTestRuntimeBuilder(testBuilder);
+ }
+
private boolean addToBootClasspath() {
return parameters.isDexRuntime()
&& parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel);
@@ -87,6 +103,63 @@
}
@Test
+ public void testD8MergeIndexed() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testD8Merge(OutputMode.DexIndexed);
+ }
+
+ @Test
+ public void testD8MergeFilePerClass() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testD8Merge(OutputMode.DexFilePerClass);
+ }
+
+ @Test
+ public void testD8MergeFilePerClassFile() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testD8Merge(OutputMode.DexFilePerClassFile);
+ }
+
+ public void testD8Merge(OutputMode outputMode) throws Exception {
+ GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+ Path incrementalOut =
+ testForD8()
+ .debug()
+ .setOutputMode(outputMode)
+ .setIntermediate(true)
+ .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals))
+ // TODO(b/213552119): Remove when enabled by default.
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(this::setupTestCompileBuilder)
+ .compile()
+ .writeToZip();
+
+ if (isGreaterOrEqualToMockLevel()) {
+ assertFalse(globals.hasGlobals());
+ } else if (outputMode == OutputMode.DexIndexed) {
+ assertTrue(globals.hasGlobals());
+ assertTrue(globals.isSingleGlobal());
+ } else {
+ assertTrue(globals.hasGlobals());
+ // The TestClass does reference the mock and should have globals.
+ assertNotNull(globals.getProvider(Reference.classFromClass(TestClass.class)));
+ // The Main class does not have references to the mock and should have no globals.
+ assertNull(globals.getProvider(Reference.classFromClass(Main.class)));
+ }
+
+ testForD8()
+ .debug()
+ .addProgramFiles(incrementalOut)
+ .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals.getProviders()))
+ .apply(this::setupTestRuntimeBuilder)
+ .compile()
+ .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput)
+ .inspect(this::inspect);
+ }
+
+ @Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.apply(this::setupTestBuilder)
diff --git a/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java b/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java
index edc42c2..e711b40 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java
@@ -3,9 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.compilerapi.globalsynthetics;
+import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.D8;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.GlobalSyntheticsConsumer;
import com.android.tools.r8.GlobalSyntheticsResourceProvider;
import com.android.tools.r8.ResourceException;
@@ -13,6 +15,7 @@
import com.android.tools.r8.compilerapi.CompilerApiTest;
import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@@ -71,10 +74,16 @@
.setGlobalSyntheticsConsumer(
new GlobalSyntheticsConsumer() {
@Override
- public void accept(byte[] bytes) {
+ public void accept(
+ ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
// Nothing is actually received here as MockClass does not give rise to
// globals.
}
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Nothing to do, just checking we can override finished.
+ }
})
.build());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index d3816f8..b77f5a7 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -10,6 +10,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestCompileResult;
@@ -18,7 +19,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
@@ -72,7 +73,10 @@
@Test
public void testMergeDesugaredInputs() throws Exception {
- GlobalSyntheticsConsumerAndProvider globals1 = new GlobalSyntheticsConsumerAndProvider();
+ // TODO(b/231598779): Records do not yet have contexts so per-file modes fail.
+ // This test should be extended or duplicated to also test the pre-file-dex modes.
+ assumeTrue("b/230445931", parameters.isDexRuntime());
+ GlobalSyntheticsTestingConsumer globals1 = new GlobalSyntheticsTestingConsumer();
Path output1 =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA_1)
@@ -83,7 +87,7 @@
.inspect(this::assertDoesNotHaveRecordTag)
.writeToZip();
- GlobalSyntheticsConsumerAndProvider globals2 = new GlobalSyntheticsConsumerAndProvider();
+ GlobalSyntheticsTestingConsumer globals2 = new GlobalSyntheticsTestingConsumer();
Path output2 =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA_2)
@@ -94,13 +98,17 @@
.inspect(this::assertDoesNotHaveRecordTag)
.writeToZip();
- assertTrue(globals1.hasBytes());
- assertTrue(globals2.hasBytes());
+ assertTrue(globals1.hasGlobals());
+ assertTrue(globals2.hasGlobals());
D8TestCompileResult result =
testForD8(parameters.getBackend())
.addProgramFiles(output1, output2)
- .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1, globals2))
+ .apply(
+ b ->
+ b.getBuilder()
+ .addGlobalSyntheticsResourceProviders(
+ globals1.getIndexedModeProvider(), globals2.getIndexedModeProvider()))
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::assertHasRecordTag);
@@ -111,7 +119,7 @@
@Test
public void testMergeDesugaredAndNonDesugaredInputs() throws Exception {
- GlobalSyntheticsConsumerAndProvider globals1 = new GlobalSyntheticsConsumerAndProvider();
+ GlobalSyntheticsTestingConsumer globals1 = new GlobalSyntheticsTestingConsumer();
Path output1 =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA_1)
@@ -124,7 +132,8 @@
D8TestCompileResult result =
testForD8(parameters.getBackend())
.addProgramFiles(output1)
- .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1))
+ .apply(
+ b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1.getProviders()))
.addProgramClassFileData(PROGRAM_DATA_2)
.setMinApi(parameters.getApiLevel())
.compile();
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index e94b6b6..9a74bb5 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import java.nio.file.Path;
@@ -75,11 +75,14 @@
@Test
public void testD8Intermediate() throws Exception {
assumeTrue(parameters.isDexRuntime());
- GlobalSyntheticsConsumerAndProvider globals = new GlobalSyntheticsConsumerAndProvider();
+ GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
Path path = compileIntermediate(globals);
testForD8()
.addProgramFiles(path)
- .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals))
+ .apply(
+ b ->
+ b.getBuilder()
+ .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
.setMinApi(parameters.getApiLevel())
.setIncludeClassesChecksum(true)
.run(parameters.getRuntime(), MAIN_TYPE)
@@ -89,12 +92,15 @@
@Test
public void testD8IntermediateNoDesugaringInStep2() throws Exception {
assumeTrue(parameters.isDexRuntime());
- GlobalSyntheticsConsumerAndProvider globals = new GlobalSyntheticsConsumerAndProvider();
+ GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
Path path = compileIntermediate(globals);
// In Android Studio they disable desugaring at this point to improve build speed.
testForD8()
.addProgramFiles(path)
- .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals))
+ .apply(
+ b ->
+ b.getBuilder()
+ .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
.setMinApi(parameters.getApiLevel())
.setIncludeClassesChecksum(true)
.disableDesugaring()
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java
deleted file mode 100644
index 57f5622..0000000
--- a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2022, 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.synthesis.globals;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import com.android.tools.r8.GlobalSyntheticsConsumer;
-import com.android.tools.r8.GlobalSyntheticsResourceProvider;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.origin.Origin;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
-public class GlobalSyntheticsConsumerAndProvider
- implements GlobalSyntheticsConsumer, GlobalSyntheticsResourceProvider {
-
- private byte[] bytes;
-
- @Override
- public void accept(byte[] bytes) {
- assertNull(this.bytes);
- assertNotNull(bytes);
- this.bytes = bytes;
- }
-
- @Override
- public Origin getOrigin() {
- return Origin.unknown();
- }
-
- @Override
- public InputStream getByteStream() throws ResourceException {
- return new ByteArrayInputStream(bytes);
- }
-
- public boolean hasBytes() {
- return true;
- }
-}
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsTestingConsumer.java b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsTestingConsumer.java
new file mode 100644
index 0000000..51897e6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsTestingConsumer.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2022, 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.synthesis.globals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.GlobalSyntheticsResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class GlobalSyntheticsTestingConsumer implements GlobalSyntheticsConsumer {
+
+ private final Map<ClassReference, GlobalSyntheticsResourceProvider> globals = new HashMap<>();
+ private boolean finished = false;
+
+ @Override
+ public void accept(ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+ assertFalse(finished);
+ assertNotNull(data);
+ Origin origin =
+ context == null
+ ? Origin.unknown()
+ : new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "globals(" + context.getTypeName() + ")";
+ }
+ };
+ TestingProvider provider = new TestingProvider(origin, data.copyByteData());
+ GlobalSyntheticsResourceProvider old = globals.put(context, provider);
+ assertNull(old);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ assertFalse(finished);
+ finished = true;
+ }
+
+ public boolean hasGlobals() {
+ return !globals.isEmpty();
+ }
+
+ public boolean isSingleGlobal() {
+ return globals.size() == 1 && globals.get(null) != null;
+ }
+
+ public GlobalSyntheticsResourceProvider getIndexedModeProvider() {
+ assertTrue(isSingleGlobal());
+ return globals.get(null);
+ }
+
+ public GlobalSyntheticsResourceProvider getProvider(ClassReference clazz) {
+ assertNotNull("Use getIndexedModeProvider to get single outputs", clazz);
+ assertFalse(isSingleGlobal());
+ return globals.get(clazz);
+ }
+
+ public Collection<GlobalSyntheticsResourceProvider> getProviders() {
+ return globals.values();
+ }
+
+ private static class TestingProvider implements GlobalSyntheticsResourceProvider {
+
+ private final Origin origin;
+ private final byte[] bytes;
+
+ public TestingProvider(Origin origin, byte[] bytes) {
+ this.origin = origin;
+ this.bytes = bytes;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ return new ByteArrayInputStream(bytes);
+ }
+ }
+}