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():