Merge commit '39c661bbccaf9c252dde77da9d8a42724209d2f2' into dev-release
Change-Id: I57af52d498b045f5b3e1bf2e416dab16a6ae3bd9
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index d6185c6..5265c49 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -1,5 +1,5 @@
{
- "identifier": "com.tools.android:desugar_jdk_libs_configuration:2.1.1",
+ "identifier": "com.tools.android:desugar_jdk_libs_configuration:2.1.2",
"configuration_format_version": 101,
"required_compilation_api_level": 30,
"synthesized_library_classes_package_prefix": "j$.",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
index a576966..a3cd551 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
@@ -1,5 +1,5 @@
{
- "identifier": "com.tools.android:desugar_jdk_libs_configuration_minimal:2.1.1",
+ "identifier": "com.tools.android:desugar_jdk_libs_configuration_minimal:2.1.2",
"configuration_format_version": 101,
"required_compilation_api_level": 24,
"synthesized_library_classes_package_prefix": "j$.",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 19db3fa..05c530d 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -1,5 +1,5 @@
{
- "identifier": "com.tools.android:desugar_jdk_libs_configuration_nio:2.1.1",
+ "identifier": "com.tools.android:desugar_jdk_libs_configuration_nio:2.1.2",
"configuration_format_version": 101,
"required_compilation_api_level": 30,
"synthesized_library_classes_package_prefix": "j$.",
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 3c858a9..316020f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -449,7 +449,7 @@
options.reporter.failIfPendingErrors();
// Supply info to all additional resource consumers.
if (!(programConsumer instanceof ConvertedCfFiles)) {
- supplyAdditionalConsumers(appView);
+ supplyAdditionalConsumers(appView, virtualFiles);
}
} finally {
timing.end();
@@ -651,7 +651,7 @@
}
@SuppressWarnings("DefaultCharset")
- public static void supplyAdditionalConsumers(AppView<?> appView) {
+ public static void supplyAdditionalConsumers(AppView<?> appView, List<VirtualFile> virtualFiles) {
InternalOptions options = appView.options();
Reporter reporter = options.reporter;
appView.getArtProfileCollection().supplyConsumers(appView);
@@ -729,7 +729,7 @@
if (options.buildMetadataConsumer != null) {
assert appView.hasClassHierarchy();
options.buildMetadataConsumer.accept(
- BuildMetadataFactory.create(appView.withClassHierarchy()));
+ BuildMetadataFactory.create(appView.withClassHierarchy(), virtualFiles));
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index dc35e67..b58266c 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -80,6 +80,7 @@
private final DexString primaryClassDescriptor;
private final DexString primaryClassSynthesizingContextDescriptor;
private DebugRepresentation debugRepresentation;
+ private boolean startup = false;
VirtualFile(int id, AppView<?> appView) {
this(id, appView, null, null, StartupProfile.empty());
@@ -169,6 +170,14 @@
return debugRepresentation;
}
+ public void setStartup() {
+ startup = true;
+ }
+
+ public boolean isStartup() {
+ return startup;
+ }
+
public static String deriveCommonPrefixAndSanityCheck(List<String> fileNames) {
Iterator<String> nameIterator = fileNames.iterator();
String first = nameIterator.next();
@@ -1466,6 +1475,7 @@
boolean isSingleStartupDexFile = hasSpaceForTransaction(virtualFile, options);
if (isSingleStartupDexFile) {
virtualFile.commitTransaction();
+ virtualFile.setStartup();
} else {
virtualFile.abortTransaction();
@@ -1473,6 +1483,7 @@
MultiStartupDexDistributor distributor =
MultiStartupDexDistributor.get(options, startupProfile);
distributor.distribute(classPartioning.getStartupClasses(), this, virtualFile, cycler);
+ cycler.filesForDistribution.forEach(VirtualFile::setStartup);
options.reporter.warning(
createStartupClassesOverflowDiagnostic(cycler.filesForDistribution.size()));
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 b5b5299..20de02a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -62,6 +62,7 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
@@ -186,7 +187,7 @@
}
globalsConsumer.finished(appView);
}
- ApplicationWriter.supplyAdditionalConsumers(appView);
+ ApplicationWriter.supplyAdditionalConsumers(appView, Collections.emptyList());
}
private void writeClassCatchingErrors(
diff --git a/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java b/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
index be06792..486abda 100644
--- a/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
+++ b/src/main/java/com/android/tools/r8/metadata/BuildMetadataFactory.java
@@ -4,13 +4,24 @@
package com.android.tools.r8.metadata;
import com.android.tools.r8.Version;
+import com.android.tools.r8.dex.VirtualFile;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
public class BuildMetadataFactory {
- @SuppressWarnings("UnusedVariable")
- public static R8BuildMetadata create(AppView<? extends AppInfoWithClassHierarchy> appView) {
- return R8BuildMetadataImpl.builder().setVersion(Version.LABEL).build();
+ public static R8BuildMetadata create(
+ AppView<? extends AppInfoWithClassHierarchy> appView, List<VirtualFile> virtualFiles) {
+ InternalOptions options = appView.options();
+ return R8BuildMetadataImpl.builder()
+ .setOptions(new R8OptionsImpl(options))
+ .setBaselineProfileRewritingOptions(R8BaselineProfileRewritingOptionsImpl.create(options))
+ .setResourceOptimizationOptions(R8ResourceOptimizationOptionsImpl.create(options))
+ .setStartupOptimizationOptions(
+ R8StartupOptimizationOptionsImpl.create(options, virtualFiles))
+ .setVersion(Version.LABEL)
+ .build();
}
}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptions.java b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptions.java
new file mode 100644
index 0000000..7598aee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptions.java
@@ -0,0 +1,9 @@
+// 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8BaselineProfileRewritingOptions {}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptionsImpl.java
new file mode 100644
index 0000000..1585469
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8BaselineProfileRewritingOptionsImpl.java
@@ -0,0 +1,18 @@
+// 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.metadata;
+
+import com.android.tools.r8.utils.InternalOptions;
+
+public class R8BaselineProfileRewritingOptionsImpl implements R8BaselineProfileRewritingOptions {
+
+ private R8BaselineProfileRewritingOptionsImpl() {}
+
+ public static R8BaselineProfileRewritingOptionsImpl create(InternalOptions options) {
+ if (options.getArtProfileOptions().getArtProfilesForRewriting().isEmpty()) {
+ return null;
+ }
+ return new R8BaselineProfileRewritingOptionsImpl();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
index e186575..c5b0dd6 100644
--- a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
@@ -4,15 +4,52 @@
package com.android.tools.r8.metadata;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
-import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
@KeepForApi
public interface R8BuildMetadata {
static R8BuildMetadata fromJson(String json) {
- return new Gson().fromJson(json, R8BuildMetadataImpl.class);
+ return new GsonBuilder()
+ .excludeFieldsWithoutExposeAnnotation()
+ .registerTypeAdapter(R8Options.class, deserializeTo(R8OptionsImpl.class))
+ .registerTypeAdapter(
+ R8BaselineProfileRewritingOptions.class,
+ deserializeTo(R8BaselineProfileRewritingOptionsImpl.class))
+ .registerTypeAdapter(
+ R8KeepAttributesOptions.class, deserializeTo(R8KeepAttributesOptionsImpl.class))
+ .registerTypeAdapter(
+ R8ResourceOptimizationOptions.class,
+ deserializeTo(R8ResourceOptimizationOptionsImpl.class))
+ .registerTypeAdapter(
+ R8StartupOptimizationOptions.class,
+ deserializeTo(R8StartupOptimizationOptionsImpl.class))
+ .create()
+ .fromJson(json, R8BuildMetadataImpl.class);
}
+ private static <T> JsonDeserializer<T> deserializeTo(Class<T> implClass) {
+ return (element, type, context) -> context.deserialize(element, implClass);
+ }
+
+ R8Options getOptions();
+
+ /**
+ * @return null if baseline profile rewriting is disabled.
+ */
+ R8BaselineProfileRewritingOptions getBaselineProfileRewritingOptions();
+
+ /**
+ * @return null if resource optimization is disabled.
+ */
+ R8ResourceOptimizationOptions getResourceOptimizationOptions();
+
+ /**
+ * @return null if startup optimization is disabled.
+ */
+ R8StartupOptimizationOptions getStartupOptizationOptions();
+
String getVersion();
String toJson();
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java
index 1032696..d59db11 100644
--- a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadataImpl.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.UsedByReflection;
import com.google.gson.Gson;
+import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
@UsedByReflection(
@@ -20,10 +21,36 @@
fieldAnnotatedByClassConstant = SerializedName.class)
public class R8BuildMetadataImpl implements R8BuildMetadata {
+ @Expose
+ @SerializedName("options")
+ private final R8Options options;
+
+ @Expose
+ @SerializedName("baselineProfileRewritingOptions")
+ private final R8BaselineProfileRewritingOptions baselineProfileRewritingOptions;
+
+ @Expose
+ @SerializedName("resourceOptimizationOptions")
+ private final R8ResourceOptimizationOptions resourceOptimizationOptions;
+
+ @Expose
+ @SerializedName("startupOptimizationOptions")
+ private final R8StartupOptimizationOptions startupOptimizationOptions;
+
+ @Expose
@SerializedName("version")
private final String version;
- public R8BuildMetadataImpl(String version) {
+ public R8BuildMetadataImpl(
+ R8Options options,
+ R8BaselineProfileRewritingOptions baselineProfileRewritingOptions,
+ R8ResourceOptimizationOptions resourceOptimizationOptions,
+ R8StartupOptimizationOptions startupOptimizationOptions,
+ String version) {
+ this.options = options;
+ this.baselineProfileRewritingOptions = baselineProfileRewritingOptions;
+ this.resourceOptimizationOptions = resourceOptimizationOptions;
+ this.startupOptimizationOptions = startupOptimizationOptions;
this.version = version;
}
@@ -32,6 +59,26 @@
}
@Override
+ public R8Options getOptions() {
+ return options;
+ }
+
+ @Override
+ public R8BaselineProfileRewritingOptions getBaselineProfileRewritingOptions() {
+ return baselineProfileRewritingOptions;
+ }
+
+ @Override
+ public R8ResourceOptimizationOptions getResourceOptimizationOptions() {
+ return resourceOptimizationOptions;
+ }
+
+ @Override
+ public R8StartupOptimizationOptions getStartupOptizationOptions() {
+ return startupOptimizationOptions;
+ }
+
+ @Override
public String getVersion() {
return version;
}
@@ -43,15 +90,47 @@
public static class Builder {
+ private R8Options options;
+ private R8BaselineProfileRewritingOptions baselineProfileRewritingOptions;
+ private R8ResourceOptimizationOptions resourceOptimizationOptions;
+ private R8StartupOptimizationOptions startupOptimizationOptions;
private String version;
+ public Builder setOptions(R8Options options) {
+ this.options = options;
+ return this;
+ }
+
+ public Builder setBaselineProfileRewritingOptions(
+ R8BaselineProfileRewritingOptions baselineProfileRewritingOptions) {
+ this.baselineProfileRewritingOptions = baselineProfileRewritingOptions;
+ return this;
+ }
+
+ public Builder setResourceOptimizationOptions(
+ R8ResourceOptimizationOptions resourceOptimizationOptions) {
+ this.resourceOptimizationOptions = resourceOptimizationOptions;
+ return this;
+ }
+
+ public Builder setStartupOptimizationOptions(
+ R8StartupOptimizationOptions startupOptimizationOptions) {
+ this.startupOptimizationOptions = startupOptimizationOptions;
+ return this;
+ }
+
public Builder setVersion(String version) {
this.version = version;
return this;
}
public R8BuildMetadataImpl build() {
- return new R8BuildMetadataImpl(version);
+ return new R8BuildMetadataImpl(
+ options,
+ baselineProfileRewritingOptions,
+ resourceOptimizationOptions,
+ startupOptimizationOptions,
+ version);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptions.java b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptions.java
new file mode 100644
index 0000000..b590c7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptions.java
@@ -0,0 +1,48 @@
+// 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8KeepAttributesOptions {
+
+ boolean isAnnotationDefaultKept();
+
+ boolean isEnclosingMethodKept();
+
+ boolean isExceptionsKept();
+
+ boolean isInnerClassesKept();
+
+ boolean isLocalVariableTableKept();
+
+ boolean isLocalVariableTypeTableKept();
+
+ boolean isMethodParametersKept();
+
+ boolean isPermittedSubclassesKept();
+
+ boolean isRuntimeInvisibleAnnotationsKept();
+
+ boolean isRuntimeInvisibleParameterAnnotationsKept();
+
+ boolean isRuntimeInvisibleTypeAnnotationsKept();
+
+ boolean isRuntimeVisibleAnnotationsKept();
+
+ boolean isRuntimeVisibleParameterAnnotationsKept();
+
+ boolean isRuntimeVisibleTypeAnnotationsKept();
+
+ boolean isSignatureKept();
+
+ boolean isSourceDebugExtensionKept();
+
+ boolean isSourceDirKept();
+
+ boolean isSourceFileKept();
+
+ boolean isStackMapTableKept();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptionsImpl.java
new file mode 100644
index 0000000..3e12905
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8KeepAttributesOptionsImpl.java
@@ -0,0 +1,218 @@
+// 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+@UsedByReflection(
+ description = "Keep and preserve @SerializedName for correct (de)serialization",
+ constraints = {KeepConstraint.LOOKUP},
+ constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+ kind = KeepItemKind.CLASS_AND_FIELDS,
+ fieldAccess = {FieldAccessFlags.PRIVATE},
+ fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8KeepAttributesOptionsImpl implements R8KeepAttributesOptions {
+
+ @Expose
+ @SerializedName("isAnnotationDefaultKept")
+ private final boolean isAnnotationDefaultKept;
+
+ @Expose
+ @SerializedName("isEnclosingMethodKept")
+ private final boolean isEnclosingMethodKept;
+
+ @Expose
+ @SerializedName("isExceptionsKept")
+ private final boolean isExceptionsKept;
+
+ @Expose
+ @SerializedName("isInnerClassesKept")
+ private final boolean isInnerClassesKept;
+
+ @Expose
+ @SerializedName("isLocalVariableTableKept")
+ private final boolean isLocalVariableTableKept;
+
+ @Expose
+ @SerializedName("isLocalVariableTypeTableKept")
+ private final boolean isLocalVariableTypeTableKept;
+
+ @Expose
+ @SerializedName("isMethodParametersKept")
+ private final boolean isMethodParametersKept;
+
+ @Expose
+ @SerializedName("isPermittedSubclassesKept")
+ private final boolean isPermittedSubclassesKept;
+
+ @Expose
+ @SerializedName("isRuntimeInvisibleAnnotationsKept")
+ private final boolean isRuntimeInvisibleAnnotationsKept;
+
+ @Expose
+ @SerializedName("isRuntimeInvisibleParameterAnnotationsKept")
+ private final boolean isRuntimeInvisibleParameterAnnotationsKept;
+
+ @Expose
+ @SerializedName("isRuntimeInvisibleTypeAnnotationsKept")
+ private final boolean isRuntimeInvisibleTypeAnnotationsKept;
+
+ @Expose
+ @SerializedName("isRuntimeVisibleAnnotationsKept")
+ private final boolean isRuntimeVisibleAnnotationsKept;
+
+ @Expose
+ @SerializedName("isRuntimeVisibleParameterAnnotationsKept")
+ private final boolean isRuntimeVisibleParameterAnnotationsKept;
+
+ @Expose
+ @SerializedName("isRuntimeVisibleTypeAnnotationsKept")
+ private final boolean isRuntimeVisibleTypeAnnotationsKept;
+
+ @Expose
+ @SerializedName("isSignatureKept")
+ private final boolean isSignatureKept;
+
+ @Expose
+ @SerializedName("isSourceDebugExtensionKept")
+ private final boolean isSourceDebugExtensionKept;
+
+ @Expose
+ @SerializedName("isSourceDirKept")
+ private final boolean isSourceDirKept;
+
+ @Expose
+ @SerializedName("isSourceFileKept")
+ private final boolean isSourceFileKept;
+
+ @Expose
+ @SerializedName("isStackMapTableKept")
+ private final boolean isStackMapTableKept;
+
+ public R8KeepAttributesOptionsImpl(ProguardKeepAttributes keepAttributes) {
+ this.isAnnotationDefaultKept = keepAttributes.annotationDefault;
+ this.isEnclosingMethodKept = keepAttributes.enclosingMethod;
+ this.isExceptionsKept = keepAttributes.exceptions;
+ this.isInnerClassesKept = keepAttributes.innerClasses;
+ this.isLocalVariableTableKept = keepAttributes.localVariableTable;
+ this.isLocalVariableTypeTableKept = keepAttributes.localVariableTypeTable;
+ this.isMethodParametersKept = keepAttributes.methodParameters;
+ this.isPermittedSubclassesKept = keepAttributes.permittedSubclasses;
+ this.isRuntimeInvisibleAnnotationsKept = keepAttributes.runtimeInvisibleAnnotations;
+ this.isRuntimeInvisibleParameterAnnotationsKept =
+ keepAttributes.runtimeInvisibleParameterAnnotations;
+ this.isRuntimeInvisibleTypeAnnotationsKept = keepAttributes.runtimeInvisibleTypeAnnotations;
+ this.isRuntimeVisibleAnnotationsKept = keepAttributes.runtimeVisibleAnnotations;
+ this.isRuntimeVisibleParameterAnnotationsKept =
+ keepAttributes.runtimeVisibleParameterAnnotations;
+ this.isRuntimeVisibleTypeAnnotationsKept = keepAttributes.runtimeVisibleTypeAnnotations;
+ this.isSignatureKept = keepAttributes.signature;
+ this.isSourceDebugExtensionKept = keepAttributes.sourceDebugExtension;
+ this.isSourceDirKept = keepAttributes.sourceDir;
+ this.isSourceFileKept = keepAttributes.sourceFile;
+ this.isStackMapTableKept = keepAttributes.stackMapTable;
+ }
+
+ @Override
+ public boolean isAnnotationDefaultKept() {
+ return isAnnotationDefaultKept;
+ }
+
+ @Override
+ public boolean isEnclosingMethodKept() {
+ return isEnclosingMethodKept;
+ }
+
+ @Override
+ public boolean isExceptionsKept() {
+ return isExceptionsKept;
+ }
+
+ @Override
+ public boolean isInnerClassesKept() {
+ return isInnerClassesKept;
+ }
+
+ @Override
+ public boolean isLocalVariableTableKept() {
+ return isLocalVariableTableKept;
+ }
+
+ @Override
+ public boolean isLocalVariableTypeTableKept() {
+ return isLocalVariableTypeTableKept;
+ }
+
+ @Override
+ public boolean isMethodParametersKept() {
+ return isMethodParametersKept;
+ }
+
+ @Override
+ public boolean isPermittedSubclassesKept() {
+ return isPermittedSubclassesKept;
+ }
+
+ @Override
+ public boolean isRuntimeInvisibleAnnotationsKept() {
+ return isRuntimeInvisibleAnnotationsKept;
+ }
+
+ @Override
+ public boolean isRuntimeInvisibleParameterAnnotationsKept() {
+ return isRuntimeInvisibleParameterAnnotationsKept;
+ }
+
+ @Override
+ public boolean isRuntimeInvisibleTypeAnnotationsKept() {
+ return isRuntimeInvisibleTypeAnnotationsKept;
+ }
+
+ @Override
+ public boolean isRuntimeVisibleAnnotationsKept() {
+ return isRuntimeVisibleAnnotationsKept;
+ }
+
+ @Override
+ public boolean isRuntimeVisibleParameterAnnotationsKept() {
+ return isRuntimeVisibleParameterAnnotationsKept;
+ }
+
+ @Override
+ public boolean isRuntimeVisibleTypeAnnotationsKept() {
+ return isRuntimeVisibleTypeAnnotationsKept;
+ }
+
+ @Override
+ public boolean isSignatureKept() {
+ return isSignatureKept;
+ }
+
+ @Override
+ public boolean isSourceDebugExtensionKept() {
+ return isSourceDebugExtensionKept;
+ }
+
+ @Override
+ public boolean isSourceDirKept() {
+ return isSourceDirKept;
+ }
+
+ @Override
+ public boolean isSourceFileKept() {
+ return isSourceFileKept;
+ }
+
+ @Override
+ public boolean isStackMapTableKept() {
+ return isStackMapTableKept;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8Options.java b/src/main/java/com/android/tools/r8/metadata/R8Options.java
new file mode 100644
index 0000000..42fe2fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8Options.java
@@ -0,0 +1,29 @@
+// 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8Options {
+
+ /**
+ * @return null if no ProGuard configuration is provided.
+ */
+ R8KeepAttributesOptions getKeepAttributesOptions();
+
+ int getMinApiLevel();
+
+ boolean isAccessModificationEnabled();
+
+ boolean isDebugModeEnabled();
+
+ boolean isProGuardCompatibilityModeEnabled();
+
+ boolean isObfuscationEnabled();
+
+ boolean isOptimizationsEnabled();
+
+ boolean isShrinkingEnabled();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8OptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8OptionsImpl.java
new file mode 100644
index 0000000..a65dfc7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8OptionsImpl.java
@@ -0,0 +1,110 @@
+// 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+@UsedByReflection(
+ description = "Keep and preserve @SerializedName for correct (de)serialization",
+ constraints = {KeepConstraint.LOOKUP},
+ constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+ kind = KeepItemKind.CLASS_AND_FIELDS,
+ fieldAccess = {FieldAccessFlags.PRIVATE},
+ fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8OptionsImpl implements R8Options {
+
+ @Expose
+ @SerializedName("keepAttributesOptions")
+ private final R8KeepAttributesOptions keepAttributesOptions;
+
+ @Expose
+ @SerializedName("minApiLevel")
+ private final int minApiLevel;
+
+ @Expose
+ @SerializedName("isAccessModificationEnabled")
+ private final boolean isAccessModificationEnabled;
+
+ @Expose
+ @SerializedName("isDebugModeEnabled")
+ private final boolean isDebugModeEnabled;
+
+ @Expose
+ @SerializedName("isProGuardCompatibilityModeEnabled")
+ private final boolean isProGuardCompatibilityModeEnabled;
+
+ @Expose
+ @SerializedName("isObfuscationEnabled")
+ private final boolean isObfuscationEnabled;
+
+ @Expose
+ @SerializedName("isOptimizationsEnabled")
+ private final boolean isOptimizationsEnabled;
+
+ @Expose
+ @SerializedName("isShrinkingEnabled")
+ private final boolean isShrinkingEnabled;
+
+ public R8OptionsImpl(InternalOptions options) {
+ this.keepAttributesOptions =
+ options.hasProguardConfiguration()
+ ? new R8KeepAttributesOptionsImpl(
+ options.getProguardConfiguration().getKeepAttributes())
+ : null;
+ this.minApiLevel = options.getMinApiLevel().getLevel();
+ this.isAccessModificationEnabled = options.isAccessModificationEnabled();
+ this.isDebugModeEnabled = options.debug;
+ this.isProGuardCompatibilityModeEnabled = options.forceProguardCompatibility;
+ this.isObfuscationEnabled = options.isMinifying();
+ this.isOptimizationsEnabled = options.isOptimizing();
+ this.isShrinkingEnabled = options.isShrinking();
+ }
+
+ @Override
+ public R8KeepAttributesOptions getKeepAttributesOptions() {
+ return keepAttributesOptions;
+ }
+
+ @Override
+ public int getMinApiLevel() {
+ return minApiLevel;
+ }
+
+ @Override
+ public boolean isAccessModificationEnabled() {
+ return isAccessModificationEnabled;
+ }
+
+ @Override
+ public boolean isDebugModeEnabled() {
+ return isDebugModeEnabled;
+ }
+
+ @Override
+ public boolean isProGuardCompatibilityModeEnabled() {
+ return isProGuardCompatibilityModeEnabled;
+ }
+
+ @Override
+ public boolean isObfuscationEnabled() {
+ return isObfuscationEnabled;
+ }
+
+ @Override
+ public boolean isOptimizationsEnabled() {
+ return isOptimizationsEnabled;
+ }
+
+ @Override
+ public boolean isShrinkingEnabled() {
+ return isShrinkingEnabled;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptions.java b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptions.java
new file mode 100644
index 0000000..693286e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptions.java
@@ -0,0 +1,12 @@
+// 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8ResourceOptimizationOptions {
+
+ boolean isOptimizedShrinkingEnabled();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptionsImpl.java
new file mode 100644
index 0000000..c9cc341
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8ResourceOptimizationOptionsImpl.java
@@ -0,0 +1,45 @@
+// 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.metadata;
+
+import com.android.tools.r8.ResourceShrinkerConfiguration;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+@UsedByReflection(
+ description = "Keep and preserve @SerializedName for correct (de)serialization",
+ constraints = {KeepConstraint.LOOKUP},
+ constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+ kind = KeepItemKind.CLASS_AND_FIELDS,
+ fieldAccess = {FieldAccessFlags.PRIVATE},
+ fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8ResourceOptimizationOptionsImpl implements R8ResourceOptimizationOptions {
+
+ @Expose
+ @SerializedName("isOptimizedShrinkingEnabled")
+ private final boolean isOptimizedShrinkingEnabled;
+
+ private R8ResourceOptimizationOptionsImpl(
+ ResourceShrinkerConfiguration resourceShrinkerConfiguration) {
+ this.isOptimizedShrinkingEnabled = resourceShrinkerConfiguration.isOptimizedShrinking();
+ }
+
+ public static R8ResourceOptimizationOptionsImpl create(InternalOptions options) {
+ if (options.androidResourceProvider == null) {
+ return null;
+ }
+ return new R8ResourceOptimizationOptionsImpl(options.resourceShrinkerConfiguration);
+ }
+
+ @Override
+ public boolean isOptimizedShrinkingEnabled() {
+ return isOptimizedShrinkingEnabled;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptions.java b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptions.java
new file mode 100644
index 0000000..a57b01e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptions.java
@@ -0,0 +1,12 @@
+// 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8StartupOptimizationOptions {
+
+ int getNumberOfStartupDexFiles();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptionsImpl.java b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptionsImpl.java
new file mode 100644
index 0000000..07b0a0f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8StartupOptimizationOptionsImpl.java
@@ -0,0 +1,47 @@
+// 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.metadata;
+
+import com.android.tools.r8.dex.VirtualFile;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+@UsedByReflection(
+ description = "Keep and preserve @SerializedName for correct (de)serialization",
+ constraints = {KeepConstraint.LOOKUP},
+ constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+ kind = KeepItemKind.CLASS_AND_FIELDS,
+ fieldAccess = {FieldAccessFlags.PRIVATE},
+ fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8StartupOptimizationOptionsImpl implements R8StartupOptimizationOptions {
+
+ @Expose
+ @SerializedName("numberOfStartupDexFiles")
+ private final int numberOfStartupDexFiles;
+
+ public R8StartupOptimizationOptionsImpl(List<VirtualFile> virtualFiles) {
+ this.numberOfStartupDexFiles =
+ (int) virtualFiles.stream().filter(VirtualFile::isStartup).count();
+ }
+
+ public static R8StartupOptimizationOptionsImpl create(
+ InternalOptions options, List<VirtualFile> virtualFiles) {
+ if (options.getStartupOptions().getStartupProfileProviders().isEmpty()) {
+ return null;
+ }
+ return new R8StartupOptimizationOptionsImpl(virtualFiles);
+ }
+
+ @Override
+ public int getNumberOfStartupDexFiles() {
+ return numberOfStartupDexFiles;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
index 564ef5f..db497a7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
@@ -222,7 +222,7 @@
if (!ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, argument.getIndex())) {
return;
}
- if (!argumentValue.getType().isClassType() || argumentValue.hasDebugUsers()) {
+ if (argumentValue.hasDebugUsers()) {
return;
}
Value usedValue;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index 445e0b0..ce5e106 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -56,15 +56,7 @@
public boolean stackMapTable = false;
public boolean permittedSubclasses = false;
- private ProguardKeepAttributes() {
- }
-
- public static ProguardKeepAttributes filterOnlySignatures() {
- ProguardKeepAttributes result = new ProguardKeepAttributes();
- result.applyPatterns(KEEP_ALL);
- result.signature = false;
- return result;
- }
+ private ProguardKeepAttributes() {}
/**
* Implements ProGuards attribute matching rules.
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 44c289f..32658ea 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -704,6 +704,9 @@
if (desugarGraphConsumer != null) {
desugarGraphConsumer.finished();
}
+ if (resourceShrinkerConfiguration.getDebugConsumer() != null) {
+ resourceShrinkerConfiguration.getDebugConsumer().finished(reporter);
+ }
}
public boolean shouldDesugarNests() {
diff --git a/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java b/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
index 47742f0..d9ad4ff 100644
--- a/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
@@ -88,12 +88,16 @@
return new ShrinkerDebugReporter() {
@Override
public void debug(Supplier<String> logSupplier) {
- consumer.accept(logSupplier.get(), diagnosticsHandler);
+ // The default usage of shrinkerdebug in the legacy resource shrinker does not add
+ // new lines. Add these to make it consistent with the normal usage of StringConsumer.
+ consumer.accept(logSupplier.get() + "\n", diagnosticsHandler);
}
@Override
public void info(Supplier<String> logProducer) {
- consumer.accept(logProducer.get(), diagnosticsHandler);
+ // The default usage of shrinkerdebug in the legacy resource shrinker does not add
+ // new lines. Add these to make it consistent with the normal usage of StringConsumer.
+ consumer.accept(logProducer.get() + "\n", diagnosticsHandler);
}
@Override
diff --git a/src/test/examplesJava17/records/EmptyRecord.java b/src/test/examplesJava17/records/EmptyRecord.java
deleted file mode 100644
index 5d16e44..0000000
--- a/src/test/examplesJava17/records/EmptyRecord.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) 2020, 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 records;
-
-public class EmptyRecord {
-
- record Empty() {}
-
- public static void main(String[] args) {
- System.out.println(new Empty());
- }
-}
diff --git a/src/test/examplesJava17/records/EmptyRecordAnnotation.java b/src/test/examplesJava17/records/EmptyRecordAnnotation.java
deleted file mode 100644
index 350e8ce..0000000
--- a/src/test/examplesJava17/records/EmptyRecordAnnotation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2021, 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 records;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-public class EmptyRecordAnnotation {
-
- record Empty() {}
-
- @Retention(RetentionPolicy.RUNTIME)
- @interface ClassAnnotation {
- Class<? extends Record> theClass();
- }
-
- @ClassAnnotation(theClass = Record.class)
- public static void annotatedMethod1() {}
-
- @ClassAnnotation(theClass = Empty.class)
- public static void annotatedMethod2() {}
-
- public static void main(String[] args) throws Exception {
- Class<?> annotatedMethod1Content =
- EmptyRecordAnnotation.class
- .getDeclaredMethod("annotatedMethod1")
- .getAnnotation(ClassAnnotation.class)
- .theClass();
- System.out.println(annotatedMethod1Content);
- Class<?> annotatedMethod2Content =
- EmptyRecordAnnotation.class
- .getDeclaredMethod("annotatedMethod2")
- .getAnnotation(ClassAnnotation.class)
- .theClass();
- System.out.println(annotatedMethod2Content);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java b/src/test/examplesJava17/records/EmptyRecordAnnotationTest.java
similarity index 64%
rename from src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
rename to src/test/examplesJava17/records/EmptyRecordAnnotationTest.java
index 84f3ea9..1759f97 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
+++ b/src/test/examplesJava17/records/EmptyRecordAnnotationTest.java
@@ -2,14 +2,16 @@
// 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.records;
+package records;
-
+import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.utils.StringUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -17,14 +19,11 @@
@RunWith(Parameterized.class)
public class EmptyRecordAnnotationTest extends TestBase {
- private static final String RECORD_NAME = "EmptyRecordAnnotation";
- private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
- private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT_NATIVE_OR_PARTIALLY_DESUGARED_RECORD =
- StringUtils.lines("class java.lang.Record", "class records.EmptyRecordAnnotation$Empty");
+ StringUtils.lines("class java.lang.Record", "class records.EmptyRecordAnnotationTest$Empty");
private static final String EXPECTED_RESULT_DESUGARED_RECORD =
StringUtils.lines(
- "class com.android.tools.r8.RecordTag", "class records.EmptyRecordAnnotation$Empty");
+ "class com.android.tools.r8.RecordTag", "class records.EmptyRecordAnnotationTest$Empty");
private final TestParameters parameters;
@@ -45,8 +44,8 @@
public void testJvm() throws Exception {
parameters.assumeJvmTestParameters();
testForJvm(parameters)
- .addProgramClassFileData(PROGRAM_DATA)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_OR_PARTIALLY_DESUGARED_RECORD);
}
@@ -54,10 +53,10 @@
public void testD8() throws Exception {
parameters.assumeDexRuntime();
testForD8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
.compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), TestClass.class)
.applyIf(
isRecordsFullyDesugaredForD8(parameters),
r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD),
@@ -68,23 +67,54 @@
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
testForR8(parameters.getBackend())
- .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .addProgramClassFileData(PROGRAM_DATA)
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
- .addKeepRules("-keep class records.EmptyRecordAnnotation { *; }")
+ .addKeepRules("-keep class records.EmptyRecordAnnotationTest$TestClass { *; }")
.addKeepRules("-keepattributes *Annotation*")
- .addKeepRules("-keep class records.EmptyRecordAnnotation$Empty")
- .addKeepMainRule(MAIN_TYPE)
+ .addKeepRules("-keep class records.EmptyRecordAnnotationTest$Empty")
+ .addKeepMainRule(TestClass.class)
// This is used to avoid renaming com.android.tools.r8.RecordTag.
.applyIf(
isRecordsFullyDesugaredForR8(parameters),
b -> b.addKeepRules("-keep class java.lang.Record"))
.compile()
.applyIf(parameters.isCfRuntime(), r -> r.inspect(RecordTestUtils::assertRecordsAreRecords))
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), TestClass.class)
.applyIf(
isRecordsFullyDesugaredForR8(parameters),
r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED_RECORD),
r -> r.assertSuccessWithOutput(EXPECTED_RESULT_NATIVE_OR_PARTIALLY_DESUGARED_RECORD));
}
+
+ record Empty() {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ClassAnnotation {
+ Class<? extends Record> theClass();
+ }
+
+ public class TestClass {
+
+ @ClassAnnotation(theClass = Record.class)
+ public static void annotatedMethod1() {}
+
+ @ClassAnnotation(theClass = Empty.class)
+ public static void annotatedMethod2() {}
+
+ public static void main(String[] args) throws Exception {
+ Class<?> annotatedMethod1Content =
+ TestClass.class
+ .getDeclaredMethod("annotatedMethod1")
+ .getAnnotation(ClassAnnotation.class)
+ .theClass();
+ System.out.println(annotatedMethod1Content);
+ Class<?> annotatedMethod2Content =
+ TestClass.class
+ .getDeclaredMethod("annotatedMethod2")
+ .getAnnotation(ClassAnnotation.class)
+ .theClass();
+ System.out.println(annotatedMethod2Content);
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/examplesJava17/records/EmptyRecordTest.java
similarity index 78%
rename from src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
rename to src/test/examplesJava17/records/EmptyRecordTest.java
index 9c88c93..963a514 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/examplesJava17/records/EmptyRecordTest.java
@@ -2,10 +2,11 @@
// 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.records;
+package records;
import static org.junit.Assume.assumeFalse;
+import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
@@ -21,13 +22,10 @@
@RunWith(Parameterized.class)
public class EmptyRecordTest extends TestBase {
- private static final String RECORD_NAME = "EmptyRecord";
- private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
- private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT_D8 = StringUtils.lines("Empty[]");
private static final String EXPECTED_RESULT_R8_MINIFICATION = StringUtils.lines("a[]");
private static final String EXPECTED_RESULT_R8_NO_MINIFICATION =
- StringUtils.lines("EmptyRecord$Empty[]");
+ StringUtils.lines("EmptyRecordTest$Empty[]");
@Parameter(0)
public boolean enableMinification;
@@ -55,8 +53,8 @@
assumeFalse("Only applicable for R8", enableMinification);
parameters.assumeJvmTestParameters();
testForJvm(parameters)
- .addProgramClassFileData(PROGRAM_DATA)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED_RESULT_D8);
}
@@ -64,10 +62,10 @@
public void testD8() throws Exception {
assumeFalse("Only applicable for R8", enableMinification || enableRepackaging);
testForD8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
.compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED_RESULT_D8);
}
@@ -76,11 +74,11 @@
parameters.assumeDexRuntime();
parameters.assumeR8TestParameters();
testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .addKeepMainRule(MAIN_TYPE)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addKeepMainRule(TestClass.class)
.applyIf(
parameters.isCfRuntime(),
- testBuilder -> testBuilder.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)))
+ testBuilder -> testBuilder.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
.addDontObfuscateUnless(enableMinification)
.applyIf(enableRepackaging, b -> b.addKeepRules("-repackageclasses p"))
.setMinApi(parameters)
@@ -88,10 +86,19 @@
.applyIf(
parameters.isCfRuntime(),
compileResult -> compileResult.inspect(RecordTestUtils::assertRecordsAreRecords))
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(
enableMinification
? EXPECTED_RESULT_R8_MINIFICATION
: EXPECTED_RESULT_R8_NO_MINIFICATION);
}
+
+ record Empty() {}
+
+ public class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Empty());
+ }
+ }
}
diff --git a/src/test/examplesJava17/records/RecordInstanceOf.java b/src/test/examplesJava17/records/RecordInstanceOf.java
deleted file mode 100644
index 591192c..0000000
--- a/src/test/examplesJava17/records/RecordInstanceOf.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2020, 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 records;
-
-public class RecordInstanceOf {
-
- record Empty() {}
-
- record Person(String name, int age) {}
-
- public static void main(String[] args) {
- Empty empty = new Empty();
- Person janeDoe = new Person("Jane Doe", 42);
- Object o = new Object();
- System.out.println(janeDoe instanceof java.lang.Record);
- System.out.println(empty instanceof java.lang.Record);
- System.out.println(o instanceof java.lang.Record);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/examplesJava17/records/RecordInstanceOfTest.java
similarity index 64%
rename from src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
rename to src/test/examplesJava17/records/RecordInstanceOfTest.java
index 301210b..0fdb15f 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/examplesJava17/records/RecordInstanceOfTest.java
@@ -2,8 +2,9 @@
// 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.records;
+package records;
+import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -17,9 +18,6 @@
@RunWith(Parameterized.class)
public class RecordInstanceOfTest extends TestBase {
- private static final String RECORD_NAME = "RecordInstanceOf";
- private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
- private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT = StringUtils.lines("true", "true", "false");
private final TestParameters parameters;
@@ -41,18 +39,18 @@
public void testJvm() throws Exception {
parameters.assumeJvmTestParameters();
testForJvm(parameters)
- .addProgramClassFileData(PROGRAM_DATA)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testD8() throws Exception {
testForD8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
.compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -61,18 +59,34 @@
parameters.assumeR8TestParameters();
R8FullTestBuilder builder =
testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
- .addKeepMainRule(MAIN_TYPE);
+ .addKeepMainRule(TestClass.class);
if (parameters.isCfRuntime()) {
builder
- .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+ builder.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ record Empty() {}
+
+ record Person(String name, int age) {}
+
+ public class TestClass {
+
+ public static void main(String[] args) {
+ Empty empty = new Empty();
+ Person janeDoe = new Person("Jane Doe", 42);
+ Object o = new Object();
+ System.out.println(janeDoe instanceof java.lang.Record);
+ System.out.println(empty instanceof java.lang.Record);
+ System.out.println(o instanceof java.lang.Record);
+ }
}
}
diff --git a/src/test/examplesJava17/records/RecordTestUtils.java b/src/test/examplesJava17/records/RecordTestUtils.java
new file mode 100644
index 0000000..6c501c0
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordTestUtils.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, 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 records;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+
+public class RecordTestUtils {
+
+ public static void assertRecordsAreRecords(CodeInspector inspector) {
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.getDexProgramClass().superType.toString().equals("java.lang.Record")) {
+ assertTrue(clazz.getDexProgramClass().isRecord());
+ }
+ }
+ }
+
+ public static void assertNoJavaLangRecord(CodeInspector inspector, TestParameters parameters) {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.V)) {
+ assertFalse(inspector.clazz("java.lang.RecordTag").isPresent());
+ } else {
+ assertFalse(inspector.clazz("java.lang.Record").isPresent());
+ }
+ }
+}
diff --git a/src/test/examplesJava17/records/UnusedRecordField.java b/src/test/examplesJava17/records/UnusedRecordField.java
deleted file mode 100644
index 1d184cd..0000000
--- a/src/test/examplesJava17/records/UnusedRecordField.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2021, 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 records;
-
-public class UnusedRecordField {
-
- Record unusedInstanceField;
-
- void printHello() {
- System.out.println("Hello!");
- }
-
- public static void main(String[] args) {
- new UnusedRecordField().printHello();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java b/src/test/examplesJava17/records/UnusedRecordFieldTest.java
similarity index 67%
rename from src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
rename to src/test/examplesJava17/records/UnusedRecordFieldTest.java
index e845260..0de5cba 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
+++ b/src/test/examplesJava17/records/UnusedRecordFieldTest.java
@@ -2,8 +2,9 @@
// 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.records;
+package records;
+import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -18,9 +19,6 @@
@RunWith(Parameterized.class)
public class UnusedRecordFieldTest extends TestBase {
- private static final String RECORD_NAME = "UnusedRecordField";
- private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
- private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT = StringUtils.lines("Hello!");
@Parameter(0)
@@ -39,18 +37,18 @@
public void testJvm() throws Exception {
parameters.assumeJvmTestParameters();
testForJvm(parameters)
- .addProgramClassFileData(PROGRAM_DATA)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), UnusedRecordField.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
- }
+ }
@Test
public void testD8() throws Exception {
testForD8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
.compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), UnusedRecordField.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -59,19 +57,34 @@
parameters.assumeR8TestParameters();
R8FullTestBuilder builder =
testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
- .addKeepRules("-keep class records.UnusedRecordField { *; }")
- .addKeepMainRule(MAIN_TYPE);
+ .addKeepRules("-keep class records.UnusedRecordFieldTest$UnusedRecordField { *; }")
+ .addKeepMainRule(UnusedRecordField.class);
if (parameters.isCfRuntime()) {
builder
- .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), UnusedRecordField.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+ builder
+ .run(parameters.getRuntime(), UnusedRecordField.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class UnusedRecordField {
+
+ Record unusedInstanceField;
+
+ void printHello() {
+ System.out.println("Hello!");
+ }
+
+ public static void main(String[] args) {
+ new UnusedRecordField().printHello();
+ }
}
}
diff --git a/src/test/examplesJava17/records/UnusedRecordMethod.java b/src/test/examplesJava17/records/UnusedRecordMethod.java
deleted file mode 100644
index 342b178..0000000
--- a/src/test/examplesJava17/records/UnusedRecordMethod.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2021, 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 records;
-
-public class UnusedRecordMethod {
-
- Record unusedInstanceMethod(Record unused) {
- return null;
- }
-
- void printHello() {
- System.out.println("Hello!");
- }
-
- public static void main(String[] args) {
- new UnusedRecordMethod().printHello();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java b/src/test/examplesJava17/records/UnusedRecordMethodTest.java
similarity index 66%
rename from src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
rename to src/test/examplesJava17/records/UnusedRecordMethodTest.java
index e78d25b..9f8bd26 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
+++ b/src/test/examplesJava17/records/UnusedRecordMethodTest.java
@@ -2,8 +2,9 @@
// 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.records;
+package records;
+import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -19,9 +20,6 @@
@RunWith(Parameterized.class)
public class UnusedRecordMethodTest extends TestBase {
- private static final String RECORD_NAME = "UnusedRecordMethod";
- private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
- private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT = StringUtils.lines("Hello!");
@Parameter(0)
@@ -40,18 +38,18 @@
public void testJvm() throws Exception {
parameters.assumeJvmTestParameters();
testForJvm(parameters)
- .addProgramClassFileData(PROGRAM_DATA)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), UnusedRecordMethod.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
- }
+ }
@Test
public void testD8() throws Exception {
testForD8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
.compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), UnusedRecordMethod.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@@ -60,19 +58,36 @@
parameters.assumeR8TestParameters();
R8FullTestBuilder builder =
testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
+ .addInnerClassesAndStrippedOuter(getClass())
.setMinApi(parameters)
- .addKeepRules("-keep class records.UnusedRecordMethod { *; }")
- .addKeepMainRule(MAIN_TYPE);
+ .addKeepRules("-keep class records.UnusedRecordMethodTest$UnusedRecordMethod { *; }")
+ .addKeepMainRule(UnusedRecordMethod.class);
if (parameters.isCfRuntime()) {
builder
- .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
.compile()
.inspect(RecordTestUtils::assertRecordsAreRecords)
- .run(parameters.getRuntime(), MAIN_TYPE)
+ .run(parameters.getRuntime(), UnusedRecordMethod.class)
.assertSuccessWithOutput(EXPECTED_RESULT);
return;
}
- builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+ builder
+ .run(parameters.getRuntime(), UnusedRecordMethod.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class UnusedRecordMethod {
+
+ Record unusedInstanceMethod(Record unused) {
+ return null;
+ }
+
+ void printHello() {
+ System.out.println("Hello!");
+ }
+
+ public static void main(String[] args) {
+ new UnusedRecordMethod().printHello();
+ }
}
}
diff --git a/src/test/examplesJava17/records/UnusedRecordReflection.java b/src/test/examplesJava17/records/UnusedRecordReflection.java
deleted file mode 100644
index 1a4891b..0000000
--- a/src/test/examplesJava17/records/UnusedRecordReflection.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2021, 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 records;
-
-import java.lang.reflect.Method;
-
-public class UnusedRecordReflection {
-
- Record instanceField;
-
- Record method(int i, Record unused, int j) {
- return null;
- }
-
- Object reflectiveGetField() {
- try {
- return this.getClass().getDeclaredField("instanceField").get(this);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- Object reflectiveCallMethod() {
- try {
- for (Method declaredMethod : this.getClass().getDeclaredMethods()) {
- if (declaredMethod.getName().equals("method")) {
- return declaredMethod.invoke(this, 0, null, 1);
- }
- }
- throw new RuntimeException("Unreachable");
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static void main(String[] args) {
- System.out.println(new UnusedRecordReflection().reflectiveGetField());
- System.out.println(new UnusedRecordReflection().reflectiveCallMethod());
- }
-}
diff --git a/src/test/examplesJava17/records/UnusedRecordReflectionTest.java b/src/test/examplesJava17/records/UnusedRecordReflectionTest.java
new file mode 100644
index 0000000..2caa702
--- /dev/null
+++ b/src/test/examplesJava17/records/UnusedRecordReflectionTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2021, 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 records;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.reflect.Method;
+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 UnusedRecordReflectionTest extends TestBase {
+
+ private static final String EXPECTED_RESULT = StringUtils.lines("null", "null");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build();
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), UnusedRecordReflection.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), UnusedRecordReflection.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .setMinApi(parameters)
+ .addKeepRules(
+ "-keep class records.UnusedRecordReflectionTest$UnusedRecordReflection { *; }")
+ .addKeepMainRule(UnusedRecordReflection.class);
+ if (parameters.isCfRuntime()) {
+ builder
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .run(parameters.getRuntime(), UnusedRecordReflection.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ builder
+ .run(parameters.getRuntime(), UnusedRecordReflection.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class UnusedRecordReflection {
+
+ Record instanceField;
+
+ Record method(int i, Record unused, int j) {
+ return null;
+ }
+
+ Object reflectiveGetField() {
+ try {
+ return this.getClass().getDeclaredField("instanceField").get(this);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Object reflectiveCallMethod() {
+ try {
+ for (Method declaredMethod : this.getClass().getDeclaredMethods()) {
+ if (declaredMethod.getName().equals("method")) {
+ return declaredMethod.invoke(this, 0, null, 1);
+ }
+ }
+ throw new RuntimeException("Unreachable");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(new UnusedRecordReflection().reflectiveGetField());
+ System.out.println(new UnusedRecordReflection().reflectiveCallMethod());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
index 5ed2e09..1028b38 100644
--- a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
@@ -6,10 +6,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
@@ -47,6 +50,7 @@
@Test
public void testR8() throws Exception {
StringBuilder log = new StringBuilder();
+ BooleanBox finished = new BooleanBox(false);
testForR8(parameters.getBackend())
.setMinApi(parameters)
.addProgramClasses(FooBar.class)
@@ -59,7 +63,17 @@
configurationBuilder.enableOptimizedShrinkingWithR8();
}
configurationBuilder.setDebugConsumer(
- (string, handler) -> log.append(string + "\n"));
+ new StringConsumer() {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ log.append(string);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ finished.set(true);
+ }
+ });
return configurationBuilder.build();
}))
.addAndroidResources(getTestResources(temp))
@@ -79,6 +93,7 @@
.assertSuccess();
// TODO(b/360284664): Add (non compatible) logging for optimized shrinking
if (!optimized) {
+ assertTrue(finished.get());
// Consistent with the old AGP embedded shrinker
List<String> strings = StringUtils.splitLines(log.toString());
// string:bar reachable from code
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index 4e2a4c2..e3612a0 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.benchmarks.appdumps.NowInAndroidBenchmarks;
import com.android.tools.r8.benchmarks.appdumps.TiviBenchmarks;
import com.android.tools.r8.benchmarks.desugaredlib.L8Benchmark;
-import com.android.tools.r8.benchmarks.desugaredlib.LegacyDesugaredLibraryBenchmark;
import com.android.tools.r8.benchmarks.helloworld.HelloWorldBenchmark;
import com.android.tools.r8.benchmarks.retrace.RetraceStackTraceBenchmark;
import java.io.IOException;
@@ -64,7 +63,6 @@
// Every benchmark that should be active on golem must be setup in this method.
return new BenchmarkCollection(
HelloWorldBenchmark.configs(),
- LegacyDesugaredLibraryBenchmark.configs(),
L8Benchmark.configs(),
NowInAndroidBenchmarks.configs(),
TiviBenchmarks.configs(),
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
index 7553ea8..6a6140a 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
@@ -242,6 +242,10 @@
printer.accept(size);
}
+ public int size() {
+ return runtimeResults.size();
+ }
+
@Override
public void writeResults(PrintStream out) {
Gson gson =
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
index 86cfe62..90a257c 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
@@ -20,7 +20,7 @@
public JsonElement serialize(
BenchmarkResultsSingle result, Type type, JsonSerializationContext jsonSerializationContext) {
JsonArray resultsArray = new JsonArray();
- for (int iteration = 0; iteration < result.getCodeSizeResults().size(); iteration++) {
+ for (int iteration = 0; iteration < result.size(); iteration++) {
JsonObject resultObject = new JsonObject();
addPropertyIfValueDifferentFromRepresentative(
resultObject,
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
index 5d52187..ad6d841 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkTarget.java
@@ -8,9 +8,8 @@
// Possible dashboard targets on golem.
// WARNING: make sure the id-name is 1:1 with tools/run_benchmark.py!
D8("d8", "D8"),
- R8_COMPAT("r8-compat", "R8"),
- R8_NON_COMPAT("r8-full", "R8-full"),
- R8_FORCE_OPT("r8-force", "R8-full-minify-optimize-shrink");
+ R8("r8-full", "R8-full"),
+ RETRACE("retrace", "retrace");
private final String idName;
private final String golemName;
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index 1ba1fd0..3f7d657 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -57,6 +57,7 @@
private String name;
private BenchmarkDependency dumpDependency;
private int fromRevision = -1;
+ private boolean enableLibraryDesugaring = true;
private final List<String> programPackages = new ArrayList<>();
public void verify() {
@@ -71,6 +72,11 @@
}
}
+ public AppDumpBenchmarkBuilder setEnableLibraryDesugaring(boolean enableLibraryDesugaring) {
+ this.enableLibraryDesugaring = enableLibraryDesugaring;
+ return this;
+ }
+
public AppDumpBenchmarkBuilder setName(String name) {
this.name = name;
return this;
@@ -111,7 +117,7 @@
verify();
return BenchmarkConfig.builder()
.setName(name)
- .setTarget(BenchmarkTarget.R8_NON_COMPAT)
+ .setTarget(BenchmarkTarget.R8)
.setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
.setMethod(runR8(this, configuration))
.setFromRevision(fromRevision)
@@ -130,7 +136,7 @@
verify();
return BenchmarkConfig.builder()
.setName(name)
- .setTarget(BenchmarkTarget.R8_NON_COMPAT)
+ .setTarget(BenchmarkTarget.R8)
.setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
.setMethod(runR8WithResourceShrinking(this, getDefaultR8Configuration()))
.setFromRevision(fromRevision)
@@ -327,7 +333,7 @@
.addProgramFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.setMinApi(dumpProperties.getMinApi())
- .apply(b -> addDesugaredLibrary(b, dump))
+ .applyIf(builder.enableLibraryDesugaring, b -> addDesugaredLibrary(b, dump))
.benchmarkCompile(results)
.benchmarkCodeSize(results);
});
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/NowInAndroidBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/NowInAndroidBenchmarks.java
index c401bad..e635966 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/NowInAndroidBenchmarks.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/NowInAndroidBenchmarks.java
@@ -38,6 +38,12 @@
.setFromRevision(16017)
.buildBatchD8(),
AppDumpBenchmarkBuilder.builder()
+ .setName("NowInAndroidAppNoJ$")
+ .setDumpDependencyPath(dump)
+ .setEnableLibraryDesugaring(false)
+ .setFromRevision(16017)
+ .buildBatchD8(),
+ AppDumpBenchmarkBuilder.builder()
.setName("NowInAndroidApp")
.setDumpDependencyPath(dump)
.setFromRevision(16017)
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java
deleted file mode 100644
index 2d50a3d..0000000
--- a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java
+++ /dev/null
@@ -1,81 +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.benchmarks.desugaredlib;
-
-import com.android.tools.r8.StringResource;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.benchmarks.BenchmarkBase;
-import com.android.tools.r8.benchmarks.BenchmarkConfig;
-import com.android.tools.r8.benchmarks.BenchmarkDependency;
-import com.android.tools.r8.benchmarks.BenchmarkEnvironment;
-import com.android.tools.r8.benchmarks.BenchmarkTarget;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class LegacyDesugaredLibraryBenchmark extends BenchmarkBase {
-
- private static final BenchmarkDependency ANDROID_JAR = BenchmarkDependency.getAndroidJar30();
- private static final BenchmarkDependency LEGACY_CONF =
- new BenchmarkDependency(
- "legacyConf",
- "1.1.5",
- Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "desugar_jdk_libs_releases"));
-
- public LegacyDesugaredLibraryBenchmark(BenchmarkConfig config, TestParameters parameters) {
- super(config, parameters);
- }
-
- @Parameters(name = "{0}")
- public static List<Object[]> data() {
- return parametersFromConfigs(configs());
- }
-
- public static List<BenchmarkConfig> configs() {
- return ImmutableList.of(
- BenchmarkConfig.builder()
- .setName("LegacyDesugaredLibraryConf")
- .setTarget(BenchmarkTarget.D8)
- .setFromRevision(12150)
- .setMethod(LegacyDesugaredLibraryBenchmark::run)
- .addDependency(ANDROID_JAR)
- .addDependency(LEGACY_CONF)
- .measureRunTime()
- .build());
- }
-
- public static void run(BenchmarkEnvironment environment) throws Exception {
- runner(environment)
- .setWarmupIterations(1)
- .setBenchmarkIterations(10)
- .reportResultSum()
- .run(
- results ->
- testForD8(environment.getTemp(), Backend.DEX)
- .setMinApi(AndroidApiLevel.B)
- .addLibraryFiles(ANDROID_JAR.getRoot(environment).resolve("android.jar"))
- .apply(
- b ->
- b.getBuilder()
- .addDesugaredLibraryConfiguration(
- StringResource.fromFile(
- LEGACY_CONF.getRoot(environment).resolve("desugar.json"))))
- .benchmarkCompile(results));
- }
-
- static class TestClass {
-
- public static void main(String[] args) {
- System.out.println(Stream.of("Hello", "world!").collect(Collectors.joining(" ")));
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
index 4c8c31c..77b66a9 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
@@ -40,7 +40,7 @@
public static List<BenchmarkConfig> configs() {
Builder<BenchmarkConfig> benchmarks = ImmutableList.builder();
makeBenchmark(BenchmarkTarget.D8, HelloWorldBenchmark::benchmarkD8, benchmarks);
- makeBenchmark(BenchmarkTarget.R8_NON_COMPAT, HelloWorldBenchmark::benchmarkR8, benchmarks);
+ makeBenchmark(BenchmarkTarget.R8, HelloWorldBenchmark::benchmarkR8, benchmarks);
return benchmarks.build();
}
@@ -60,7 +60,7 @@
public String getName() {
// The name include each non-target option for the variants to ensure unique benchmarks.
- String backendString = backend.isCf() ? "Cf" : "Dex";
+ String backendString = backend.isCf() ? "Cf" : "";
String libraryString = library != null ? "" : "NoLib";
return "HelloWorld" + backendString + libraryString;
}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
index c3cd9d0..73e7345 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
@@ -46,8 +46,8 @@
return ImmutableList.<BenchmarkConfig>builder()
.add(
BenchmarkConfig.builder()
- .setName("RetraceStackTraceWithProguardMap")
- .setTarget(BenchmarkTarget.R8_NON_COMPAT)
+ .setName("R8")
+ .setTarget(BenchmarkTarget.RETRACE)
.measureRunTime()
.setMethod(benchmarkRetrace())
.setFromRevision(12266)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeStandaloneDayTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeStandaloneDayTest.java
index 1cab68d..629217e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeStandaloneDayTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeStandaloneDayTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
import com.android.tools.r8.TestParameters;
@@ -24,8 +25,8 @@
@RunWith(Parameterized.class)
public class DateTimeStandaloneDayTest extends DesugaredLibraryTestBase {
- // TODO(b/362277530): Replace expected output when desugared library is updated.
- private static final String EXPECTED_OUTPUT_TO_FIX =
+ // Standalone weekday names is only supported inn JDK-11 desugared library, see b/362277530.
+ private static final String EXPECTED_OUTPUT_TO_JDK8 =
StringUtils.lines("1", "2", "3", "4", "5", "6", "7");
private static final String EXPECTED_OUTPUT =
StringUtils.lines(
@@ -58,10 +59,11 @@
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputIf(
- parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O), EXPECTED_OUTPUT)
- .assertSuccessWithOutputIf(
- parameters.getApiLevel().isLessThan(AndroidApiLevel.O), EXPECTED_OUTPUT_TO_FIX);
+ .applyIf(
+ libraryDesugaringSpecification == JDK11
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT_TO_JDK8));
}
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
deleted file mode 100644
index 9dba59e..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2021, 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.records;
-
-import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.StringUtils;
-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 UnusedRecordReflectionTest extends TestBase {
-
- private static final String RECORD_NAME = "UnusedRecordReflection";
- private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
- private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
- private static final String EXPECTED_RESULT = StringUtils.lines("null", "null");
-
- @Parameter(0)
- public TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
- .withDexRuntimes()
- .withAllApiLevelsAlsoForCf()
- .build();
- }
-
- @Test
- public void testD8AndJvm() throws Exception {
- parameters.assumeJvmTestParameters();
- testForJvm(parameters)
- .addProgramClassFileData(PROGRAM_DATA)
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
- }
-
- @Test
- public void testD8() throws Exception {
- testForD8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters)
- .compile()
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
- }
-
- @Test
- public void testR8() throws Exception {
- parameters.assumeR8TestParameters();
- R8FullTestBuilder builder =
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters)
- .addKeepRules("-keep class records.UnusedRecordReflection { *; }")
- .addKeepMainRule(MAIN_TYPE);
- if (parameters.isCfRuntime()) {
- builder
- .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .compile()
- .inspect(RecordTestUtils::assertRecordsAreRecords)
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT);
- return;
- }
- builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
index 0f90b82..cec355d 100644
--- a/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/metadata/BuildMetadataTest.java
@@ -3,12 +3,29 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.metadata;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.Version;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupClass;
+import com.android.tools.r8.startup.profile.ExternalStartupItem;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -28,19 +45,63 @@
@Test
public void test() throws Exception {
+ ClassReference mainReference = Reference.classFromClass(Main.class);
+ List<ExternalStartupItem> startupProfile =
+ ImmutableList.of(ExternalStartupClass.builder().setClassReference(mainReference).build());
R8BuildMetadata buildMetadata =
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(
+ ExternalArtProfile.builder().addClassRule(mainReference).build())
+ .apply(StartupTestingUtils.addStartupProfile(startupProfile))
+ .applyIf(
+ parameters.isDexRuntime(),
+ testBuilder ->
+ testBuilder.addAndroidResources(getTestResources()).enableOptimizedShrinking())
+ .allowDiagnosticInfoMessages(parameters.canUseNativeMultidex())
.collectBuildMetadata()
.setMinApi(parameters)
- .compile()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (parameters.canUseNativeMultidex()) {
+ diagnostics.assertInfosMatch(
+ diagnosticMessage(containsString("Startup DEX files contains")));
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ })
.getBuildMetadata();
String json = buildMetadata.toJson();
// Inspecting the exact contents is not important here, but it *is* important to test that the
// property names are unobfuscated when testing with R8lib (!).
- assertEquals("{\"version\":\"" + Version.LABEL + "\"}", json);
+ assertThat(json, containsString("\"version\":\"" + Version.LABEL + "\""));
buildMetadata = R8BuildMetadata.fromJson(json);
+ inspectDeserializedBuildMetadata(buildMetadata);
+ }
+
+ private AndroidTestResource getTestResources() throws IOException {
+ return new AndroidTestResourceBuilder().withSimpleManifestAndAppNameString().build(temp);
+ }
+
+ private void inspectDeserializedBuildMetadata(R8BuildMetadata buildMetadata) {
+ assertNotNull(buildMetadata.getBaselineProfileRewritingOptions());
+ assertNotNull(buildMetadata.getOptions());
+ assertNotNull(buildMetadata.getOptions().getKeepAttributesOptions());
+ if (parameters.isDexRuntime()) {
+ R8ResourceOptimizationOptions resourceOptimizationOptions =
+ buildMetadata.getResourceOptimizationOptions();
+ assertNotNull(resourceOptimizationOptions);
+ assertTrue(resourceOptimizationOptions.isOptimizedShrinkingEnabled());
+ } else {
+ assertNull(buildMetadata.getResourceOptimizationOptions());
+ }
+ R8StartupOptimizationOptions startupOptimizationOptions =
+ buildMetadata.getStartupOptizationOptions();
+ assertNotNull(startupOptimizationOptions);
+ assertEquals(
+ parameters.isDexRuntime() && parameters.canUseNativeMultidex() ? 1 : 0,
+ startupOptimizationOptions.getNumberOfStartupDexFiles());
assertEquals(Version.LABEL, buildMetadata.getVersion());
}
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
index ac5fd5a..9f5883e 100644
--- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@
-f4f486f37cf801466634ffbf584adb80dc839f4d
\ No newline at end of file
+f3213584e94bf6951c3765c6b8d23405c332ca1b
\ No newline at end of file
diff --git a/tools/perf.py b/tools/perf.py
index 5d3cb2b..fec3449 100755
--- a/tools/perf.py
+++ b/tools/perf.py
@@ -15,11 +15,59 @@
if utils.is_bot():
import upload_benchmark_data_to_google_storage
-APPS = [
- 'ChromeApp', 'CraneApp', 'JetLaggedApp', 'JetNewsApp', 'JetCasterApp',
- 'JetChatApp', 'JetSnackApp', 'NowInAndroidApp', 'OwlApp', 'ReplyApp',
- 'TiviApp'
-]
+BENCHMARKS = {
+ 'ChromeApp': {
+ 'targets': ['r8-full']
+ },
+ 'CraneApp': {
+ 'targets': ['r8-full']
+ },
+ 'HelloWorld': {
+ 'targets': ['d8']
+ },
+ 'HelloWorldNoLib': {
+ 'targets': ['d8']
+ },
+ 'HelloWorldCf': {
+ 'targets': ['d8']
+ },
+ 'HelloWorldCfNoLib': {
+ 'targets': ['d8']
+ },
+ 'JetLaggedApp': {
+ 'targets': ['r8-full']
+ },
+ 'JetNewsApp': {
+ 'targets': ['r8-full']
+ },
+ 'JetCasterApp': {
+ 'targets': ['r8-full']
+ },
+ 'JetChatApp': {
+ 'targets': ['r8-full']
+ },
+ 'JetSnackApp': {
+ 'targets': ['r8-full']
+ },
+ 'NowInAndroidApp': {
+ 'targets': ['d8', 'r8-full']
+ },
+ 'NowInAndroidAppNoJ$': {
+ 'targets': ['d8']
+ },
+ 'OwlApp': {
+ 'targets': ['r8-full']
+ },
+ 'R8': {
+ 'targets': ['retrace']
+ },
+ 'ReplyApp': {
+ 'targets': ['r8-full']
+ },
+ 'TiviApp': {
+ 'targets': ['r8-full']
+ },
+}
BUCKET = "r8-perf-results"
SAMPLE_BENCHMARK_RESULT_JSON = {
'benchmark_name': '<benchmark_name>',
@@ -37,8 +85,8 @@
# meta contains information about the execution (machine)
def ParseOptions():
result = argparse.ArgumentParser()
- result.add_argument('--app',
- help='Specific app(s) to measure.',
+ result.add_argument('--benchmark',
+ help='Specific benchmark(s) to measure.',
action='append')
result.add_argument('--iterations',
help='How many times run_benchmark is run.',
@@ -54,10 +102,10 @@
help='Skip if output exists.',
action='store_true',
default=False)
- result.add_argument('--target',
- help='Specific target to run on.',
- default='r8-full',
- choices=['d8', 'r8-full', 'r8-force', 'r8-compat'])
+ result.add_argument(
+ '--target',
+ help='Specific target to run on.',
+ choices=['d8', 'r8-full', 'r8-force', 'r8-compat', 'retrace'])
result.add_argument('--verbose',
help='To enable verbose logging.',
action='store_true',
@@ -67,21 +115,22 @@
help='Use R8 hash for the run (default local build)',
default=None)
options, args = result.parse_known_args()
- options.apps = options.app or APPS
+ options.benchmarks = options.benchmark or BENCHMARKS.keys()
options.quiet = not options.verbose
- del options.app
+ del options.benchmark
return options, args
def Build(options):
utils.Print('Building', quiet=options.quiet)
- build_cmd = GetRunCmd('N/A', options, ['--iterations', '0'])
+ target = options.target or 'r8-full'
+ build_cmd = GetRunCmd('N/A', target, options, ['--iterations', '0'])
subprocess.check_call(build_cmd)
-def GetRunCmd(app, options, args):
+def GetRunCmd(benchmark, target, options, args):
base_cmd = [
- 'tools/run_benchmark.py', '--benchmark', app, '--target', options.target
+ 'tools/run_benchmark.py', '--benchmark', benchmark, '--target', target
]
if options.verbose:
base_cmd.append('--verbose')
@@ -119,9 +168,9 @@
return json.loads(''.join(lines))
-def GetArtifactLocation(app, target, version, filename):
+def GetArtifactLocation(benchmark, target, version, filename):
version_or_head = version or utils.get_HEAD_sha1()
- return f'{app}/{target}/{version_or_head}/{filename}'
+ return f'{benchmark}/{target}/{version_or_head}/{filename}'
def GetGSLocation(filename, bucket=BUCKET):
@@ -155,62 +204,70 @@
download_options = argparse.Namespace(no_build=True, nolib=True)
r8jar = compiledump.download_distribution(options.version,
download_options, temp)
- for app in options.apps:
- if options.skip_if_output_exists:
- if options.outdir:
- raise NotImplementedError
- output = GetGSLocation(
- GetArtifactLocation(app, options.target, options.version,
- 'result.json'))
- if utils.cloud_storage_exists(output):
- print(f'Skipping run, {output} already exists.')
+ for benchmark in options.benchmarks:
+ targets = [options.target
+ ] if options.target else BENCHMARKS[benchmark]['targets']
+ for target in targets:
+ if options.skip_if_output_exists:
+ if options.outdir:
+ raise NotImplementedError
+ output = GetGSLocation(
+ GetArtifactLocation(benchmark, target, options.version,
+ 'result.json'))
+ if utils.cloud_storage_exists(output):
+ print(f'Skipping run, {output} already exists.')
+ continue
+
+ # Run benchmark.
+ benchmark_result_json_files = []
+ failed = False
+ for i in range(options.iterations):
+ utils.Print(
+ f'Benchmarking {benchmark} ({i+1}/{options.iterations})',
+ quiet=options.quiet)
+ benchhmark_result_file = os.path.join(
+ temp, f'result_file_{i}')
+ iteration_cmd = GetRunCmd(benchmark, target, options, [
+ '--iterations',
+ str(options.iterations_inner), '--output',
+ benchhmark_result_file, '--no-build'
+ ])
+ try:
+ subprocess.check_call(iteration_cmd)
+ benchmark_result_json_files.append(
+ benchhmark_result_file)
+ except subprocess.CalledProcessError as e:
+ failed = True
+ any_failed = True
+ break
+
+ if failed:
continue
- # Run benchmark.
- benchmark_result_json_files = []
- failed = False
- for i in range(options.iterations):
- utils.Print(f'Benchmarking {app} ({i+1}/{options.iterations})',
- quiet=options.quiet)
- benchhmark_result_file = os.path.join(temp, f'result_file_{i}')
- iteration_cmd = GetRunCmd(app, options, [
- '--iterations',
- str(options.iterations_inner), '--output',
- benchhmark_result_file, '--no-build'
- ])
- try:
- subprocess.check_call(iteration_cmd)
- benchmark_result_json_files.append(benchhmark_result_file)
- except subprocess.CalledProcessError as e:
- failed = True
- any_failed = True
- break
-
- if failed:
- continue
-
- # Merge results and write output.
- result_file = os.path.join(temp, 'result_file')
- with open(result_file, 'w') as f:
- json.dump(
- MergeBenchmarkResultJsonFiles(benchmark_result_json_files),
- f)
- ArchiveOutputFile(result_file,
- GetArtifactLocation(app, options.target,
- options.version,
- 'result.json'),
- outdir=options.outdir)
-
- # Write metadata.
- if utils.is_bot():
- meta_file = os.path.join(temp, "meta")
- with open(meta_file, 'w') as f:
- f.write("Produced by: " + os.environ.get('SWARMING_BOT_ID'))
- ArchiveOutputFile(meta_file,
- GetArtifactLocation(app, options.target,
- options.version, 'meta'),
+ # Merge results and write output.
+ result_file = os.path.join(temp, 'result_file')
+ with open(result_file, 'w') as f:
+ json.dump(
+ MergeBenchmarkResultJsonFiles(
+ benchmark_result_json_files), f)
+ ArchiveOutputFile(result_file,
+ GetArtifactLocation(benchmark, target,
+ options.version,
+ 'result.json'),
outdir=options.outdir)
+ # Write metadata.
+ if utils.is_bot():
+ meta_file = os.path.join(temp, "meta")
+ with open(meta_file, 'w') as f:
+ f.write("Produced by: " +
+ os.environ.get('SWARMING_BOT_ID'))
+ ArchiveOutputFile(meta_file,
+ GetArtifactLocation(
+ benchmark, target, options.version,
+ 'meta'),
+ outdir=options.outdir)
+
if utils.is_bot():
upload_benchmark_data_to_google_storage.run()
diff --git a/tools/perf/chart.js b/tools/perf/chart.js
new file mode 100644
index 0000000..96c7f07
--- /dev/null
+++ b/tools/perf/chart.js
@@ -0,0 +1,117 @@
+// 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.
+import dom from "./dom.js";
+import scales from "./scales.js";
+import state from "./state.js";
+import url from "./url.js";
+
+var chart = null;
+var dataProvider = null;
+
+function getBenchmarkColors(theChart) {
+ const benchmarkColors = {};
+ for (var datasetIndex = 0;
+ datasetIndex < theChart.data.datasets.length;
+ datasetIndex++) {
+ if (theChart.getDatasetMeta(datasetIndex).hidden) {
+ continue;
+ }
+ const dataset = theChart.data.datasets[datasetIndex];
+ const benchmark = dataset.benchmark;
+ const benchmarkColor = dataset.borderColor;
+ if (!(benchmark in benchmarkColors)) {
+ benchmarkColors[benchmark] = benchmarkColor;
+ }
+ }
+ return benchmarkColors;
+}
+
+function get() {
+ return chart;
+}
+
+function getData() {
+ const filteredCommits = state.commits(state.zoom);
+ return dataProvider(filteredCommits);
+}
+
+function getDataLabelFormatter(value, context) {
+ var percentageChange = getDataPercentageChange(context);
+ var percentageChangeTwoDecimals = Math.round(percentageChange * 100) / 100;
+ var glyph = percentageChange < 0 ? 'â–¼' : 'â–²';
+ return glyph + ' ' + percentageChangeTwoDecimals + '%';
+}
+
+function getDataPercentageChange(context) {
+ var i = context.dataIndex;
+ var value = context.dataset.data[i];
+ var j = i;
+ var previousValue;
+ do {
+ if (j == 0) {
+ return null;
+ }
+ previousValue = context.dataset.data[--j];
+ } while (previousValue === undefined || isNaN(previousValue));
+ return (value - previousValue) / previousValue * 100;
+}
+
+function initializeChart(options) {
+ chart = new Chart(dom.canvas, {
+ data: getData(),
+ options: options,
+ plugins: [ChartDataLabels]
+ });
+ // Hide disabled legends.
+ if (state.selectedLegends.size < state.legends.size) {
+ update(false, true);
+ } else {
+ update(false, false);
+ }
+}
+
+function setDataProvider(theDataProvider) {
+ dataProvider = theDataProvider;
+}
+
+function update(dataChanged, legendsChanged) {
+ console.assert(state.zoom.left <= state.zoom.right);
+
+ // Update datasets.
+ if (dataChanged) {
+ const newData = getData();
+ Object.assign(chart.data, newData);
+ // Update chart.
+ chart.update();
+ }
+
+ // Update legends.
+ if (legendsChanged || (dataChanged && state.selectedLegends.size < state.legends.size)) {
+ for (var datasetIndex = 0;
+ datasetIndex < chart.data.datasets.length;
+ datasetIndex++) {
+ const datasetMeta = chart.getDatasetMeta(datasetIndex);
+ datasetMeta.hidden = !state.isLegendSelected(datasetMeta.label);
+ }
+
+ // Update scales.
+ scales.update(chart.options.scales);
+
+ // Update chart.
+ chart.update();
+ }
+
+ dom.updateBenchmarkColors(getBenchmarkColors(chart));
+ dom.updateChartNavigation();
+ url.updateHash(state);
+}
+
+export default {
+ get: get,
+ getDataLabelFormatter: getDataLabelFormatter,
+ getDataPercentageChange: getDataPercentageChange,
+ initializeChart: initializeChart,
+ setDataProvider: setDataProvider,
+ update: update
+};
diff --git a/tools/perf/d8.html b/tools/perf/d8.html
new file mode 100644
index 0000000..bd9c867
--- /dev/null
+++ b/tools/perf/d8.html
@@ -0,0 +1,258 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>D8 perf</title>
+ <link rel="stylesheet" href="stylesheet.css">
+</head>
+<body>
+ <div id="benchmark-selectors"></div>
+ <div>
+ <canvas id="myChart"></canvas>
+ </div>
+ <div>
+ <div style="float: left; width: 50%">
+ <button type="button" id="show-more-left" disabled>⇐</button>
+ <button type="button" id="show-less-left">⇒</button>
+ </div>
+ <div style="float: left; text-align: right; width: 50%">
+ <button type="button" id="show-less-right">⇐</button>
+ <button type="button" id="show-more-right" disabled>⇒</button>
+ </div>
+ </div>
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>
+ <script src="extensions.js"></script>
+ <script src="utils.js"></script>
+ <script type="module">
+ import chart from "./chart.js";
+ import dom from "./dom.js";
+ import scales from "./scales.js";
+ import state from "./state.js";
+
+ const commits = await state.importCommits("./d8_benchmark_data.json");
+ state.initializeBenchmarks();
+ state.initializeLegends({
+ 'Dex size': { default: true },
+ 'Nondeterminism': { default: true },
+ 'Runtime': { default: true },
+ 'Runtime variance': { default: false }
+ });
+ state.initializeZoom();
+ dom.initializeBenchmarkSelectors();
+ dom.initializeChartNavigation();
+
+ // Chart data provider.
+ function getData(filteredCommits) {
+ const labels = filteredCommits.map((c, i) => c.index);
+ const datasets = getDatasets(filteredCommits);
+ return {
+ labels: labels,
+ datasets: datasets
+ };
+ }
+
+ function getDatasets(filteredCommits) {
+ const datasets = [];
+ state.forEachSelectedBenchmark(
+ selectedBenchmark => {
+ const codeSizeData =
+ filteredCommits.map(
+ (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "code_size"));
+ const codeSizeScatterData = [];
+ for (const commit of filteredCommits.values()) {
+ if (!(selectedBenchmark in commit.benchmarks)) {
+ continue;
+ }
+ const seen = new Set();
+ seen.add(getSingleResult(selectedBenchmark, commit, "code_size"));
+ const codeSizes = getAllResults(selectedBenchmark, commit, "code_size")
+ for (const codeSize of codeSizes.values()) {
+ if (!seen.has(codeSize)) {
+ codeSizeScatterData.push({ x: commit.index, y: codeSize });
+ seen.add(codeSize);
+ }
+ }
+ }
+ const runtimeData =
+ filteredCommits.map(
+ (c, i) =>
+ selectedBenchmark in filteredCommits[i].benchmarks
+ ? getAllResults(selectedBenchmark, filteredCommits[i], "runtime")
+ .min()
+ .ns_to_s()
+ : NaN);
+ const runtimeScatterData = [];
+ for (const commit of filteredCommits.values()) {
+ if (!(selectedBenchmark in commit.benchmarks)) {
+ continue;
+ }
+ const runtimes = getAllResults(selectedBenchmark, commit, "runtime")
+ for (const runtime of runtimes.values()) {
+ runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
+ }
+ }
+
+ const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;
+ datasets.push(...[
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Dex size',
+ data: codeSizeData,
+ datalabels: {
+ align: 'end',
+ anchor: 'end'
+ },
+ tension: 0.1,
+ yAxisID: 'y',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'scatter',
+ label: 'Nondeterminism',
+ data: codeSizeScatterData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ radius: 6,
+ pointBackgroundColor: 'red'
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Runtime',
+ data: runtimeData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ tension: 0.1,
+ yAxisID: 'y_runtime',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'scatter',
+ label: 'Runtime variance',
+ data: runtimeScatterData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ yAxisID: 'y_runtime'
+ }
+ ]);
+ });
+ return datasets;
+ }
+
+ // Chart options.
+ const options = {
+ onHover: (event, chartElement) =>
+ event.native.target.style.cursor =
+ chartElement[0] ? 'pointer' : 'default',
+ plugins: {
+ datalabels: {
+ backgroundColor: 'rgba(255, 255, 255, 0.7)',
+ borderColor: 'rgba(128, 128, 128, 0.7)',
+ borderRadius: 4,
+ borderWidth: 1,
+ color: context => chart.getDataPercentageChange(context) < 0 ? 'green' : 'red',
+ display: context => {
+ var percentageChange = chart.getDataPercentageChange(context);
+ return percentageChange !== null && Math.abs(percentageChange) >= 0.1;
+ },
+ font: {
+ size: 20,
+ weight: 'bold'
+ },
+ offset: 8,
+ formatter: chart.getDataLabelFormatter,
+ padding: 6
+ },
+ legend: {
+ labels: {
+ filter: (legendItem, data) => {
+ // Only retain the legends for the first selected benchmark. If
+ // multiple benchmarks are selected, then use the legends of the
+ // first selected benchmark to control all selected benchmarks.
+ const numUniqueLegends =
+ data.datasets.length / state.selectedBenchmarks.size;
+ return legendItem.datasetIndex < numUniqueLegends;
+ },
+ },
+ onClick: (e, legendItem, legend) => {
+ const clickedLegend = legendItem.text;
+ if (state.selectedLegends.has(clickedLegend)) {
+ state.selectedLegends.delete(clickedLegend);
+ } else {
+ state.selectedLegends.add(clickedLegend);
+ }
+ chart.update(false, true);
+ },
+ },
+ tooltip: {
+ callbacks: {
+ title: context => {
+ const elementInfo = context[0];
+ var commit;
+ if (elementInfo.dataset.type == 'line') {
+ commit = commits[state.zoom.left + elementInfo.dataIndex];
+ } else {
+ console.assert(elementInfo.dataset.type == 'scatter');
+ commit = commits[elementInfo.raw.x];
+ }
+ return commit.title;
+ },
+ footer: context => {
+ const elementInfo = context[0];
+ var commit;
+ if (elementInfo.dataset.type == 'line') {
+ commit = commits[state.zoom.left + elementInfo.dataIndex];
+ } else {
+ console.assert(elementInfo.dataset.type == 'scatter');
+ commit = commits[elementInfo.raw.x];
+ }
+ const dataset = chart.get().data.datasets[elementInfo.datasetIndex];
+ return `App: ${dataset.benchmark}\n`
+ + `Author: ${commit.author}\n`
+ + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n`
+ + `Hash: ${commit.hash}\n`
+ + `Index: ${commit.index}`;
+ }
+ }
+ }
+ },
+ responsive: true,
+ scales: scales.get()
+ };
+
+ chart.setDataProvider(getData);
+ chart.initializeChart(options);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/tools/perf/dom.js b/tools/perf/dom.js
new file mode 100644
index 0000000..101c588
--- /dev/null
+++ b/tools/perf/dom.js
@@ -0,0 +1,125 @@
+// 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.
+import chart from "./chart.js";
+import state from "./state.js";
+
+// DOM references.
+const benchmarkSelectors = document.getElementById('benchmark-selectors');
+const canvas = document.getElementById('myChart');
+const showMoreLeft = document.getElementById('show-more-left');
+const showLessLeft = document.getElementById('show-less-left');
+const showLessRight = document.getElementById('show-less-right');
+const showMoreRight = document.getElementById('show-more-right');
+
+function initializeBenchmarkSelectors() {
+ state.forEachBenchmark(
+ (benchmark, selected) => {
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+ input.name = 'benchmark';
+ input.id = benchmark;
+ input.value = benchmark;
+ input.checked = selected;
+ input.onchange = function (e) {
+ if (e.target.checked) {
+ state.selectedBenchmarks.add(e.target.value);
+ } else {
+ state.selectedBenchmarks.delete(e.target.value);
+ }
+ chart.update(true, false);
+ };
+
+ const label = document.createElement('label');
+ label.id = benchmark + 'Label';
+ label.htmlFor = benchmark;
+ label.innerHTML = benchmark;
+
+ benchmarkSelectors.appendChild(input);
+ benchmarkSelectors.appendChild(label);
+ });
+}
+
+function initializeChartNavigation() {
+ const zoom = state.zoom;
+
+ canvas.onclick = event => {
+ const points =
+ chart.get().getElementsAtEventForMode(
+ event, 'nearest', { intersect: true }, true);
+ if (points.length > 0) {
+ const point = points[0];
+ const commit = state.commits[point.index];
+ window.open('https://r8.googlesource.com/r8/+/' + commit.hash, '_blank');
+ }
+ };
+
+ showMoreLeft.onclick = event => {
+ if (zoom.left == 0) {
+ return;
+ }
+ const currentSize = zoom.right - zoom.left;
+ zoom.left = zoom.left - currentSize;
+ if (zoom.left < 0) {
+ zoom.left = 0;
+ }
+ chart.update(true, false);
+ };
+
+ showLessLeft.onclick = event => {
+ const currentSize = zoom.right - zoom.left;
+ zoom.left = zoom.left + Math.floor(currentSize / 2);
+ if (zoom.left >= zoom.right) {
+ zoom.left = zoom.right - 1;
+ }
+ chart.update(true, false);
+ };
+
+ showLessRight.onclick = event => {
+ if (zoom.right == 0) {
+ return;
+ }
+ const currentSize = zoom.right - zoom.left;
+ zoom.right = zoom.right - Math.floor(currentSize / 2);
+ if (zoom.right < zoom.left) {
+ zoom.right = zoom.left;
+ }
+ chart.update(true, false);
+ };
+
+ showMoreRight.onclick = event => {
+ const currentSize = zoom.right - zoom.left;
+ zoom.right = zoom.right + currentSize;
+ if (zoom.right > state.commits().length) {
+ zoom.right = state.commits().length;
+ }
+ chart.update(true, false);
+ };
+}
+
+function updateBenchmarkColors(benchmarkColors) {
+ state.forEachBenchmark(
+ benchmark => {
+ const benchmarkLabel = document.getElementById(benchmark + 'Label');
+ const benchmarkColor = benchmarkColors[benchmark] || '#000000';
+ const benchmarkFontWeight = benchmark in benchmarkColors ? 'bold' : 'normal';
+ benchmarkLabel.style.color = benchmarkColor;
+ benchmarkLabel.style.fontWeight = benchmarkFontWeight;
+ });
+}
+
+function updateChartNavigation() {
+ const zoom = state.zoom;
+ showMoreLeft.disabled = zoom.left == 0;
+ showLessLeft.disabled = zoom.left == zoom.right - 1;
+ showLessRight.disabled = zoom.left == zoom.right - 1;
+ showMoreRight.disabled = zoom.right == state.commits().length;
+}
+
+export default {
+ canvas: canvas,
+ initializeBenchmarkSelectors: initializeBenchmarkSelectors,
+ initializeChartNavigation: initializeChartNavigation,
+ updateBenchmarkColors: updateBenchmarkColors,
+ updateChartNavigation: updateChartNavigation
+};
\ No newline at end of file
diff --git a/tools/perf/extensions.js b/tools/perf/extensions.js
new file mode 100644
index 0000000..1aa20e0
--- /dev/null
+++ b/tools/perf/extensions.js
@@ -0,0 +1,32 @@
+// 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.
+Array.prototype.any = function(predicate) {
+ for (const element of this.values()) {
+ if (predicate(element)) {
+ return true;
+ }
+ }
+ return false;
+};
+Array.prototype.first = function() {
+ return this[0];
+};
+Array.prototype.avg = function() {
+ return this.reduce(function(x, y) { return x + y; }, 0) / this.length;
+};
+Array.prototype.min = function() {
+ return this.reduce(function(x, y) { return x === null ? y : Math.min(x, y); }, null);
+};
+Array.prototype.reverseInPlace = function() {
+ for (var i = 0; i < Math.floor(this.length / 2); i++) {
+ var temp = this[i];
+ this[i] = this[this.length - i - 1];
+ this[this.length - i - 1] = temp;
+ }
+};
+Number.prototype.ns_to_s = function() {
+ const seconds = this/10E8;
+ const seconds_with_one_decimal = Math.round(seconds*10)/10;
+ return seconds;
+};
\ No newline at end of file
diff --git a/tools/perf/index.html b/tools/perf/index.html
deleted file mode 100644
index 8d4a6ef..0000000
--- a/tools/perf/index.html
+++ /dev/null
@@ -1,649 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <meta charset="utf-8">
- <title>R8 perf</title>
- <link rel="stylesheet" href="stylesheet.css">
-</head>
-<body>
- <div id="benchmark-selectors"></div>
- <div>
- <canvas id="myChart"></canvas>
- </div>
- <div>
- <div style="float: left; width: 50%">
- <button type="button" id="show-more-left" disabled>⇐</button>
- <button type="button" id="show-less-left">⇒</button>
- </div>
- <div style="float: left; text-align: right; width: 50%">
- <button type="button" id="show-less-right">⇐</button>
- <button type="button" id="show-more-right" disabled>⇒</button>
- </div>
- </div>
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>
- <script type="module">
- // Utility methods.
- Array.prototype.any = function(predicate) {
- for (const element of this.values()) {
- if (predicate(element)) {
- return true;
- }
- }
- return false;
- };
- Array.prototype.first = function() {
- return this[0];
- };
- Array.prototype.avg = function() {
- return this.reduce(function(x, y) { return x + y; }, 0) / this.length;
- };
- Array.prototype.min = function() {
- return this.reduce(function(x, y) { return x === null ? y : Math.min(x, y); }, null);
- };
- Array.prototype.reverseInPlace = function() {
- for (var i = 0; i < Math.floor(this.length / 2); i++) {
- var temp = this[i];
- this[i] = this[this.length - i - 1];
- this[this.length - i - 1] = temp;
- }
- };
- Number.prototype.ns_to_s = function() {
- const seconds = this/10E8;
- const seconds_with_one_decimal = Math.round(seconds*10)/10;
- return seconds;
- };
-
- // Import and reverse commits so that newest are last.
- import commits from "./benchmark_data.json" with { type: "json" };
- commits.reverseInPlace();
-
- // Amend the commits with their unique index.
- for (var i = 0; i < commits.length; i++) {
- commits[i].index = i;
- }
-
- // DOM references.
- const benchmarkSelectors = document.getElementById('benchmark-selectors');
- const canvas = document.getElementById('myChart');
- const showMoreLeft = document.getElementById('show-more-left');
- const showLessLeft = document.getElementById('show-less-left');
- const showLessRight = document.getElementById('show-less-right');
- const showMoreRight = document.getElementById('show-more-right');
-
- // Initialize benchmark selectors.
- const benchmarks = new Set();
- for (const commit of commits.values()) {
- for (const benchmark in commit.benchmarks) {
- benchmarks.add(benchmark);
- }
- }
- const selectedBenchmarks = new Set();
- const urlOptions = unescape(window.location.hash.substring(1)).split(',');
- for (const benchmark of benchmarks.values()) {
- for (const filter of urlOptions.values()) {
- if (filter) {
- const match = benchmark.match(new RegExp(filter.replace("*", ".*")));
- if (match) {
- selectedBenchmarks.add(benchmark);
- break;
- }
- }
- }
- }
- if (selectedBenchmarks.size == 0) {
- const randomBenchmarkIndex = Math.floor(Math.random() * benchmarks.size);
- const randomBenchmark = Array.from(benchmarks)[randomBenchmarkIndex];
- selectedBenchmarks.add(randomBenchmark);
- }
- for (const benchmark of benchmarks.values()) {
- const input = document.createElement('input');
- input.type = 'checkbox';
- input.name = 'benchmark';
- input.id = benchmark;
- input.value = benchmark;
- input.checked = selectedBenchmarks.has(benchmark);
- input.onchange = function (e) {
- if (e.target.checked) {
- selectedBenchmarks.add(e.target.value);
- } else {
- selectedBenchmarks.delete(e.target.value);
- }
- updateChart(true, false);
- };
-
- const label = document.createElement('label');
- label.id = benchmark + 'Label';
- label.htmlFor = benchmark;
- label.innerHTML = benchmark;
-
- benchmarkSelectors.appendChild(input);
- benchmarkSelectors.appendChild(label);
- }
-
- function getSingleResult(benchmark, commit, resultName, resultIteration = 0) {
- if (!(benchmark in commit.benchmarks)) {
- return NaN;
- }
- const allResults = commit.benchmarks[benchmark].results;
- const resultsForIteration = allResults[resultIteration];
- // If a given iteration does not declare a result, then the result
- // was the same as the first run.
- if (resultIteration > 0 && !(resultName in resultsForIteration)) {
- return allResults.first()[resultName];
- }
- return resultsForIteration[resultName];
- }
-
- function getAllResults(benchmark, commit, resultName) {
- const result = [];
- const allResults = commit.benchmarks[benchmark].results;
- for (var iteration = 0; iteration < allResults.length; iteration++) {
- result.push(getSingleResult(benchmark, commit, resultName, iteration));
- }
- return result;
- }
-
- // Chart data provider.
- function getData() {
- const filteredCommits = commits.slice(zoom.left, zoom.right);
- const labels = filteredCommits.map((c, i) => c.index);
- const datasets = [];
- for (const selectedBenchmark of selectedBenchmarks.values()) {
- const codeSizeData =
- filteredCommits.map(
- (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "code_size"));
- const instructionCodeSizeData =
- filteredCommits.map(
- (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "ins_code_size"));
- const composableInstructionCodeSizeData =
- filteredCommits.map(
- (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "composable_code_size"));
- const oatCodeSizeData =
- filteredCommits.map(
- (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "oat_code_size"));
- const codeSizeScatterData = [];
- for (const commit of filteredCommits.values()) {
- if (!(selectedBenchmark in commit.benchmarks)) {
- continue;
- }
- const seen = new Set();
- seen.add(getSingleResult(selectedBenchmark, commit, "code_size"));
- const codeSizes = getAllResults(selectedBenchmark, commit, "code_size")
- for (const codeSize of codeSizes.values()) {
- if (!seen.has(codeSize)) {
- codeSizeScatterData.push({ x: commit.index, y: codeSize });
- seen.add(codeSize);
- }
- }
- }
- const runtimeData =
- filteredCommits.map(
- (c, i) =>
- selectedBenchmark in filteredCommits[i].benchmarks
- ? getAllResults(selectedBenchmark, filteredCommits[i], "runtime")
- .min()
- .ns_to_s()
- : NaN);
- const runtimeScatterData = [];
- for (const commit of filteredCommits.values()) {
- if (!(selectedBenchmark in commit.benchmarks)) {
- continue;
- }
- const runtimes = getAllResults(selectedBenchmark, commit, "runtime")
- for (const runtime of runtimes.values()) {
- runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
- }
- }
-
- const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;
- datasets.push(...[
- {
- benchmark: selectedBenchmark,
- type: 'line',
- label: 'Dex size',
- data: codeSizeData,
- datalabels: {
- align: 'end',
- anchor: 'end'
- },
- tension: 0.1,
- yAxisID: 'y',
- segment: {
- borderColor: ctx =>
- skipped(
- ctx,
- myChart
- ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
- : undefined),
- borderDash: ctx => skipped(ctx, [6, 6]),
- },
- spanGaps: true
- },
- {
- benchmark: selectedBenchmark,
- type: 'line',
- label: 'Instruction size',
- data: instructionCodeSizeData,
- datalabels: {
- align: 'end',
- anchor: 'end'
- },
- tension: 0.1,
- yAxisID: 'y_ins_code_size',
- segment: {
- borderColor: ctx =>
- skipped(
- ctx,
- myChart
- ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
- : undefined),
- borderDash: ctx => skipped(ctx, [6, 6]),
- },
- spanGaps: true
- },
- {
- benchmark: selectedBenchmark,
- type: 'line',
- label: 'Composable size',
- data: composableInstructionCodeSizeData,
- datalabels: {
- align: 'start',
- anchor: 'start'
- },
- tension: 0.1,
- yAxisID: 'y_ins_code_size',
- segment: {
- borderColor: ctx =>
- skipped(
- ctx,
- myChart
- ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
- : undefined),
- borderDash: ctx => skipped(ctx, [6, 6]),
- },
- spanGaps: true
- },
- {
- benchmark: selectedBenchmark,
- type: 'line',
- label: 'Oat size',
- data: oatCodeSizeData,
- datalabels: {
- align: 'start',
- anchor: 'start'
- },
- tension: 0.1,
- yAxisID: 'y_oat_code_size',
- segment: {
- borderColor: ctx =>
- skipped(
- ctx,
- myChart
- ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
- : undefined),
- borderDash: ctx => skipped(ctx, [6, 6]),
- },
- spanGaps: true
- },
- {
- benchmark: selectedBenchmark,
- type: 'scatter',
- label: 'Nondeterminism',
- data: codeSizeScatterData,
- datalabels: {
- labels: {
- value: null
- }
- },
- radius: 6,
- pointBackgroundColor: 'red'
- },
- {
- benchmark: selectedBenchmark,
- type: 'line',
- label: 'Runtime',
- data: runtimeData,
- datalabels: {
- labels: {
- value: null
- }
- },
- tension: 0.1,
- yAxisID: 'y_runtime',
- segment: {
- borderColor: ctx =>
- skipped(
- ctx,
- myChart
- ? myChart.data.datasets[ctx.datasetIndex].backgroundColor
- : undefined),
- borderDash: ctx => skipped(ctx, [6, 6]),
- },
- spanGaps: true
- },
- {
- benchmark: selectedBenchmark,
- type: 'scatter',
- label: 'Runtime variance',
- data: runtimeScatterData,
- datalabels: {
- labels: {
- value: null
- }
- },
- yAxisID: 'y_runtime'
- }
- ]);
- }
-
- return {
- labels: labels,
- datasets: datasets,
- };
- }
-
- // Legend tracking.
- const legends =
- new Set(['Dex size', 'Instruction size', 'Composable size', 'Oat size', 'Nondeterminism', 'Runtime', 'Runtime variance']);
- const selectedLegends =
- new Set(
- unescape(window.location.hash.substring(1))
- .split(',')
- .filter(l => legends.has(l)));
- if (selectedLegends.size == 0) {
- legends.forEach(l => selectedLegends.add(l));
- selectedLegends.delete('Runtime variance')
- }
-
- function getDataPercentageChange(context) {
- var i = context.dataIndex;
- var value = context.dataset.data[i];
- var j = i;
- var previousValue;
- do {
- if (j == 0) {
- return null;
- }
- previousValue = context.dataset.data[--j];
- } while (previousValue === undefined || isNaN(previousValue));
- return (value - previousValue) / previousValue * 100;
- }
-
- // Chart options.
- const options = {
- onHover: (event, chartElement) =>
- event.native.target.style.cursor =
- chartElement[0] ? 'pointer' : 'default',
- plugins: {
- datalabels: {
- backgroundColor: 'rgba(255, 255, 255, 0.7)',
- borderColor: 'rgba(128, 128, 128, 0.7)',
- borderRadius: 4,
- borderWidth: 1,
- color: context => getDataPercentageChange(context) < 0 ? 'green' : 'red',
- display: context => {
- var percentageChange = getDataPercentageChange(context);
- return percentageChange !== null && Math.abs(percentageChange) >= 0.1;
- },
- font: {
- size: 20,
- weight: 'bold'
- },
- offset: 8,
- formatter: (value, context) => {
- var percentageChange = getDataPercentageChange(context);
- var percentageChangeTwoDecimals = Math.round(percentageChange * 100) / 100;
- var glyph = percentageChange < 0 ? 'â–¼' : 'â–²';
- return glyph + ' ' + percentageChangeTwoDecimals + '%';
- },
- padding: 6
- },
- legend: {
- labels: {
- filter: (legendItem, data) => {
- // Only retain the legends for the first selected benchmark. If
- // multiple benchmarks are selected, then use the legends of the
- // first selected benchmark to control all selected benchmarks.
- const numUniqueLegends =
- data.datasets.length / selectedBenchmarks.size;
- return legendItem.datasetIndex < numUniqueLegends;
- },
- },
- onClick: (e, legendItem, legend) => {
- const clickedLegend = legendItem.text;
- if (selectedLegends.has(clickedLegend)) {
- selectedLegends.delete(clickedLegend);
- } else {
- selectedLegends.add(clickedLegend);
- }
- updateChart(false, true);
- },
- },
- tooltip: {
- callbacks: {
- title: context => {
- const elementInfo = context[0];
- var commit;
- if (elementInfo.dataset.type == 'line') {
- commit = commits[zoom.left + elementInfo.dataIndex];
- } else {
- console.assert(elementInfo.dataset.type == 'scatter');
- commit = commits[elementInfo.raw.x];
- }
- return commit.title;
- },
- footer: context => {
- const elementInfo = context[0];
- var commit;
- if (elementInfo.dataset.type == 'line') {
- commit = commits[zoom.left + elementInfo.dataIndex];
- } else {
- console.assert(elementInfo.dataset.type == 'scatter');
- commit = commits[elementInfo.raw.x];
- }
- const dataset = myChart.data.datasets[elementInfo.datasetIndex];
- return `App: ${dataset.benchmark}\n`
- + `Author: ${commit.author}\n`
- + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n`
- + `Hash: ${commit.hash}\n`
- + `Index: ${commit.index}`;
- }
- }
- }
- },
- responsive: true,
- scales: {
- x: {},
- y: {
- position: 'left',
- title: {
- display: true,
- text: 'Dex size (bytes)'
- }
- },
- y_runtime: {
- position: 'right',
- title: {
- display: true,
- text: 'Runtime (seconds)'
- }
- },
- y_ins_code_size: {
- position: 'left',
- title: {
- display: true,
- text: 'Instruction size (bytes)'
- }
- },
- y_oat_code_size: {
- position: 'left',
- title: {
- display: true,
- text: 'Oat size (bytes)'
- }
- }
- }
- };
-
- // Setup click handler.
- canvas.onclick = event => {
- const points =
- myChart.getElementsAtEventForMode(
- event, 'nearest', { intersect: true }, true);
- if (points.length > 0) {
- const point = points[0];
- const commit = commits[point.index];
- window.open('https://r8.googlesource.com/r8/+/' + commit.hash, '_blank');
- }
- };
-
- // Setup chart navigation.
- var zoom = { left: Math.max(0, commits.length - 75), right: commits.length };
- for (const urlOption of urlOptions.values()) {
- if (urlOption.startsWith('L')) {
- var left = parseInt(urlOption.substring(1));
- if (isNaN(left)) {
- continue;
- }
- left = left >= 0 ? left : commits.length + left;
- if (left < 0) {
- zoom.left = 0;
- } else if (left >= commits.length) {
- zoom.left = commits.length - 1;
- } else {
- zoom.left = left;
- }
- }
- }
-
- showMoreLeft.onclick = event => {
- if (zoom.left == 0) {
- return;
- }
- const currentSize = zoom.right - zoom.left;
- zoom.left = zoom.left - currentSize;
- if (zoom.left < 0) {
- zoom.left = 0;
- }
- updateChart(true, false);
- };
-
- showLessLeft.onclick = event => {
- const currentSize = zoom.right - zoom.left;
- zoom.left = zoom.left + Math.floor(currentSize / 2);
- if (zoom.left >= zoom.right) {
- zoom.left = zoom.right - 1;
- }
- updateChart(true, false);
- };
-
- showLessRight.onclick = event => {
- if (zoom.right == 0) {
- return;
- }
- const currentSize = zoom.right - zoom.left;
- zoom.right = zoom.right - Math.floor(currentSize / 2);
- if (zoom.right < zoom.left) {
- zoom.right = zoom.left;
- }
- updateChart(true, false);
- };
-
- showMoreRight.onclick = event => {
- const currentSize = zoom.right - zoom.left;
- zoom.right = zoom.right + currentSize;
- if (zoom.right > commits.length) {
- zoom.right = commits.length;
- }
- updateChart(true, false);
- };
-
- function updateChart(dataChanged, legendsChanged) {
- console.assert(zoom.left <= zoom.right);
-
- // Update datasets.
- if (dataChanged) {
- const newData = getData();
- Object.assign(myChart.data, newData);
- // Update chart.
- myChart.update();
- }
-
- // Update legends.
- if (legendsChanged || (dataChanged && selectedLegends.size < legends.size)) {
- for (var datasetIndex = 0;
- datasetIndex < myChart.data.datasets.length;
- datasetIndex++) {
- const datasetMeta = myChart.getDatasetMeta(datasetIndex);
- datasetMeta.hidden = !selectedLegends.has(datasetMeta.label);
- }
-
- // Update scales.
- options.scales.y.display = selectedLegends.has('Dex size');
- options.scales.y_ins_code_size.display =
- selectedLegends.has('Instruction size') || selectedLegends.has('Composable size');
- options.scales.y_oat_code_size.display = selectedLegends.has('Oat size');
- options.scales.y_runtime.display =
- selectedLegends.has('Runtime') || selectedLegends.has('Runtime variance');
-
- // Update chart.
- myChart.update();
- }
-
-
- // Update checkbox colors.
- const benchmarkColors = {};
- for (var datasetIndex = 0;
- datasetIndex < myChart.data.datasets.length;
- datasetIndex++) {
- if (myChart.getDatasetMeta(datasetIndex).hidden) {
- continue;
- }
- const dataset = myChart.data.datasets[datasetIndex];
- const benchmark = dataset.benchmark;
- const benchmarkColor = dataset.borderColor;
- if (!(benchmark in benchmarkColors)) {
- benchmarkColors[benchmark] = benchmarkColor;
- }
- }
- for (const benchmark of benchmarks.values()) {
- const benchmarkLabel = document.getElementById(benchmark + 'Label');
- const benchmarkColor = benchmarkColors[benchmark] || '#000000';
- const benchmarkFontWeight = benchmark in benchmarkColors ? 'bold' : 'normal';
- benchmarkLabel.style.color = benchmarkColor;
- benchmarkLabel.style.fontWeight = benchmarkFontWeight;
- }
-
- // Update navigation.
- showMoreLeft.disabled = zoom.left == 0;
- showLessLeft.disabled = zoom.left == zoom.right - 1;
- showLessRight.disabled = zoom.left == zoom.right - 1;
- showMoreRight.disabled = zoom.right == commits.length;
-
- // Update hash.
- window.location.hash =
- Array.from(selectedBenchmarks)
- .concat(
- selectedLegends.size == legends.size
- ? []
- : Array.from(selectedLegends))
- .join(',');
- }
-
- // Create chart.
- const myChart = new Chart(canvas, {
- data: getData(),
- options: options,
- plugins: [ChartDataLabels]
- });
-
- // Hide disabled legends.
- if (selectedLegends.size < legends.size) {
- updateChart(false, true);
- } else {
- updateChart(false, false);
- }
- </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/tools/perf/r8.html b/tools/perf/r8.html
new file mode 100644
index 0000000..a7da848
--- /dev/null
+++ b/tools/perf/r8.html
@@ -0,0 +1,336 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>R8 perf</title>
+ <link rel="stylesheet" href="stylesheet.css">
+</head>
+<body>
+ <div id="benchmark-selectors"></div>
+ <div>
+ <canvas id="myChart"></canvas>
+ </div>
+ <div>
+ <div style="float: left; width: 50%">
+ <button type="button" id="show-more-left" disabled>⇐</button>
+ <button type="button" id="show-less-left">⇒</button>
+ </div>
+ <div style="float: left; text-align: right; width: 50%">
+ <button type="button" id="show-less-right">⇐</button>
+ <button type="button" id="show-more-right" disabled>⇒</button>
+ </div>
+ </div>
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>
+ <script src="extensions.js"></script>
+ <script src="utils.js"></script>
+ <script type="module">
+ import chart from "./chart.js";
+ import dom from "./dom.js";
+ import scales from "./scales.js";
+ import state from "./state.js";
+
+ const commits = await state.importCommits("./r8_benchmark_data.json");
+ state.initializeBenchmarks();
+ state.initializeLegends({
+ 'Dex size': { default: true },
+ 'Instruction size': { default: true },
+ 'Composable size': { default: true },
+ 'Oat size': { default: true },
+ 'Nondeterminism': { default: true },
+ 'Runtime': { default: true },
+ 'Runtime variance': { default: false }
+ });
+ state.initializeZoom();
+ dom.initializeBenchmarkSelectors();
+ dom.initializeChartNavigation();
+
+ // Chart data provider.
+ function getData(filteredCommits) {
+ const labels = filteredCommits.map((c, i) => c.index);
+ const datasets = getDatasets(filteredCommits);
+ return {
+ labels: labels,
+ datasets: datasets
+ };
+ }
+
+ function getDatasets(filteredCommits) {
+ const datasets = [];
+ state.forEachSelectedBenchmark(
+ selectedBenchmark => {
+ const codeSizeData =
+ filteredCommits.map(
+ (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "code_size"));
+ const instructionCodeSizeData =
+ filteredCommits.map(
+ (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "ins_code_size"));
+ const composableInstructionCodeSizeData =
+ filteredCommits.map(
+ (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "composable_code_size"));
+ const oatCodeSizeData =
+ filteredCommits.map(
+ (c, i) => getSingleResult(selectedBenchmark, filteredCommits[i], "oat_code_size"));
+ const codeSizeScatterData = [];
+ for (const commit of filteredCommits.values()) {
+ if (!(selectedBenchmark in commit.benchmarks)) {
+ continue;
+ }
+ const seen = new Set();
+ seen.add(getSingleResult(selectedBenchmark, commit, "code_size"));
+ const codeSizes = getAllResults(selectedBenchmark, commit, "code_size")
+ for (const codeSize of codeSizes.values()) {
+ if (!seen.has(codeSize)) {
+ codeSizeScatterData.push({ x: commit.index, y: codeSize });
+ seen.add(codeSize);
+ }
+ }
+ }
+ const runtimeData =
+ filteredCommits.map(
+ (c, i) =>
+ selectedBenchmark in filteredCommits[i].benchmarks
+ ? getAllResults(selectedBenchmark, filteredCommits[i], "runtime")
+ .min()
+ .ns_to_s()
+ : NaN);
+ const runtimeScatterData = [];
+ for (const commit of filteredCommits.values()) {
+ if (!(selectedBenchmark in commit.benchmarks)) {
+ continue;
+ }
+ const runtimes = getAllResults(selectedBenchmark, commit, "runtime")
+ for (const runtime of runtimes.values()) {
+ runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
+ }
+ }
+
+ const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;
+ datasets.push(...[
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Dex size',
+ data: codeSizeData,
+ datalabels: {
+ align: 'end',
+ anchor: 'end'
+ },
+ tension: 0.1,
+ yAxisID: 'y',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Instruction size',
+ data: instructionCodeSizeData,
+ datalabels: {
+ align: 'end',
+ anchor: 'end'
+ },
+ tension: 0.1,
+ yAxisID: 'y_ins_code_size',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Composable size',
+ data: composableInstructionCodeSizeData,
+ datalabels: {
+ align: 'start',
+ anchor: 'start'
+ },
+ tension: 0.1,
+ yAxisID: 'y_ins_code_size',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Oat size',
+ data: oatCodeSizeData,
+ datalabels: {
+ align: 'start',
+ anchor: 'start'
+ },
+ tension: 0.1,
+ yAxisID: 'y_oat_code_size',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'scatter',
+ label: 'Nondeterminism',
+ data: codeSizeScatterData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ radius: 6,
+ pointBackgroundColor: 'red'
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Runtime',
+ data: runtimeData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ tension: 0.1,
+ yAxisID: 'y_runtime',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'scatter',
+ label: 'Runtime variance',
+ data: runtimeScatterData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ yAxisID: 'y_runtime'
+ }
+ ]);
+ });
+ return datasets;
+ }
+
+ // Chart options.
+ const options = {
+ onHover: (event, chartElement) =>
+ event.native.target.style.cursor =
+ chartElement[0] ? 'pointer' : 'default',
+ plugins: {
+ datalabels: {
+ backgroundColor: 'rgba(255, 255, 255, 0.7)',
+ borderColor: 'rgba(128, 128, 128, 0.7)',
+ borderRadius: 4,
+ borderWidth: 1,
+ color: context => chart.getDataPercentageChange(context) < 0 ? 'green' : 'red',
+ display: context => {
+ var percentageChange = chart.getDataPercentageChange(context);
+ return percentageChange !== null && Math.abs(percentageChange) >= 0.1;
+ },
+ font: {
+ size: 20,
+ weight: 'bold'
+ },
+ offset: 8,
+ formatter: chart.getDataLabelFormatter,
+ padding: 6
+ },
+ legend: {
+ labels: {
+ filter: (legendItem, data) => {
+ // Only retain the legends for the first selected benchmark. If
+ // multiple benchmarks are selected, then use the legends of the
+ // first selected benchmark to control all selected benchmarks.
+ const numUniqueLegends =
+ data.datasets.length / state.selectedBenchmarks.size;
+ return legendItem.datasetIndex < numUniqueLegends;
+ },
+ },
+ onClick: (e, legendItem, legend) => {
+ const clickedLegend = legendItem.text;
+ if (state.selectedLegends.has(clickedLegend)) {
+ state.selectedLegends.delete(clickedLegend);
+ } else {
+ state.selectedLegends.add(clickedLegend);
+ }
+ chart.update(false, true);
+ },
+ },
+ tooltip: {
+ callbacks: {
+ title: context => {
+ const elementInfo = context[0];
+ var commit;
+ if (elementInfo.dataset.type == 'line') {
+ commit = commits[state.zoom.left + elementInfo.dataIndex];
+ } else {
+ console.assert(elementInfo.dataset.type == 'scatter');
+ commit = commits[elementInfo.raw.x];
+ }
+ return commit.title;
+ },
+ footer: context => {
+ const elementInfo = context[0];
+ var commit;
+ if (elementInfo.dataset.type == 'line') {
+ commit = commits[state.zoom.left + elementInfo.dataIndex];
+ } else {
+ console.assert(elementInfo.dataset.type == 'scatter');
+ commit = commits[elementInfo.raw.x];
+ }
+ const dataset = chart.get().data.datasets[elementInfo.datasetIndex];
+ return `App: ${dataset.benchmark}\n`
+ + `Author: ${commit.author}\n`
+ + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n`
+ + `Hash: ${commit.hash}\n`
+ + `Index: ${commit.index}`;
+ }
+ }
+ }
+ },
+ responsive: true,
+ scales: scales.get()
+ };
+
+ chart.setDataProvider(getData);
+ chart.initializeChart(options);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/tools/perf/retrace.html b/tools/perf/retrace.html
new file mode 100644
index 0000000..98a6661
--- /dev/null
+++ b/tools/perf/retrace.html
@@ -0,0 +1,203 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Retrace perf</title>
+ <link rel="stylesheet" href="stylesheet.css">
+</head>
+<body>
+ <div id="benchmark-selectors"></div>
+ <div>
+ <canvas id="myChart"></canvas>
+ </div>
+ <div>
+ <div style="float: left; width: 50%">
+ <button type="button" id="show-more-left" disabled>⇐</button>
+ <button type="button" id="show-less-left">⇒</button>
+ </div>
+ <div style="float: left; text-align: right; width: 50%">
+ <button type="button" id="show-less-right">⇐</button>
+ <button type="button" id="show-more-right" disabled>⇒</button>
+ </div>
+ </div>
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>
+ <script src="extensions.js"></script>
+ <script src="utils.js"></script>
+ <script type="module">
+ import chart from "./chart.js";
+ import dom from "./dom.js";
+ import scales from "./scales.js";
+ import state from "./state.js";
+
+ const commits = await state.importCommits("./retrace_benchmark_data.json");
+ state.initializeBenchmarks();
+ state.initializeLegends({
+ 'Runtime': { default: true },
+ 'Runtime variance': { default: true }
+ });
+ state.initializeZoom();
+ dom.initializeBenchmarkSelectors();
+ dom.initializeChartNavigation();
+
+ // Chart data provider.
+ function getData(filteredCommits) {
+ const labels = filteredCommits.map((c, i) => c.index);
+ const datasets = getDatasets(filteredCommits);
+ return {
+ labels: labels,
+ datasets: datasets
+ };
+ }
+
+ function getDatasets(filteredCommits) {
+ const datasets = [];
+ state.forEachSelectedBenchmark(
+ selectedBenchmark => {
+ const runtimeData =
+ filteredCommits.map(
+ (c, i) =>
+ selectedBenchmark in filteredCommits[i].benchmarks
+ ? getAllResults(selectedBenchmark, filteredCommits[i], "runtime")
+ .min()
+ .ns_to_s()
+ : NaN);
+ const runtimeScatterData = [];
+ for (const commit of filteredCommits.values()) {
+ if (!(selectedBenchmark in commit.benchmarks)) {
+ continue;
+ }
+ const runtimes = getAllResults(selectedBenchmark, commit, "runtime")
+ for (const runtime of runtimes.values()) {
+ runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
+ }
+ }
+
+ const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;
+ datasets.push(...[
+ {
+ benchmark: selectedBenchmark,
+ type: 'line',
+ label: 'Runtime',
+ data: runtimeData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ tension: 0.1,
+ yAxisID: 'y_runtime',
+ segment: {
+ borderColor: ctx =>
+ skipped(
+ ctx,
+ chart.get()
+ ? chart.get().data.datasets[ctx.datasetIndex].backgroundColor
+ : undefined),
+ borderDash: ctx => skipped(ctx, [6, 6]),
+ },
+ spanGaps: true
+ },
+ {
+ benchmark: selectedBenchmark,
+ type: 'scatter',
+ label: 'Runtime variance',
+ data: runtimeScatterData,
+ datalabels: {
+ labels: {
+ value: null
+ }
+ },
+ yAxisID: 'y_runtime'
+ }
+ ]);
+ });
+ return datasets;
+ }
+
+ // Chart options.
+ const options = {
+ onHover: (event, chartElement) =>
+ event.native.target.style.cursor =
+ chartElement[0] ? 'pointer' : 'default',
+ plugins: {
+ datalabels: {
+ backgroundColor: 'rgba(255, 255, 255, 0.7)',
+ borderColor: 'rgba(128, 128, 128, 0.7)',
+ borderRadius: 4,
+ borderWidth: 1,
+ color: context => chart.getDataPercentageChange(context) < 0 ? 'green' : 'red',
+ display: context => {
+ var percentageChange = chart.getDataPercentageChange(context);
+ return percentageChange !== null && Math.abs(percentageChange) >= 0.1;
+ },
+ font: {
+ size: 20,
+ weight: 'bold'
+ },
+ offset: 8,
+ formatter: chart.getDataLabelFormatter,
+ padding: 6
+ },
+ legend: {
+ labels: {
+ filter: (legendItem, data) => {
+ // Only retain the legends for the first selected benchmark. If
+ // multiple benchmarks are selected, then use the legends of the
+ // first selected benchmark to control all selected benchmarks.
+ const numUniqueLegends =
+ data.datasets.length / state.selectedBenchmarks.size;
+ return legendItem.datasetIndex < numUniqueLegends;
+ },
+ },
+ onClick: (e, legendItem, legend) => {
+ const clickedLegend = legendItem.text;
+ if (state.selectedLegends.has(clickedLegend)) {
+ state.selectedLegends.delete(clickedLegend);
+ } else {
+ state.selectedLegends.add(clickedLegend);
+ }
+ chart.update(false, true);
+ },
+ },
+ tooltip: {
+ callbacks: {
+ title: context => {
+ const elementInfo = context[0];
+ var commit;
+ if (elementInfo.dataset.type == 'line') {
+ commit = commits[state.zoom.left + elementInfo.dataIndex];
+ } else {
+ console.assert(elementInfo.dataset.type == 'scatter');
+ commit = commits[elementInfo.raw.x];
+ }
+ return commit.title;
+ },
+ footer: context => {
+ const elementInfo = context[0];
+ var commit;
+ if (elementInfo.dataset.type == 'line') {
+ commit = commits[state.zoom.left + elementInfo.dataIndex];
+ } else {
+ console.assert(elementInfo.dataset.type == 'scatter');
+ commit = commits[elementInfo.raw.x];
+ }
+ const dataset = chart.get().data.datasets[elementInfo.datasetIndex];
+ return `App: ${dataset.benchmark}\n`
+ + `Author: ${commit.author}\n`
+ + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n`
+ + `Hash: ${commit.hash}\n`
+ + `Index: ${commit.index}`;
+ }
+ }
+ }
+ },
+ responsive: true,
+ scales: scales.get()
+ };
+
+ chart.setDataProvider(getData);
+ chart.initializeChart(options);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/tools/perf/scales.js b/tools/perf/scales.js
new file mode 100644
index 0000000..8378e2f
--- /dev/null
+++ b/tools/perf/scales.js
@@ -0,0 +1,72 @@
+// 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.
+import state from "./state.js";
+
+function get() {
+ const scales = {};
+ scales.x = {};
+ if (state.hasLegend('Dex size')) {
+ scales.y = {
+ position: 'left',
+ title: {
+ display: true,
+ text: 'Dex size (bytes)'
+ }
+ };
+ } else {
+ console.assert(!state.hasLegend('Instruction size'));
+ console.assert(!state.hasLegend('Composable size'));
+ console.assert(!state.hasLegend('Oat size'));
+ }
+ console.assert(state.hasLegend('Runtime'));
+ console.assert(state.hasLegend('Runtime variance'));
+ scales.y_runtime = {
+ position: state.hasLegend('Dex size') ? 'right' : 'left',
+ title: {
+ display: true,
+ text: 'Runtime (seconds)'
+ }
+ };
+ if (state.hasLegend('Instruction size') || state.hasLegend('Composable size')) {
+ scales.y_ins_code_size = {
+ position: 'left',
+ title: {
+ display: true,
+ text: 'Instruction size (bytes)'
+ }
+ };
+ }
+ if (state.hasLegend('Oat size')) {
+ scales.y_oat_code_size = {
+ position: 'left',
+ title: {
+ display: true,
+ text: 'Oat size (bytes)'
+ }
+ };
+ }
+ return scales;
+}
+
+function update(scales) {
+ if (scales.y) {
+ scales.y.display = state.isLegendSelected('Dex size');
+ }
+ if (scales.y_ins_code_size) {
+ scales.y_ins_code_size.display =
+ state.isLegendSelected('Instruction size') || state.isLegendSelected('Composable size');
+ }
+ if (scales.y_oat_code_size) {
+ scales.y_oat_code_size.display = state.isLegendSelected('Oat size');
+ }
+ if (scales.y_runtime) {
+ scales.y_runtime.display =
+ state.isLegendSelected('Runtime') || state.isLegendSelected('Runtime variance');
+ }
+}
+
+export default {
+ get: get,
+ update: update
+};
\ No newline at end of file
diff --git a/tools/perf/state.js b/tools/perf/state.js
new file mode 100644
index 0000000..4d0128f
--- /dev/null
+++ b/tools/perf/state.js
@@ -0,0 +1,121 @@
+// 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.
+import url from "./url.js";
+
+var commits = null;
+
+const benchmarks = new Set();
+const selectedBenchmarks = new Set();
+
+const legends = new Set();
+const selectedLegends = new Set();
+
+const zoom = { left: -1, right: -1 };
+
+function forEachBenchmark(callback) {
+ for (const benchmark of benchmarks.values()) {
+ callback(benchmark, selectedBenchmarks.has(benchmark));
+ }
+}
+
+function forEachSelectedBenchmark(callback) {
+ forEachBenchmark((benchmark, selected) => {
+ if (selected) {
+ callback(benchmark);
+ }
+ });
+}
+
+function hasLegend(legend) {
+ return legends.has(legend);
+}
+
+function importCommits(url) {
+ return import(url, { with: { type: "json" }})
+ .then(module => {
+ commits = module.default;
+ commits.reverseInPlace();
+ // Amend the commits with their unique index.
+ for (var i = 0; i < commits.length; i++) {
+ commits[i].index = i;
+ }
+ return commits;
+ });
+}
+
+function initializeBenchmarks() {
+ for (const commit of commits.values()) {
+ for (const benchmark in commit.benchmarks) {
+ benchmarks.add(benchmark);
+ }
+ }
+ for (const benchmark of benchmarks.values()) {
+ if (url.matches(benchmark)) {
+ selectedBenchmarks.add(benchmark);
+ }
+ }
+ if (selectedBenchmarks.size == 0) {
+ const randomBenchmarkIndex = Math.floor(Math.random() * benchmarks.size);
+ const randomBenchmark = Array.from(benchmarks)[randomBenchmarkIndex];
+ selectedBenchmarks.add(randomBenchmark);
+ }
+}
+
+function initializeLegends(legendsInfo) {
+ for (var legend in legendsInfo) {
+ legends.add(legend);
+ if (url.contains(legend)) {
+ selectedLegends.add(legend);
+ }
+ }
+ if (selectedLegends.size == 0) {
+ for (let [legend, legendInfo] of Object.entries(legendsInfo)) {
+ if (legendInfo.default) {
+ selectedLegends.add(legend);
+ }
+ }
+ }
+}
+
+function initializeZoom() {
+ zoom.left = Math.max(0, commits.length - 75);
+ zoom.right = commits.length;
+ for (const urlOption of url.values()) {
+ if (urlOption.startsWith('L')) {
+ var left = parseInt(urlOption.substring(1));
+ if (isNaN(left)) {
+ continue;
+ }
+ left = left >= 0 ? left : commits.length + left;
+ if (left < 0) {
+ zoom.left = 0;
+ } else if (left >= commits.length) {
+ zoom.left = commits.length - 1;
+ } else {
+ zoom.left = left;
+ }
+ }
+ }
+}
+
+function isLegendSelected(legend) {
+ return selectedLegends.has(legend);
+}
+
+export default {
+ benchmarks: benchmarks,
+ commits: zoom => zoom ? commits.slice(zoom.left, zoom.right) : commits,
+ legends: legends,
+ selectedBenchmarks: selectedBenchmarks,
+ selectedLegends: selectedLegends,
+ forEachBenchmark: forEachBenchmark,
+ forEachSelectedBenchmark: forEachSelectedBenchmark,
+ hasLegend: hasLegend,
+ initializeBenchmarks: initializeBenchmarks,
+ initializeLegends: initializeLegends,
+ initializeZoom: initializeZoom,
+ importCommits: importCommits,
+ isLegendSelected: isLegendSelected,
+ zoom: zoom
+};
diff --git a/tools/perf/url.js b/tools/perf/url.js
new file mode 100644
index 0000000..f00a2f77
--- /dev/null
+++ b/tools/perf/url.js
@@ -0,0 +1,41 @@
+// 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.
+const options = unescape(window.location.hash.substring(1)).split(',');
+
+function contains(subject) {
+ return options.includes(subject);
+}
+
+function matches(subject) {
+ for (const filter of options.values()) {
+ if (filter) {
+ const match = subject.match(new RegExp(filter.replace("*", ".*")));
+ if (match) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function updateHash(state) {
+ window.location.hash =
+ Array.from(state.selectedBenchmarks)
+ .concat(
+ state.selectedLegends.size == state.legends.size
+ ? []
+ : Array.from(state.selectedLegends))
+ .join(',');
+}
+
+function values() {
+ return options;
+}
+
+export default {
+ contains: contains,
+ matches: matches,
+ updateHash: updateHash,
+ values: values
+};
\ No newline at end of file
diff --git a/tools/perf/utils.js b/tools/perf/utils.js
new file mode 100644
index 0000000..46890d0
--- /dev/null
+++ b/tools/perf/utils.js
@@ -0,0 +1,25 @@
+// 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.
+function getSingleResult(benchmark, commit, resultName, resultIteration = 0) {
+ if (!(benchmark in commit.benchmarks)) {
+ return NaN;
+ }
+ const allResults = commit.benchmarks[benchmark].results;
+ const resultsForIteration = allResults[resultIteration];
+ // If a given iteration does not declare a result, then the result
+ // was the same as the first run.
+ if (resultIteration > 0 && !(resultName in resultsForIteration)) {
+ return allResults.first()[resultName];
+ }
+ return resultsForIteration[resultName];
+}
+
+function getAllResults(benchmark, commit, resultName) {
+ const result = [];
+ const allResults = commit.benchmarks[benchmark].results;
+ for (var iteration = 0; iteration < allResults.length; iteration++) {
+ result.push(getSingleResult(benchmark, commit, resultName, iteration));
+ }
+ return result;
+}
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
index 783e695..436c046 100755
--- a/tools/run_benchmark.py
+++ b/tools/run_benchmark.py
@@ -47,7 +47,7 @@
help='The test target to run',
required=True,
# These should 1:1 with benchmarks/BenchmarkTarget.java
- choices=['d8', 'r8-full', 'r8-force', 'r8-compat'])
+ choices=['d8', 'r8-full', 'r8-force', 'r8-compat', 'retrace'])
result.add_argument(
'--debug-agent',
'--debug_agent',
@@ -165,8 +165,11 @@
def run(options, r8jar, testjars):
jdkhome = get_jdk_home(options, options.benchmark)
cmd = [
- jdk.GetJavaExecutable(jdkhome), '-Xms8g', '-Xmx8g',
- '-XX:+TieredCompilation', '-XX:TieredStopAtLevel=4',
+ jdk.GetJavaExecutable(jdkhome),
+ '-Xms8g',
+ '-Xmx8g',
+ '-XX:+TieredCompilation',
+ '-XX:TieredStopAtLevel=4',
'-DBENCHMARK_IGNORE_CODE_SIZE_DIFFERENCES',
f'-DBUILD_PROP_KEEPANNO_RUNTIME_PATH={utils.REPO_ROOT}/d8_r8/keepanno/build/classes/java/main',
# Since we change the working directory to a temp folder.
diff --git a/tools/test.py b/tools/test.py
index aa63a0f..16bb545 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -264,6 +264,10 @@
help='Pass --no-daemon to the gradle run',
default=False,
action='store_true')
+ result.add_argument('--low-priority',
+ help='Run gradle with priority=low (higher niceness)',
+ default=False,
+ action='store_true')
result.add_argument(
'--kotlin-compiler-dev',
help='Specify to download a kotlin dev compiler and run '
@@ -384,6 +388,9 @@
# Bots don't like dangling processes.
gradle_args.append('--no-daemon')
+ if options.low_priority:
+ gradle_args.append('--priority=low')
+
# Set all necessary Gradle properties and options first.
if options.shard_count:
assert options.shard_number
diff --git a/tools/upload_benchmark_data_to_google_storage.py b/tools/upload_benchmark_data_to_google_storage.py
index f293c2c..f979010 100755
--- a/tools/upload_benchmark_data_to_google_storage.py
+++ b/tools/upload_benchmark_data_to_google_storage.py
@@ -12,11 +12,14 @@
import sys
-APPS = perf.APPS
+BENCHMARKS = perf.BENCHMARKS
TARGETS = ['r8-full']
NUM_COMMITS = 1000
-INDEX_HTML = os.path.join(utils.TOOLS_DIR, 'perf/index.html')
+FILES = [
+ 'chart.js', 'd8.html', 'dom.js', 'extensions.js', 'r8.html', 'retrace.html',
+ 'scales.js', 'state.js', 'stylesheet.css', 'url.js', 'utils.js'
+]
def DownloadCloudBucket(dest):
@@ -40,6 +43,51 @@
return None
+def RecordBenchmarkResult(commit, benchmark, benchmark_info, local_bucket,
+ target, benchmarks):
+ if not target in benchmark_info['targets']:
+ return
+ filename = perf.GetArtifactLocation(benchmark, target, commit.hash(),
+ 'result.json')
+ benchmark_data = ParseJsonFromCloudStorage(filename, local_bucket)
+ if benchmark_data:
+ benchmarks[benchmark] = benchmark_data
+
+
+def RecordBenchmarkResults(commit, benchmarks, benchmark_data):
+ if benchmarks or benchmark_data:
+ benchmark_data.append({
+ 'author': commit.author_name(),
+ 'hash': commit.hash(),
+ 'submitted': commit.committer_timestamp(),
+ 'title': commit.title(),
+ 'benchmarks': benchmarks
+ })
+
+
+def TrimBenchmarkResults(benchmark_data):
+ new_benchmark_data_len = len(benchmark_data)
+ while new_benchmark_data_len > 0:
+ candidate_len = new_benchmark_data_len - 1
+ if not benchmark_data[candidate_len]['benchmarks']:
+ new_benchmark_data_len = candidate_len
+ else:
+ break
+ return benchmark_data[0:new_benchmark_data_len]
+
+
+def ArchiveBenchmarkResults(benchmark_data, dest, temp):
+ # Serialize JSON to temp file.
+ benchmark_data_file = os.path.join(temp, dest)
+ with open(benchmark_data_file, 'w') as f:
+ json.dump(benchmark_data, f)
+
+ # Write output files to public bucket.
+ perf.ArchiveOutputFile(benchmark_data_file,
+ dest,
+ header='Cache-Control:no-store')
+
+
def run():
# Get the N most recent commits sorted by newest first.
top = utils.get_sha1_from_revision('origin/main')
@@ -52,49 +100,45 @@
local_bucket = os.path.join(temp, perf.BUCKET)
DownloadCloudBucket(local_bucket)
- # Aggregate all the result.json files into a single benchmark_data.json file
- # that has the same format as tools/perf/benchmark_data.json.
- benchmark_data = []
+ # Aggregate all the result.json files into a single file that has the
+ # same format as tools/perf/benchmark_data.json.
+ d8_benchmark_data = []
+ r8_benchmark_data = []
+ retrace_benchmark_data = []
for commit in commits:
- benchmarks = {}
- for app in APPS:
- for target in TARGETS:
- filename = perf.GetArtifactLocation(app, target,
- commit.hash(),
- 'result.json')
- app_benchmark_data = ParseJsonFromCloudStorage(
- filename, local_bucket)
- if app_benchmark_data:
- benchmarks[app] = app_benchmark_data
- if benchmarks or benchmark_data:
- benchmark_data.append({
- 'author': commit.author_name(),
- 'hash': commit.hash(),
- 'submitted': commit.committer_timestamp(),
- 'title': commit.title(),
- 'benchmarks': benchmarks
- })
+ d8_benchmarks = {}
+ r8_benchmarks = {}
+ retrace_benchmarks = {}
+ for benchmark, benchmark_info in BENCHMARKS.items():
+ RecordBenchmarkResult(commit, benchmark, benchmark_info,
+ local_bucket, 'd8', d8_benchmarks)
+ RecordBenchmarkResult(commit, benchmark, benchmark_info,
+ local_bucket, 'r8-full', r8_benchmarks)
+ RecordBenchmarkResult(commit, benchmark, benchmark_info,
+ local_bucket, 'retrace',
+ retrace_benchmarks)
+ RecordBenchmarkResults(commit, d8_benchmarks, d8_benchmark_data)
+ RecordBenchmarkResults(commit, r8_benchmarks, r8_benchmark_data)
+ RecordBenchmarkResults(commit, retrace_benchmarks,
+ retrace_benchmark_data)
# Trim data.
- new_benchmark_data_len = len(benchmark_data)
- while new_benchmark_data_len > 0:
- candidate_len = new_benchmark_data_len - 1
- if not benchmark_data[candidate_len]['benchmarks']:
- new_benchmark_data_len = candidate_len
- else:
- break
- benchmark_data = benchmark_data[0:new_benchmark_data_len]
-
- # Serialize JSON to temp file.
- benchmark_data_file = os.path.join(temp, 'benchmark_data.json')
- with open(benchmark_data_file, 'w') as f:
- json.dump(benchmark_data, f)
+ d8_benchmark_data = TrimBenchmarkResults(d8_benchmark_data)
+ r8_benchmark_data = TrimBenchmarkResults(r8_benchmark_data)
+ retrace_benchmark_data = TrimBenchmarkResults(retrace_benchmark_data)
# Write output files to public bucket.
- perf.ArchiveOutputFile(benchmark_data_file,
- 'benchmark_data.json',
- header='Cache-Control:no-store')
- perf.ArchiveOutputFile(INDEX_HTML, 'index.html')
+ ArchiveBenchmarkResults(d8_benchmark_data, 'd8_benchmark_data.json',
+ temp)
+ ArchiveBenchmarkResults(r8_benchmark_data, 'r8_benchmark_data.json',
+ temp)
+ ArchiveBenchmarkResults(retrace_benchmark_data,
+ 'retrace_benchmark_data.json', temp)
+
+ # Write remaining files to public bucket.
+ for file in FILES:
+ dest = os.path.join(utils.TOOLS_DIR, 'perf', file)
+ perf.ArchiveOutputFile(dest, file)
def main():