blob: 4fd254a43dfc213ec1516bdf02c4512b2d5ac1e3 [file] [log] [blame]
// 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.utils;
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.dex.Marker.Tool;
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.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
implements ProgramConsumer, ByteBufferProvider {
public static final String COMPILER_INFO_ENTRY_NAME = "compilerinfo";
public static final String OUTPUT_KIND_ENTRY_NAME = "kind";
// Builder for constructing a valid "globals" data payload.
private static class GlobalsFileBuilder {
private final Kind kind;
private final List<Pair<String, byte[]>> content = new ArrayList<>();
public GlobalsFileBuilder(Kind kind) {
this.kind = kind;
}
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;
}
}
public static class InternalGlobalSyntheticsDexIndexedConsumer
extends InternalGlobalSyntheticsProgramConsumer implements DexFilePerClassFileConsumer {
private final GlobalSyntheticsConsumer clientConsumer;
private final GlobalsFileBuilder builder = new GlobalsFileBuilder(Kind.DEX);
public InternalGlobalSyntheticsDexIndexedConsumer(GlobalSyntheticsConsumer clientConsumer) {
this.clientConsumer = clientConsumer;
}
@Override
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) {
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;
}
@Override
public void accept(
String primaryClassDescriptor,
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
addGlobal(primaryClassDescriptor, data);
}
@Override
public boolean combineSyntheticClassesWithPrimaryClass() {
return false;
}
}
public static class InternalGlobalSyntheticsCfConsumer extends PerFileBase
implements ClassFileConsumer {
public InternalGlobalSyntheticsCfConsumer(GlobalSyntheticsConsumer consumer, AppView appView) {
super(consumer, appView);
}
@Override
Kind getKind() {
return Kind.CF;
}
@Override
public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
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();
@Override
public final void finished(DiagnosticsHandler handler) {
throw new Unreachable("Unexpected call to non-internal finished.");
}
@Override
public void finished(AppView<?> appView) {
Map<DexType, Set<DexType>> globalsToContexts =
appView.getSyntheticItems().getFinalGlobalSyntheticContexts(appView);
// The global synthetics generator is generating the world of globals, thus no contexts exist.
if (appView.options().tool.equals(Tool.GlobalSyntheticsGenerator)) {
assert globalsToContexts.isEmpty();
GlobalsFileBuilder builder = new GlobalsFileBuilder(getKind());
globalToBytes.forEach(
(globalType, globalBytes) -> {
builder.addGlobalSynthetic(globalType.toDescriptorString(), globalBytes);
});
try {
clientConsumer.accept(ByteDataView.of(builder.build()), null, appView.reporter());
} catch (IOException e) {
appView.reporter().error(new ExceptionDiagnostic(e));
}
clientConsumer.finished(appView.reporter());
return;
}
// Otherwise, there must be at least one context for any global.
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);
assert 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);
}