diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2017-07-19 07:24:29 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2017-07-19 07:24:29 +0000 |
commit | be69fcefad315b2068a9903019108f879ca4db56 (patch) | |
tree | 9749176d782b4a256f9d71fb30007b81dceca872 | |
parent | 5a59612a39e9a2b27cbacc2e4a252227be9aed35 (diff) | |
parent | 8c5f281882ee39c2c31608cc1267c57e3f403949 (diff) | |
download | r8-oreo-m2-s2-release.tar.gz |
release-request-bd6aa7dd-7b02-4794-942c-14599bf61208-for-git_oc-mr1-release-4193791 snap-temp-L98700000083613807android-wear-8.1.0_r1android-vts-8.1_r9android-vts-8.1_r8android-vts-8.1_r7android-vts-8.1_r6android-vts-8.1_r5android-vts-8.1_r4android-vts-8.1_r3android-vts-8.1_r14android-vts-8.1_r13android-vts-8.1_r12android-vts-8.1_r11android-vts-8.1_r10android-security-8.1.0_r93android-security-8.1.0_r92android-security-8.1.0_r91android-security-8.1.0_r90android-security-8.1.0_r89android-security-8.1.0_r88android-security-8.1.0_r87android-security-8.1.0_r86android-security-8.1.0_r85android-security-8.1.0_r84android-security-8.1.0_r83android-security-8.1.0_r82android-cts-8.1_r9android-cts-8.1_r8android-cts-8.1_r7android-cts-8.1_r6android-cts-8.1_r5android-cts-8.1_r4android-cts-8.1_r3android-cts-8.1_r25android-cts-8.1_r24android-cts-8.1_r23android-cts-8.1_r22android-cts-8.1_r21android-cts-8.1_r20android-cts-8.1_r2android-cts-8.1_r19android-cts-8.1_r18android-cts-8.1_r17android-cts-8.1_r16android-cts-8.1_r15android-cts-8.1_r14android-cts-8.1_r13android-cts-8.1_r12android-cts-8.1_r11android-cts-8.1_r10android-cts-8.1_r1android-8.1.0_r9android-8.1.0_r81android-8.1.0_r80android-8.1.0_r8android-8.1.0_r79android-8.1.0_r78android-8.1.0_r77android-8.1.0_r76android-8.1.0_r75android-8.1.0_r74android-8.1.0_r73android-8.1.0_r72android-8.1.0_r71android-8.1.0_r70android-8.1.0_r7android-8.1.0_r69android-8.1.0_r68android-8.1.0_r67android-8.1.0_r66android-8.1.0_r65android-8.1.0_r64android-8.1.0_r63android-8.1.0_r62android-8.1.0_r61android-8.1.0_r60android-8.1.0_r6android-8.1.0_r53android-8.1.0_r52android-8.1.0_r51android-8.1.0_r50android-8.1.0_r5android-8.1.0_r48android-8.1.0_r47android-8.1.0_r46android-8.1.0_r45android-8.1.0_r43android-8.1.0_r42android-8.1.0_r41android-8.1.0_r40android-8.1.0_r4android-8.1.0_r39android-8.1.0_r38android-8.1.0_r37android-8.1.0_r36android-8.1.0_r35android-8.1.0_r33android-8.1.0_r32android-8.1.0_r31android-8.1.0_r30android-8.1.0_r3android-8.1.0_r29android-8.1.0_r28android-8.1.0_r27android-8.1.0_r26android-8.1.0_r25android-8.1.0_r23android-8.1.0_r22android-8.1.0_r21android-8.1.0_r20android-8.1.0_r2android-8.1.0_r19android-8.1.0_r18android-8.1.0_r17android-8.1.0_r16android-8.1.0_r15android-8.1.0_r14android-8.1.0_r13android-8.1.0_r12android-8.1.0_r11android-8.1.0_r10android-8.1.0_r1security-oc-mr1-releaseoreo-mr1-wear-releaseoreo-mr1-vts-releaseoreo-mr1-security-releaseoreo-mr1-s1-releaseoreo-mr1-releaseoreo-mr1-cuttlefish-testingoreo-mr1-cts-releaseoreo-m8-releaseoreo-m7-releaseoreo-m6-s4-releaseoreo-m6-s3-releaseoreo-m6-s2-releaseoreo-m5-releaseoreo-m4-s9-releaseoreo-m4-s8-releaseoreo-m4-s7-releaseoreo-m4-s6-releaseoreo-m4-s5-releaseoreo-m4-s4-releaseoreo-m4-s3-releaseoreo-m4-s2-releaseoreo-m4-s12-releaseoreo-m4-s11-releaseoreo-m4-s10-releaseoreo-m4-s1-releaseoreo-m3-releaseoreo-m2-s5-releaseoreo-m2-s4-releaseoreo-m2-s3-releaseoreo-m2-s2-releaseoreo-m2-s1-releaseoreo-m2-release
Change-Id: I614f22473f18fba375f3820bb3e69b8d49694de7
41 files changed, 665 insertions, 795 deletions
diff --git a/build.gradle b/build.gradle index 99c4865c6..58b0aafba 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,12 @@ // 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 org.gradle.internal.os.OperatingSystem -import utils.Utils; +import utils.Utils apply plugin: 'java' apply plugin: 'idea' apply plugin: 'jacoco' +apply plugin: 'com.google.protobuf' apply from: 'copyAdditionalJctfCommonFiles.gradle' @@ -14,6 +15,15 @@ repositories { mavenCentral() } +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1' + } +} + // Custom source set for example tests and generated tests. sourceSets { test { @@ -38,7 +48,12 @@ sourceSets { } examples { java { - srcDirs = ['src/test/examples'] + srcDirs = ['src/test/examples', 'build/generated/source/proto/examples/javalite/' ] + } + proto { + srcDirs = [ + 'src/test/examples', + ] } output.resourcesDir = 'build/classes/examples' } @@ -97,6 +112,31 @@ dependencies { jctfTestsCompile 'junit:junit:4.12' jctfTestsCompile sourceSets.jctfCommon.output examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '5.1' + examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0' + examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0' +} + +protobuf { + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.0.0' + } + plugins { + javalite { + artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' + } + } + generateProtoTasks { + all().each { task -> + task.builtins { + // Disable the java code generator, as we want javalite only. + remove java + } + task.plugins { + javalite {} + } + } + } } def osString = OperatingSystem.current().isLinux() ? "linux" : @@ -464,16 +504,26 @@ task buildDebugTestResourcesJars { task buildExampleJars { dependsOn downloadProguard def examplesDir = file("src/test/examples") + def protoSourceDir = file("build/generated/source/proto/examples/javalite") def proguardScript if (OperatingSystem.current().isWindows()) { proguardScript = "third_party/proguard/proguard5.2.1/bin/proguard.bat" } else { proguardScript = "third_party/proguard/proguard5.2.1/bin/proguard.sh" } - task "compile_examples"(type: JavaCompile) { - source = fileTree(dir: examplesDir, include: '**/*.java') + task extractExamplesRuntime(type: Sync) { + dependsOn configurations.examplesRuntime + from configurations.examplesRuntime.collect { zipTree(it) } + include "**/*.class" + includeEmptyDirs false + into "$buildDir/runtime/examples/" + } + + task "compile_examples"(type: JavaCompile, dependsOn: "generateExamplesProto") { + source examplesDir, protoSourceDir + include "**/*.java" destinationDir = file("build/test/examples/classes") - classpath = sourceSets.main.compileClasspath + classpath = sourceSets.examples.compileClasspath sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 options.compilerArgs += ["-Xlint:-options"] @@ -483,6 +533,15 @@ task buildExampleJars { def exampleOutputDir = file("build/test/examples"); def jarName = "${name}.jar" dependsOn "jar_example_${name}" + dependsOn "extractExamplesRuntime" + def runtimeDependencies = copySpec { } + if (!fileTree(dir: dir, include: '**/*.proto').empty) { + // If we have any proto use, we have to include those classes and the runtime. + runtimeDependencies = copySpec { + from "$buildDir/runtime/examples/" + include "com/google/protobuf/**/*.class" + } + } // The "throwing" test verifies debugging/stack info on the post-proguarded output. def proguardConfigPath = "${dir}/proguard.cfg" if (new File(proguardConfigPath).exists()) { @@ -490,7 +549,9 @@ task buildExampleJars { archiveName = "${name}_pre_proguard.jar" destinationDir = exampleOutputDir from "build/test/examples/classes" - include "**/" + name + "/**/*.class" + include name + "/**/*.class" + with runtimeDependencies + includeEmptyDirs false } def jarPath = files(tasks.getByPath("pre_proguard_example_${name}")).files.first(); def proguardJarPath = "${exampleOutputDir}/${jarName}" @@ -519,7 +580,9 @@ task buildExampleJars { archiveName = jarName destinationDir = exampleOutputDir from "build/test/examples/classes" - include "**/" + name + "/**/*.class" + include name + "/**/*.class" + with runtimeDependencies + includeEmptyDirs false } } } diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java index a547d126e..b77493f59 100644 --- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java +++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java @@ -294,9 +294,12 @@ public class CompatDx { multiDex = options.has(spec.multiDex); mainDexList = options.valueOf(spec.mainDexList); minimalMainDex = options.has(spec.minimalMainDex); - minApiLevel = options.has(spec.minApiLevel) - ? options.valueOf(spec.minApiLevel) - : Constants.DEFAULT_ANDROID_API; + if (options.has(spec.minApiLevel)) { + List<Integer> allMinApiLevels = options.valuesOf(spec.minApiLevel); + minApiLevel = allMinApiLevels.get(allMinApiLevels.size() - 1); + } else { + minApiLevel = Constants.DEFAULT_ANDROID_API; + } inputList = options.valueOf(spec.inputList); inputs = ImmutableList.copyOf(options.valuesOf(spec.inputs)); maxIndexNumber = options.valueOf(spec.maxIndexNumber); diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java index dca5e02e9..84d6d5da6 100644 --- a/src/main/java/com/android/tools/r8/graph/Code.java +++ b/src/main/java/com/android/tools/r8/graph/Code.java @@ -34,15 +34,15 @@ public abstract class Code extends CanonicalizedDexItem { } public DexCode asDexCode() { - throw new Unreachable(); + throw new Unreachable(getClass().getCanonicalName() + ".asDexCode()"); } public JarCode asJarCode() { - throw new Unreachable(); + throw new Unreachable(getClass().getCanonicalName() + ".asJarCode()"); } public OutlineCode asOutlineCode() { - throw new Unreachable(); + throw new Unreachable(getClass().getCanonicalName() + ".asOutlineCode()"); } @Override diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java index 55aa9868a..1afd77ea3 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java @@ -16,19 +16,6 @@ import java.util.ArrayList; import java.util.List; public class DexAnnotation extends DexItem { - // Dex system annotations. - // See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation - private static final String ANNOTATION_DEFAULT_DESCRIPTOR = - "Ldalvik/annotation/AnnotationDefault;"; - private static final String ENCLOSING_CLASS_DESCRIPTOR = "Ldalvik/annotation/EnclosingClass;"; - private static final String ENCLOSING_METHOD_DESCRIPTOR = "Ldalvik/annotation/EnclosingMethod;"; - private static final String INNER_CLASS_DESCRIPTOR = "Ldalvik/annotation/InnerClass;"; - private static final String MEMBER_CLASSES_DESCRIPTOR = "Ldalvik/annotation/MemberClasses;"; - private static final String METHOD_PARAMETERS_DESCRIPTOR = "Ldalvik/annotation/MethodParameters;"; - private static final String SIGNATURE_DESCRIPTOR = "Ldalvik/annotation/Signature;"; - private static final String SOURCE_DEBUG_EXTENSION = "Ldalvik/annotation/SourceDebugExtension;"; - private static final String THROWS_DESCRIPTOR = "Ldalvik/annotation/Throws;"; - public static final int VISIBILITY_BUILD = 0x00; public static final int VISIBILITY_RUNTIME = 0x01; public static final int VISIBILITY_SYSTEM = 0x02; @@ -74,37 +61,36 @@ public class DexAnnotation extends DexItem { public static DexAnnotation createEnclosingClassAnnotation(DexType enclosingClass, DexItemFactory factory) { - return createSystemValueAnnotation(ENCLOSING_CLASS_DESCRIPTOR, factory, + return createSystemValueAnnotation(factory.annotationEnclosingClass, factory, new DexValueType(enclosingClass)); } public static DexAnnotation createEnclosingMethodAnnotation(DexMethod enclosingMethod, DexItemFactory factory) { - return createSystemValueAnnotation(ENCLOSING_METHOD_DESCRIPTOR, factory, + return createSystemValueAnnotation(factory.annotationEnclosingMethod, factory, new DexValueMethod(enclosingMethod)); } - public static boolean isEnclosingClassAnnotation(DexAnnotation annotation) { - return annotation.annotation.type.toDescriptorString().equals(ENCLOSING_CLASS_DESCRIPTOR); - } - - public static boolean isEnclosingMethodAnnotation(DexAnnotation annotation) { - return annotation.annotation.type.toDescriptorString().equals(ENCLOSING_METHOD_DESCRIPTOR); + public static boolean isEnclosingClassAnnotation(DexAnnotation annotation, + DexItemFactory factory) { + return annotation.annotation.type == factory.annotationEnclosingClass; } - public static boolean isEnclosingAnnotation(DexAnnotation annotation) { - return isEnclosingClassAnnotation(annotation) || isEnclosingMethodAnnotation(annotation); + public static boolean isEnclosingMethodAnnotation(DexAnnotation annotation, + DexItemFactory factory) { + return annotation.annotation.type == factory.annotationEnclosingMethod; } - public static boolean isInnerClassesAnnotation(DexAnnotation annotation) { - return annotation.annotation.type.toDescriptorString().equals(MEMBER_CLASSES_DESCRIPTOR) - || annotation.annotation.type.toDescriptorString().equals(INNER_CLASS_DESCRIPTOR); + public static boolean isInnerClassesAnnotation(DexAnnotation annotation, + DexItemFactory factory) { + return annotation.annotation.type == factory.annotationMemberClasses + || annotation.annotation.type == factory.annotationInnerClass; } public static DexAnnotation createInnerClassAnnotation(String clazz, int access, DexItemFactory factory) { return new DexAnnotation(VISIBILITY_SYSTEM, - new DexEncodedAnnotation(factory.createType(INNER_CLASS_DESCRIPTOR), + new DexEncodedAnnotation(factory.annotationInnerClass, new DexAnnotationElement[]{ new DexAnnotationElement( factory.createString("accessFlags"), @@ -123,14 +109,14 @@ public class DexAnnotation extends DexItem { for (int i = 0; i < classes.size(); i++) { values[i] = new DexValueType(classes.get(i)); } - return createSystemValueAnnotation(MEMBER_CLASSES_DESCRIPTOR, factory, + return createSystemValueAnnotation(factory.annotationMemberClasses, factory, new DexValueArray(values)); } public static DexAnnotation createSourceDebugExtensionAnnotation(DexValue value, DexItemFactory factory) { return new DexAnnotation(VISIBILITY_SYSTEM, - new DexEncodedAnnotation(factory.createType(SOURCE_DEBUG_EXTENSION), + new DexEncodedAnnotation(factory.annotationSourceDebugExtension, new DexAnnotationElement[] { new DexAnnotationElement(factory.createString("value"), value) })); @@ -140,7 +126,7 @@ public class DexAnnotation extends DexItem { DexValue[] accessFlags, DexItemFactory factory) { assert names.length == accessFlags.length; return new DexAnnotation(VISIBILITY_SYSTEM, - new DexEncodedAnnotation(factory.createType(METHOD_PARAMETERS_DESCRIPTOR), + new DexEncodedAnnotation(factory.annotationMethodParameters, new DexAnnotationElement[]{ new DexAnnotationElement( factory.createString("names"), @@ -153,7 +139,7 @@ public class DexAnnotation extends DexItem { public static DexAnnotation createAnnotationDefaultAnnotation(DexType type, List<DexAnnotationElement> defaults, DexItemFactory factory) { - return createSystemValueAnnotation(ANNOTATION_DEFAULT_DESCRIPTOR, factory, + return createSystemValueAnnotation(factory.annotationDefault, factory, new DexValueAnnotation( new DexEncodedAnnotation(type, defaults.toArray(new DexAnnotationElement[defaults.size()]))) @@ -161,34 +147,38 @@ public class DexAnnotation extends DexItem { } public static DexAnnotation createSignatureAnnotation(String signature, DexItemFactory factory) { - return createSystemValueAnnotation(SIGNATURE_DESCRIPTOR, factory, + return createSystemValueAnnotation(factory.annotationSignature, factory, compressSignature(signature, factory)); } public static DexAnnotation createThrowsAnnotation(DexValue[] exceptions, DexItemFactory factory) { - return createSystemValueAnnotation(THROWS_DESCRIPTOR, factory, new DexValueArray(exceptions)); + return createSystemValueAnnotation(factory.annotationThrows, factory, + new DexValueArray(exceptions)); } - private static DexAnnotation createSystemValueAnnotation(String desc, DexItemFactory factory, + private static DexAnnotation createSystemValueAnnotation(DexType type, DexItemFactory factory, DexValue value) { return new DexAnnotation(VISIBILITY_SYSTEM, - new DexEncodedAnnotation(factory.createType(desc), new DexAnnotationElement[] { + new DexEncodedAnnotation(type, new DexAnnotationElement[]{ new DexAnnotationElement(factory.createString("value"), value) })); } - public static boolean isThrowingAnnotation(DexAnnotation annotation) { - return annotation.annotation.type.toDescriptorString().equals(THROWS_DESCRIPTOR); + public static boolean isThrowingAnnotation(DexAnnotation annotation, + DexItemFactory factory) { + return annotation.annotation.type == factory.annotationThrows; } - public static boolean isSignatureAnnotation(DexAnnotation annotation) { - return annotation.annotation.type.toDescriptorString().equals(SIGNATURE_DESCRIPTOR); + public static boolean isSignatureAnnotation(DexAnnotation annotation, + DexItemFactory factory) { + return annotation.annotation.type == factory.annotationSignature; } - public static boolean isSourceDebugExtension(DexAnnotation annotation) { - return annotation.annotation.type.toDescriptorString().equals(SOURCE_DEBUG_EXTENSION); + public static boolean isSourceDebugExtension(DexAnnotation annotation, + DexItemFactory factory) { + return annotation.annotation.type == factory.annotationSourceDebugExtension; } /** diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java index 04e94d7ea..e91e1f009 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java @@ -68,6 +68,15 @@ public class DexAnnotationSet extends DexItem { sorted = hashCode(); } + public DexAnnotation getFirstMatching(DexType type) { + for (DexAnnotation annotation : annotations) { + if (annotation.annotation.type == type) { + return annotation; + } + } + return null; + } + private int sortedHashCode() { int hashCode = hashCode(); return hashCode == UNSORTED ? 1 : hashCode; diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java index ca702fe3a..d84dd8c76 100644 --- a/src/main/java/com/android/tools/r8/graph/DexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.Hashtable; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -87,7 +86,8 @@ public class DexApplication { } public List<DexProgramClass> classes() { - List<DexProgramClass> classes = programClasses.collectLoadedClasses(); + programClasses.forceLoad(type -> true); + List<DexProgramClass> classes = programClasses.getAllClasses(); assert reorderClasses(classes); return classes; } @@ -110,16 +110,16 @@ public class DexApplication { // program classes are supposed to be loaded, but force-loading them is no-op. programClasses.forceLoad(type -> true); - programClasses.collectLoadedClasses().forEach(clazz -> loaded.put(clazz.type, clazz)); + programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz)); if (classpathClasses != null) { classpathClasses.forceLoad(type -> !loaded.containsKey(type)); - classpathClasses.collectLoadedClasses().forEach(clazz -> loaded.put(clazz.type, clazz)); + classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz)); } if (libraryClasses != null) { libraryClasses.forceLoad(type -> !loaded.containsKey(type)); - libraryClasses.collectLoadedClasses().forEach(clazz -> loaded.put(clazz.type, clazz)); + libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz)); } return loaded; @@ -192,7 +192,7 @@ public class DexApplication { * <p>If no directory is provided everything is written to System.out. */ public void disassemble(Path outputDir, InternalOptions options) { - for (DexProgramClass clazz : programClasses.collectLoadedClasses()) { + for (DexProgramClass clazz : programClasses.getAllClasses()) { for (DexEncodedMethod method : clazz.virtualMethods()) { if (options.methodMatchesFilter(method)) { disassemble(method, getProguardMap(), outputDir); @@ -246,7 +246,7 @@ public class DexApplication { * Write smali source for the application code on the provided PrintStream. */ public void smali(InternalOptions options, PrintStream ps) { - List<DexProgramClass> classes = programClasses.collectLoadedClasses(); + List<DexProgramClass> classes = programClasses.getAllClasses(); classes.sort(Comparator.comparing(DexProgramClass::toSourceString)); boolean firstClass = true; for (DexClass clazz : classes) { @@ -322,7 +322,7 @@ public class DexApplication { } public Builder(DexApplication application) { - programClasses = application.programClasses.collectLoadedClasses(); + programClasses = application.programClasses.getAllClasses(); classpathClasses = application.classpathClasses; libraryClasses = application.libraryClasses; proguardMap = application.proguardMap; diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index 023cfb0e4..5b1b0f3f8 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java @@ -10,6 +10,7 @@ import com.android.tools.r8.errors.Unreachable; import com.google.common.base.MoreObjects; +import java.util.Arrays; import java.util.function.Consumer; public abstract class DexClass extends DexItem { @@ -83,6 +84,17 @@ public abstract class DexClass extends DexItem { } } + public DexEncodedMethod[] allMethodsSorted() { + int vLen = virtualMethods().length; + int dLen = directMethods().length; + DexEncodedMethod[] result = new DexEncodedMethod[vLen+dLen]; + System.arraycopy(virtualMethods(), 0, result, 0, vLen); + System.arraycopy(directMethods(), 0, result, vLen, dLen); + Arrays.sort(result, + (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method)); + return result; + } + public DexEncodedField[] staticFields() { return MoreObjects.firstNonNull(staticFields, NO_FIELDS); } diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java index dd5efbbd0..036b8bc74 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java @@ -7,8 +7,9 @@ import com.android.tools.r8.Resource; import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.Unreachable; +import java.util.function.Supplier; -public class DexClasspathClass extends DexClass { +public class DexClasspathClass extends DexClass implements Supplier<DexClasspathClass> { public DexClasspathClass(DexType type, Resource.Kind origin, DexAccessFlags accessFlags, DexType superType, DexTypeList interfaces, DexString sourceFile, DexAnnotationSet annotations, @@ -43,4 +44,9 @@ public class DexClasspathClass extends DexClass { public DexClasspathClass asClasspathClass() { return this; } + + @Override + public DexClasspathClass get() { + return this; + } } diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 5dff4b432..36d732d8d 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java @@ -129,8 +129,8 @@ public class DexItemFactory { public DexType annotationType = createType(annotationDescriptor); public DexType throwableType = createType(throwableDescriptor); - public DexType stringBuilderType = createType(createString("Ljava/lang/StringBuilder;")); - public DexType stringBufferType = createType(createString("Ljava/lang/StringBuffer;")); + public DexType stringBuilderType = createType("Ljava/lang/StringBuilder;"); + public DexType stringBufferType = createType("Ljava/lang/StringBuffer;"); public StringBuildingMethods stringBuilderMethods = new StringBuildingMethods(stringBuilderType); public StringBuildingMethods stringBufferMethods = new StringBuildingMethods(stringBufferType); @@ -140,6 +140,21 @@ public class DexItemFactory { public ThrowableMethods throwableMethods = new ThrowableMethods(); public ClassMethods classMethods = new ClassMethods(); + // Dex system annotations. + // See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation + public final DexType annotationDefault = createType("Ldalvik/annotation/AnnotationDefault;"); + public final DexType annotationEnclosingClass = createType("Ldalvik/annotation/EnclosingClass;"); + public final DexType annotationEnclosingMethod = createType( + "Ldalvik/annotation/EnclosingMethod;"); + public final DexType annotationInnerClass = createType("Ldalvik/annotation/InnerClass;"); + public final DexType annotationMemberClasses = createType("Ldalvik/annotation/MemberClasses;"); + public final DexType annotationMethodParameters = createType( + "Ldalvik/annotation/MethodParameters;"); + public final DexType annotationSignature = createType("Ldalvik/annotation/Signature;"); + public final DexType annotationSourceDebugExtension = createType( + "Ldalvik/annotation/SourceDebugExtension;"); + public final DexType annotationThrows = createType("Ldalvik/annotation/Throws;"); + public void clearSubtypeInformation() { types.values().forEach(DexType::clearSubtypeInformation); } diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java index c1b9205e1..0c8ce8cf5 100644 --- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java @@ -7,8 +7,9 @@ import com.android.tools.r8.Resource; import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; import com.android.tools.r8.errors.Unreachable; +import java.util.function.Supplier; -public class DexLibraryClass extends DexClass { +public class DexLibraryClass extends DexClass implements Supplier<DexLibraryClass> { public DexLibraryClass(DexType type, Resource.Kind origin, DexAccessFlags accessFlags, DexType superType, DexTypeList interfaces, DexString sourceFile, DexAnnotationSet annotations, @@ -48,4 +49,9 @@ public class DexLibraryClass extends DexClass { public DexLibraryClass asLibraryClass() { return this; } + + @Override + public DexLibraryClass get() { + return this; + } } diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java index f93a7da59..a38c9cb99 100644 --- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java @@ -7,8 +7,9 @@ import com.android.tools.r8.Resource; import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; import java.util.Arrays; +import java.util.function.Supplier; -public class DexProgramClass extends DexClass { +public class DexProgramClass extends DexClass implements Supplier<DexProgramClass> { private DexEncodedArray staticValues; @@ -152,4 +153,9 @@ public class DexProgramClass extends DexClass { directMethods = Arrays.copyOf(directMethods, directMethods.length + 1); directMethods[directMethods.length - 1] = staticMethod; } + + @Override + public DexProgramClass get() { + return this; + } } diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java index a6b3fd0d7..c65b3709a 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java @@ -428,7 +428,11 @@ public class BasicBlockInstructionIterator implements InstructionIterator, Instr } // Insert inlinee blocks into the IR code. - inlinee.blocks.forEach(blocksIterator::add); + int blockNumber = code.getHighestBlockNumber() + 1; + for (BasicBlock bb : inlinee.blocks) { + bb.setNumber(blockNumber++); + blocksIterator.add(bb); + } // If the invoke block had catch handlers copy those down to all inlined blocks. if (invokeBlock.hasCatchHandlers()) { diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index c96bf92fa..e1e9a99ba 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java @@ -17,6 +17,7 @@ import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; +import java.util.stream.Collectors; public class IRCode { @@ -135,6 +136,7 @@ public class IRCode { } public boolean isConsistentGraph() { + assert consistentBlockNumbering(); assert consistentPredecessorSuccessors(); assert consistentCatchHandlers(); assert consistentBlockInstructions(); @@ -263,6 +265,12 @@ public class IRCode { return true; } + public boolean consistentBlockNumbering() { + return blocks.stream() + .collect(Collectors.groupingBy(BasicBlock::getNumber, Collectors.counting())) + .entrySet().stream().noneMatch((bb2count) -> bb2count.getValue() > 1); + } + private boolean consistentBlockInstructions() { for (BasicBlock block : blocks) { for (Instruction instruction : block.getInstructions()) { @@ -378,4 +386,8 @@ public class IRCode { public ConstNumber createFalse() { return new ConstNumber(ConstType.INT, createValue(MoveType.SINGLE), 0); } + + public final int getHighestBlockNumber() { + return blocks.stream().max(Comparator.comparingInt(BasicBlock::getNumber)).get().getNumber(); + } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java index 4d8eee148..f4406c0b8 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java @@ -19,9 +19,9 @@ import com.android.tools.r8.ir.code.Invoke.Type; import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; import com.google.common.collect.Sets; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.HashMap; -import java.util.IdentityHashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -52,10 +52,10 @@ public class CallGraph { private boolean isSelfRecursive = false; // Outgoing calls from this method. - public final Set<Node> callees = new LinkedHashSet<>(); + private final Set<Node> callees = new LinkedHashSet<>(); // Incoming calls to this method. - public final Set<Node> callers = new LinkedHashSet<>(); + private final Set<Node> callers = new LinkedHashSet<>(); private Node(DexEncodedMethod method) { this.method = method; @@ -81,10 +81,6 @@ public class CallGraph { return callees.isEmpty(); } - int callDegree() { - return callees.size(); - } - @Override public int hashCode() { return method.hashCode(); @@ -133,75 +129,35 @@ public class CallGraph { } } - public class Leaves { - - private final List<DexEncodedMethod> leaves; - private final boolean brokeCycles; - private final Map<DexEncodedMethod, Set<DexEncodedMethod>> cycleBreakingCalls; - - private Leaves(List<DexEncodedMethod> leaves, boolean brokeCycles, - Map<DexEncodedMethod, Set<DexEncodedMethod>> cycleBreakingCalls) { - this.leaves = leaves; - this.brokeCycles = brokeCycles; - this.cycleBreakingCalls = cycleBreakingCalls; - assert brokeCycles == (cycleBreakingCalls.size() != 0); - } - - public int size() { - return leaves.size(); - } - - public List<DexEncodedMethod> getLeaves() { - return leaves; - } - - public boolean hasBrokeCycles() { - return brokeCycles; - } - - /** - * Calls that were broken to produce the leaves. - * - * If {@link Leaves#breakCycles()} return <code>true</code> this provides the calls for each - * leaf that were broken. - * - * NOTE: The broken calls are not confined to the set of leaves. - */ - public Map<DexEncodedMethod, Set<DexEncodedMethod>> getCycleBreakingCalls() { - return cycleBreakingCalls; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Leaves: "); - builder.append(leaves.size()); - builder.append("\n"); - builder.append(brokeCycles ? "Call cycles broken" : "No call cycles broken"); - return builder.toString(); - } - } - private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>(); private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>(); + // Returns whether the method->callee edge has been removed from the call graph + // to break a cycle in the call graph. + public boolean isBreaker(DexEncodedMethod method, DexEncodedMethod callee) { + Set<DexEncodedMethod> value = breakers.get(method); + return (value != null) && value.contains(callee); + } + private List<Node> leaves = null; private Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet(); private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet(); public static CallGraph build(DexApplication application, AppInfoWithSubtyping appInfo, GraphLense graphLense) { - CallGraph graph = new CallGraph(); - for (DexClass clazz : application.classes()) { - clazz.forEachMethod( method -> { + DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]); + Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type)); + for (DexClass clazz : classes) { + for (DexEncodedMethod method : clazz.allMethodsSorted()) { Node node = graph.ensureMethodNode(method); InvokeExtractor extractor = new InvokeExtractor(appInfo, graphLense, node, graph); method.registerReachableDefinitions(extractor); - }); + } } - assert allMethodsExists(application, graph); + graph.breakCycles(); + assert graph.breakCycles() == 0; // This time the cycles should be gone. graph.fillCallSiteSets(appInfo); graph.fillInitialLeaves(); return graph; @@ -258,7 +214,6 @@ public class CallGraph { /** * Remove all leaves (nodes with an call (outgoing) degree of 0). - * <p> * * @return List of {@link DexEncodedMethod} of the leaves removed. */ @@ -282,73 +237,62 @@ public class CallGraph { * leave is returned if the graph is not empty. * <p> * - * @return object with the leaves as a List of {@link DexEncodedMethod} and <code>boolean</code> - * indication of whether cycles were broken to produce leaves. <code>null</code> if the graph is - * empty. + * @return List of {@link DexEncodedMethod}. */ - public Leaves pickLeaves() { - boolean cyclesBroken = false; - Map<DexEncodedMethod, Set<DexEncodedMethod>> brokenCalls = Collections.emptyMap(); + List<DexEncodedMethod> extractLeaves() { if (isEmpty()) { return null; } List<DexEncodedMethod> leaves = removeLeaves(); - if (leaves.size() == 0) { - // The graph had no more leaves, so break cycles to construct leaves. - brokenCalls = breakCycles(); - cyclesBroken = true; - leaves = removeLeaves(); - } assert leaves.size() > 0; - for (DexEncodedMethod leaf : leaves) { - assert !leaf.isProcessed(); - } - return new Leaves(leaves, cyclesBroken, brokenCalls); + leaves.forEach( leaf -> { assert !leaf.isProcessed(); }); + return leaves; } - /** - * Break some cycles in the graph by removing edges. - * <p> - * This will find the lowest call (outgoing) degree in the graph. Then go through all nodes with - * that call (outgoing) degree, and remove all call (outgoing) edges from nodes with that call - * (outgoing) degree. - * <p> - * It will avoid removing edges from bridge-methods if possible. - * <p> - * Returns the calls that were broken. - */ - private Map<DexEncodedMethod, Set<DexEncodedMethod>> breakCycles() { - Map<DexEncodedMethod, Set<DexEncodedMethod>> brokenCalls = new IdentityHashMap<>(); - // Break non bridges with degree 1. - int minDegree = nodes.size(); - for (Node node : nodes.values()) { - // Break cycles and add all leaves created in the process. - if (!node.isBridge() && node.callDegree() <= 1) { - assert node.callDegree() == 1; - Set<DexEncodedMethod> calls = removeAllCalls(node); - leaves.add(node); - brokenCalls.put(node.method, calls); - } else { - minDegree = Integer.min(minDegree, node.callDegree()); + private int traverse(Node node, HashSet<Node> stack, HashSet<Node> marked) { + int numberOfCycles = 0; + if (!marked.contains(node)) { + assert !stack.contains(node); + stack.add(node); + ArrayList<Node> toBeRemoved = null; + // Sort the callees before calling traverse recursively. + // This will ensure cycles are broken the same way across + // multiple invocations of the R8 compiler. + Node[] callees = node.callees.toArray(new Node[node.callees.size()]); + Arrays.sort(callees, (Node a, Node b) -> a.method.method.slowCompareTo(b.method.method)); + for (Node callee : callees) { + if (stack.contains(callee)) { + if (toBeRemoved == null) { + toBeRemoved = new ArrayList<>(); + } + // We have a cycle; break it by removing node->callee. + toBeRemoved.add(callee); + callee.callers.remove(node); + breakers.computeIfAbsent(node.method, ignore -> new HashSet<>()).add(callee.method); + } else { + numberOfCycles += traverse(callee, stack, marked); + } } + if (toBeRemoved != null) { + numberOfCycles += toBeRemoved.size(); + node.callees.removeAll(toBeRemoved); + } + stack.remove(node); + marked.add(node); } + return numberOfCycles; + } - // Return if new leaves were created. - if (leaves.size() > 0) { - return brokenCalls; - } - - // Break methods with the found minimum degree and add all leaves created in the process. - for (Node node : nodes.values()) { - if (node.callDegree() <= minDegree) { - assert node.callDegree() == minDegree; - Set<DexEncodedMethod> calls = removeAllCalls(node); - leaves.add(node); - brokenCalls.put(node.method, calls); - } + private int breakCycles() { + // Break cycles in this call graph by removing edges causing cycles. + // The remove edges are stored in @breakers. + int numberOfCycles = 0; + HashSet<Node> stack = new HashSet<>(); + HashSet<Node> marked = new HashSet<>(); + for(Node node : nodes.values()) { + numberOfCycles += traverse(node, stack, marked); } - assert leaves.size() > 0; - return brokenCalls; + return numberOfCycles; } synchronized private Node ensureMethodNode(DexEncodedMethod method) { @@ -367,16 +311,6 @@ public class CallGraph { callee.invokeCount++; } - private Set<DexEncodedMethod> removeAllCalls(Node node) { - Set<DexEncodedMethod> calls = Sets.newIdentityHashSet(); - for (Node call : node.callees) { - calls.add(call.method); - call.callers.remove(node); - } - node.callees.clear(); - return calls; - } - private void remove(Node node, List<Node> leaves) { assert node != null; for (Node caller : node.callers) { diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index 4ce2c4ce4..ba63d804a 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java @@ -270,6 +270,8 @@ public class IRBuilder { private List<Value> debugLocalReads = new ArrayList<>(); private List<Value> debugLocalEnds = new ArrayList<>(); + private int nextBlockNumber = 0; + public IRBuilder(DexEncodedMethod method, SourceCode source, InternalOptions options) { this(method, source, new ValueNumberGenerator(), options); } @@ -350,15 +352,14 @@ public class IRBuilder { // Process normal blocks reachable from the entry block using a worklist of reachable // blocks. - int blockNumber = 0; addToWorklist(currentBlock, 0); - blockNumber = processWorklist(blockNumber); + processWorklist(); // Check that the last block is closed and does not fall off the end. assert currentBlock == null; // Handle where a catch handler hits the same block as the fallthrough. - blockNumber = handleFallthroughToCatchBlock(blockNumber); + handleFallthroughToCatchBlock(); // Verify that we have properly filled all blocks // Must be after handle-catch (which has delayed edges), @@ -366,7 +367,7 @@ public class IRBuilder { assert verifyFilledPredecessors(); // If there are multiple returns create an exit block. - blockNumber = handleExitBlock(blockNumber); + handleExitBlock(); // Clear all reaching definitions to free up memory (and avoid invalid use). for (BasicBlock block : blocks) { @@ -437,14 +438,14 @@ public class IRBuilder { return true; } - private int processWorklist(int blockNumber) { + private void processWorklist() { for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) { if (item.block.isFilled()) { continue; } setCurrentBlock(item.block); blocks.add(currentBlock); - currentBlock.setNumber(blockNumber++); + currentBlock.setNumber(nextBlockNumber++); // Build IR for each dex instruction in the block. for (int i = item.firstInstructionIndex; i < source.instructionCount(); ++i) { if (currentBlock == null) { @@ -461,7 +462,6 @@ public class IRBuilder { source.buildInstruction(this, i); } } - return blockNumber; } // Helper to resolve switch payloads and build switch instructions (dex code only). @@ -1536,6 +1536,7 @@ public class IRBuilder { return; } BasicBlock block = new BasicBlock(); + block.setNumber(nextBlockNumber++); blocks.add(block); block.incrementUnfilledPredecessorCount(); int freshOffset = INITIAL_BLOCK_OFFSET - 1; @@ -1721,7 +1722,7 @@ public class IRBuilder { closeCurrentBlock(); } - int handleExitBlock(int blockNumber) { + void handleExitBlock() { if (exitBlocks.size() > 0) { // Create and populate the exit block if needed (eg, synchronized support for jar). setCurrentBlock(new BasicBlock()); @@ -1730,11 +1731,11 @@ public class IRBuilder { if (currentBlock.getInstructions().isEmpty() && exitBlocks.size() == 1) { normalExitBlock = exitBlocks.get(0); setCurrentBlock(null); - return blockNumber; + return; } // Commit to creating the new exit block. normalExitBlock = currentBlock; - normalExitBlock.setNumber(blockNumber++); + normalExitBlock.setNumber(nextBlockNumber++); blocks.add(normalExitBlock); // Add the return instruction possibly creating a phi of return values. Return origReturn = exitBlocks.get(0).exit().asReturn(); @@ -1776,10 +1777,9 @@ public class IRBuilder { phi.addOperands(operands); } } - return blockNumber; } - private int handleFallthroughToCatchBlock(int blockNumber) { + private void handleFallthroughToCatchBlock() { // When a catch handler for a block goes to the same block as the fallthrough for that // block the graph only has one edge there. In these cases we add an additional block so the // catch edge goes through that and then make the fallthrough go through a new direct edge. @@ -1788,7 +1788,7 @@ public class IRBuilder { BasicBlock target = pair.second; // New block with one unfilled predecessor. - BasicBlock newBlock = BasicBlock.createGotoBlock(target, blockNumber++); + BasicBlock newBlock = BasicBlock.createGotoBlock(target, nextBlockNumber++); blocks.add(newBlock); newBlock.incrementUnfilledPredecessorCount(); @@ -1808,7 +1808,6 @@ public class IRBuilder { } target.filledPredecessor(this); } - return blockNumber; } /** diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 98a261914..a345ddfa4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -37,7 +37,7 @@ import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; -import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.List; @@ -277,50 +277,21 @@ public class IRConverter { // Process the application identifying outlining candidates. timing.begin("IR conversion phase 1"); OptimizationFeedback directFeedback = new OptimizationFeedbackDirect(); - OptimizationFeedbackDelayed delayedFeedback = new OptimizationFeedbackDelayed(); while (!callGraph.isEmpty()) { - CallGraph.Leaves leaves = callGraph.pickLeaves(); - List<DexEncodedMethod> methods = leaves.getLeaves(); + List<DexEncodedMethod> methods = callGraph.extractLeaves(); assert methods.size() > 0; - List<Future<?>> futures = new ArrayList<>(); - // For testing we have the option to determine the processing order of the methods. if (options.testing.irOrdering != null) { - methods = options.testing.irOrdering.apply(methods, leaves); + methods = options.testing.irOrdering.apply(methods); } - - while (methods.size() > 0) { - // Process the methods on multiple threads. If cycles where broken collect the - // optimization feedback to reprocess methods affected by it. This is required to get - // deterministic behaviour, as the processing order within each set of leaves is - // non-deterministic. - - // Due to a race condition, we serialize processing of methods if cycles are broken. - // TODO(bak) - if (leaves.hasBrokeCycles()) { - for (DexEncodedMethod method : methods) { - processMethod(method, - leaves.hasBrokeCycles() ? delayedFeedback : directFeedback, - outliner == null ? Outliner::noProcessing : outliner::identifyCandidates); - } - } else { - for (DexEncodedMethod method : methods) { - futures.add(executorService.submit(() -> { - processMethod(method, - leaves.hasBrokeCycles() ? delayedFeedback : directFeedback, - outliner == null ? Outliner::noProcessing : outliner::identifyCandidates); - })); - } - ThreadUtils.awaitFutures(futures); - } - if (leaves.hasBrokeCycles()) { - // If cycles in the call graph were broken, then re-process all methods which are - // affected by the optimization feedback of other methods in this group. - methods = delayedFeedback.applyAndClear(methods, leaves); - } else { - methods = ImmutableList.of(); - } + List<Future<?>> futures = new ArrayList<>(); + for (DexEncodedMethod method : methods) { + futures.add(executorService.submit(() -> { + processMethod(method, directFeedback, + outliner == null ? Outliner::noProcessing : outliner::identifyCandidates); + })); } + ThreadUtils.awaitFutures(futures); } timing.end(); @@ -550,7 +521,7 @@ public class IRConverter { feedback.markProcessed(method, state); } - private void updateHighestSortingStrings(DexEncodedMethod method) { + private synchronized void updateHighestSortingStrings(DexEncodedMethod method) { DexString highestSortingReferencedString = method.getCode().asDexCode().highestSortingString; if (highestSortingReferencedString != null) { if (highestSortingString == null diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java deleted file mode 100644 index a8c81ef75..000000000 --- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2017, 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.ir.conversion; - -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.ir.optimize.Inliner.Constraint; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import it.unimi.dsi.fastutil.objects.Reference2IntMap; -import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.Reference2LongMap; -import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class OptimizationFeedbackDelayed implements OptimizationFeedback { - - private Reference2IntMap<DexEncodedMethod> returnsArgument = new Reference2IntOpenHashMap<>(); - private Reference2LongMap<DexEncodedMethod> returnsConstant = new Reference2LongOpenHashMap<>(); - private Set<DexEncodedMethod> neverReturnsNull = Sets.newIdentityHashSet(); - private Map<DexEncodedMethod, Constraint> inliningConstraints = Maps.newIdentityHashMap(); - - @Override - synchronized public void methodReturnsArgument(DexEncodedMethod method, int argument) { - if (method.getOptimizationInfo().returnsArgument()) { - assert method.getOptimizationInfo().getReturnedArgument() == argument; - return; - } - assert !returnsArgument.containsKey(method); - returnsArgument.put(method, argument); - } - - @Override - synchronized public void methodReturnsConstant(DexEncodedMethod method, long value) { - if (method.getOptimizationInfo().returnsConstant()) { - assert method.getOptimizationInfo().getReturnedConstant() == value; - return; - } - assert !returnsConstant.containsKey(method); - returnsConstant.put(method, value); - } - - @Override - synchronized public void methodNeverReturnsNull(DexEncodedMethod method) { - if (method.getOptimizationInfo().neverReturnsNull()) { - return; - } - assert !neverReturnsNull.contains(method); - neverReturnsNull.add(method); - } - - @Override - public void markProcessed(DexEncodedMethod method, Constraint state) { - if (state == Constraint.NEVER) { - assert method.cannotInline(); - method.markProcessed(state); - } else { - inliningConstraints.put(method, state); - } - } - - private <T> boolean setsOverlap(Set<T> set1, Set<T> set2) { - for (T element : set1) { - if (set2.contains(element)) { - return true; - } - } - return false; - } - - /** - * Apply the optimization feedback. - * - * Returns the methods from the passed in list that could be affected by applying the - * optimization feedback. - */ - public List<DexEncodedMethod> applyAndClear( - List<DexEncodedMethod> processed, CallGraph.Leaves leaves) { - returnsArgument.forEach(DexEncodedMethod::markReturnsArgument); - returnsConstant.forEach(DexEncodedMethod::markReturnsConstant); - neverReturnsNull.forEach(DexEncodedMethod::markNeverReturnsNull); - - // Collect all methods affected by the optimization feedback applied. - Set<DexEncodedMethod> all = Sets.newIdentityHashSet(); - all.addAll(returnsArgument.keySet()); - all.addAll(returnsConstant.keySet()); - all.addAll(neverReturnsNull); - inliningConstraints.forEach((method, constraint) -> { - boolean changed = method.markProcessed(constraint); - if (changed) { - all.add(method); - } - }); - - // Collect the processed methods which could be affected by the applied optimization feedback. - List<DexEncodedMethod> result = new ArrayList<>(); - for (DexEncodedMethod method : processed) { - Set<DexEncodedMethod> calls = leaves.getCycleBreakingCalls().get(method); - if (setsOverlap(calls, all)) { - result.add(method); - } - } - - // Clear the collected optimization feedback. - returnsArgument.clear(); - returnsConstant.clear(); - neverReturnsNull.clear(); - inliningConstraints.clear(); - - return result; - } -} diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index 6d6981757..4ede5a839 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java @@ -284,6 +284,10 @@ public class Inliner { if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) { continue; } + if (callGraph.isBreaker(method, target)) { + // Make sure we don't inline a call graph breaker. + continue; + } // If this code did not go through the full pipeline, apply inlining to make sure // that force inline targets get processed. if (!target.isProcessed()) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java index 4b93d8d77..446b75a63 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java @@ -66,6 +66,7 @@ public class InliningOracle { assert !candidate.getOptimizationInfo().forceInline(); return null; } + if (candidate.accessFlags.isSynchronized()) { // Don't inline if target is synchronized. if (info != null) { @@ -73,6 +74,12 @@ public class InliningOracle { } return null; } + + if (callGraph.isBreaker(method, candidate)) { + // Cycle breaker so abort to preserve compilation order. + return null; + } + if (!inliner.hasInliningAccess(method, candidate)) { if (info != null) { info.exclude(invoke, "Inlinee candidate does not have right access flags"); @@ -154,6 +161,11 @@ public class InliningOracle { return null; } + if (callGraph.isBreaker(method, target)) { + // Cycle breaker so abort to preserve compilation order. + return null; + } + // Abort inlining attempt if method -> target access is not right. if (!inliner.hasInliningAccess(method, target)) { if (info != null) { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java index de81dfeff..9a7086453 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java @@ -42,6 +42,7 @@ public class PeepholeOptimizer { private static void shareIdenticalBlockSuffix(IRCode code, RegisterAllocator allocator) { Collection<BasicBlock> blocks = code.blocks; do { + int startNumberOfNewBlock = code.getHighestBlockNumber() + 1; Map<BasicBlock, BasicBlock> newBlocks = new IdentityHashMap<>(); for (BasicBlock block : blocks) { InstructionEquivalence equivalence = new InstructionEquivalence(allocator); @@ -79,7 +80,7 @@ public class PeepholeOptimizer { commonSuffixSize, sharedSuffixSizeExcludingExit(firstPred, pred, allocator)); } assert commonSuffixSize >= 1; - int blockNumber = code.blocks.size() + newBlocks.size(); + int blockNumber = startNumberOfNewBlock + newBlocks.size(); BasicBlock newBlock = createAndInsertBlockForSuffix( blockNumber, commonSuffixSize, predsWithSameLastInstruction, block); newBlocks.put(predsWithSameLastInstruction.get(0), newBlock); diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java index 7c12590b3..9f26fb51f 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java @@ -3,14 +3,17 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; +import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexClass; -import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexValue; +import com.android.tools.r8.graph.DexValue.DexValueType; import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; import com.android.tools.r8.shaking.RootSetBuilder.RootSet; +import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.StringUtils; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.util.Collections; @@ -29,30 +32,31 @@ public class ClassNameMinifier { private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap(); private final Map<String, NamingState> states = new HashMap<>(); - final List<String> dictionary; + private final List<String> dictionary; + private final boolean keepInnerClassStructure; public ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix, - List<String> dictionary) { + List<String> dictionary, boolean keepInnerClassStructure) { this.appInfo = appInfo; this.rootSet = rootSet; this.packagePrefix = packagePrefix; this.dictionary = dictionary; + this.keepInnerClassStructure = keepInnerClassStructure; } public Map<DexType, DexString> computeRenaming() { + Iterable<DexProgramClass> classes = appInfo.classes(); // Collect names we have to keep. for (DexClass clazz : appInfo.classes()) { if (rootSet.noObfuscation.contains(clazz)) { assert !renaming.containsKey(clazz.type); - renaming.put(clazz.type, clazz.type.descriptor); - usedTypeNames.add(clazz.type.descriptor); + registerClassAsUsed(clazz.type); } } for (DexClass clazz : appInfo.classes()) { if (!renaming.containsKey(clazz.type)) { - String packageName = getPackageNameFor(clazz); - NamingState state = getStateFor(packageName); - renaming.put(clazz.type, state.nextTypeName()); + DexString renamed = computeName(clazz); + renaming.put(clazz.type, renamed); } } appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded); @@ -60,6 +64,63 @@ public class ClassNameMinifier { return Collections.unmodifiableMap(renaming); } + /** + * Registers the given type as used. + * <p> + * When {@link #keepInnerClassStructure} is true, keeping the name of an inner class will + * automatically also keep the name of the outer class, as otherwise the structure would be + * invalidated. + */ + private void registerClassAsUsed(DexType type) { + renaming.put(type, type.descriptor); + usedTypeNames.add(type.descriptor); + if (keepInnerClassStructure) { + DexType outerClass = getOutClassForType(type); + if (outerClass != null) { + if (!renaming.containsKey(outerClass)) { + // The outer class was not previously kept. We have to do this now. + registerClassAsUsed(outerClass); + } + } + } + } + + private DexType getOutClassForType(DexType type) { + DexClass clazz = appInfo.definitionFor(type); + if (clazz == null) { + return null; + } + DexAnnotation annotation = + clazz.annotations.getFirstMatching(appInfo.dexItemFactory.annotationEnclosingClass); + if (annotation != null) { + assert annotation.annotation.elements.length == 1; + DexValue value = annotation.annotation.elements[0].value; + return ((DexValueType) value).value; + } + // We do not need to preserve the names for local or anonymous classes, as they do not result + // in a member type declaration and hence cannot be referenced as nested classes in + // method signatures. + // See https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.5. + return null; + } + + private DexString computeName(DexClass clazz) { + NamingState state = null; + if (keepInnerClassStructure) { + // When keeping the nesting structure of inner classes, we have to insert the name + // of the outer class for the $ prefix. + DexType outerClass = getOutClassForType(clazz.type); + if (outerClass != null) { + state = getStateForOuterClass(outerClass); + } + } + if (state == null) { + String packageName = getPackageNameFor(clazz); + state = getStateFor(packageName); + } + return state.nextTypeName(); + } + private String getPackageNameFor(DexClass clazz) { if ((packagePrefix == null) || rootSet.keepPackageName.contains(clazz)) { return clazz.type.getPackageDescriptor(); @@ -72,6 +133,27 @@ public class ClassNameMinifier { return states.computeIfAbsent(packageName, NamingState::new); } + private NamingState getStateForOuterClass(DexType outer) { + String prefix = DescriptorUtils + .getClassBinaryNameFromDescriptor(outer.toDescriptorString()); + return states.computeIfAbsent(prefix, k -> { + // Create a naming state with this classes renaming as prefix. + DexString renamed = renaming.get(outer); + if (renamed == null) { + // The outer class has not been renamed yet, so rename the outer class first. + DexClass outerClass = appInfo.definitionFor(outer); + if (outerClass == null) { + renamed = outer.descriptor; + } else { + renamed = computeName(outerClass); + renaming.put(outer, renamed); + } + } + String binaryName = DescriptorUtils.getClassBinaryNameFromDescriptor(renamed.toString()); + return new NamingState(binaryName, "$"); + }); + } + private void renameArrayTypeIfNeeded(DexType type) { if (type.isArrayType()) { DexType base = type.toBaseType(appInfo.dexItemFactory); @@ -92,11 +174,18 @@ public class ClassNameMinifier { private class NamingState { private final char[] packagePrefix; + private final String separator; private int typeCounter = 1; private Iterator<String> dictionaryIterator; NamingState(String packageName) { - this.packagePrefix = ("L" + packageName + (packageName.isEmpty() ? "" : "/")).toCharArray(); + this(packageName, "/"); + } + + NamingState(String packageName, String separator) { + this.packagePrefix = ("L" + packageName + (packageName.isEmpty() ? "" : separator)) + .toCharArray(); + this.separator = separator; this.dictionaryIterator = dictionary.iterator(); } diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java index 9e8110278..025002dbf 100644 --- a/src/main/java/com/android/tools/r8/naming/Minifier.java +++ b/src/main/java/com/android/tools/r8/naming/Minifier.java @@ -42,7 +42,8 @@ public class Minifier { timing.begin("MinifyClasses"); Map<DexType, DexString> classRenaming = new ClassNameMinifier( - appInfo, rootSet, options.packagePrefix, options.classObfuscationDictionary) + appInfo, rootSet, options.packagePrefix, options.classObfuscationDictionary, + options.attributeRemoval.signature) .computeRenaming(); timing.end(); timing.begin("MinifyMethods"); diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java index e60f241c9..3ab231cb0 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java @@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexAnnotationSet; import com.android.tools.r8.graph.DexAnnotationSetRefList; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; import com.android.tools.r8.utils.InternalOptions; @@ -42,21 +43,24 @@ public class AnnotationRemover { case DexAnnotation.VISIBILITY_SYSTEM: // EnclosingClass and InnerClass are used in Dalvik to represent the InnerClasses // attribute section from class files. - if (keep.innerClasses && - (DexAnnotation.isInnerClassesAnnotation(annotation) || - DexAnnotation.isEnclosingClassAnnotation(annotation))) { + DexItemFactory factory = appInfo.dexItemFactory; + if (keep.innerClasses + && (DexAnnotation.isInnerClassesAnnotation(annotation, factory) || + DexAnnotation.isEnclosingClassAnnotation(annotation, factory))) { return true; } - if (keep.enclosingMethod && DexAnnotation.isEnclosingMethodAnnotation(annotation)) { + if (keep.enclosingMethod + && DexAnnotation.isEnclosingMethodAnnotation(annotation, factory)) { return true; } - if (keep.exceptions && DexAnnotation.isThrowingAnnotation(annotation)) { + if (keep.exceptions && DexAnnotation.isThrowingAnnotation(annotation, factory)) { return true; } - if (keep.signature && DexAnnotation.isSignatureAnnotation(annotation)) { + if (keep.signature && DexAnnotation.isSignatureAnnotation(annotation, factory)) { return true; } - if (keep.sourceDebugExtension && DexAnnotation.isSourceDebugExtension(annotation)) { + if (keep.sourceDebugExtension + && DexAnnotation.isSourceDebugExtension(annotation, factory)) { return true; } return false; diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java index 91adca4c8..9677473dc 100644 --- a/src/main/java/com/android/tools/r8/utils/ClassMap.java +++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java @@ -4,17 +4,19 @@ package com.android.tools.r8.utils; import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.ClassKind; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexType; import com.google.common.collect.Sets; import java.util.ArrayList; -import java.util.Collection; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; +import java.util.function.Supplier; /** * Represents a collection of classes. Collection can be fully loaded, @@ -25,13 +27,17 @@ public abstract class ClassMap<T extends DexClass> { // resources provided by different resource providers. // // NOTE: all access must be synchronized on `classes`. - private final Map<DexType, Value<T>> classes; + private final Map<DexType, Supplier<T>> classes; - // Class provider if available. In case it's `null`, all classes of - // the collection must be pre-populated in `classes`. - private final ClassProvider<T> classProvider; + // Class provider if available. + // + // If the class provider is `null` it indicates that all classes are already present + // in a map referenced by `classes` and thus the collection is fully loaded. + // + // NOTE: all access must be synchronized on `classes`. + private ClassProvider<T> classProvider; - ClassMap(Map<DexType, Value<T>> classes, ClassProvider<T> classProvider) { + ClassMap(Map<DexType, Supplier<T>> classes, ClassProvider<T> classProvider) { this.classes = classes == null ? new IdentityHashMap<>() : classes; this.classProvider = classProvider; assert this.classProvider == null || this.classProvider.getClassKind() == getClassKind(); @@ -40,6 +46,9 @@ public abstract class ClassMap<T extends DexClass> { /** Resolves a class conflict by selecting a class, may generate compilation error. */ abstract T resolveClassConflict(T a, T b); + /** Return supplier for preloaded class. */ + abstract Supplier<T> getTransparentSupplier(T clazz); + /** Kind of the classes supported by this collection. */ abstract ClassKind getClassKind(); @@ -53,115 +62,175 @@ public abstract class ClassMap<T extends DexClass> { /** Returns a definition for a class or `null` if there is no such class in the collection. */ public T get(DexType type) { - final Value<T> value = getOrCreateValue(type); + Supplier<T> supplier; - if (value == null) { - return null; - } - - if (!value.ready) { - // Load the value in a context synchronized on value instance. This way - // we avoid locking the whole collection during expensive resource loading - // and classes construction operations. - synchronized (value) { - if (!value.ready) { - assert classProvider != null : "getOrCreateValue() created " - + "Value for missing type when there is no classProvider."; - classProvider.collectClass(type, clazz -> { - assert clazz != null; - assert getClassKind().isOfKind(clazz); - assert !value.ready; - - if (clazz.type != type) { - throw new CompilationError("Class content provided for type descriptor " - + type.toSourceString() + " actually defines class " + clazz.type - .toSourceString()); - } - - if (value.clazz == null) { - value.clazz = clazz; - } else { - // The class resolution *may* generate a compilation error as one of - // possible resolutions. In this case we leave `value` in (false, null) - // state so in rare case of another thread trying to get the same class - // before this error is propagated it will get the same conflict. - T oldClass = value.clazz; - value.clazz = null; - value.clazz = resolveClassConflict(oldClass, clazz); - } - }); - value.ready = true; + synchronized (classes) { + supplier = classes.get(type); + + // Get class supplier, create it if it does not + // exist and the collection is NOT fully loaded. + if (supplier == null) { + if (classProvider == null) { + // There is no supplier, but the collection is fully loaded. + return null; } + + supplier = new ConcurrentClassLoader<>(this, this.classProvider, type); + classes.put(type, supplier); } } - assert value.ready; - return value.clazz; + return supplier.get(); } - private Value<T> getOrCreateValue(DexType type) { + /** Returns all classes from the collection. The collection must be force-loaded. */ + public List<T> getAllClasses() { + List<T> loadedClasses = new ArrayList<>(); synchronized (classes) { - Value<T> value = classes.get(type); - if (value == null && classProvider != null) { - value = new Value<>(); - classes.put(type, value); + if (classProvider != null) { + throw new Unreachable("Getting all classes from not fully loaded collection."); + } + for (Supplier<T> supplier : classes.values()) { + // Since the class map is fully loaded, all suppliers must be + // loaded and non-null. + T clazz = supplier.get(); + assert clazz != null; + loadedClasses.add(clazz); } - return value; } + return loadedClasses; } /** - * Returns currently loaded classes. + * Forces loading of all the classes satisfying the criteria specified. * - * Method is assumed to be called when the collection is fully loaded, - * otherwise only a subset of potentially loaded classes may be returned. + * NOTE: after this method finishes, the class map is considered to be fully-loaded + * and thus sealed. This has one side-effect: if we filter out some of the classes + * with `load` predicate, these classes will never be loaded. */ - public List<T> collectLoadedClasses() { - List<T> loadedClasses = new ArrayList<>(); + public void forceLoad(Predicate<DexType> load) { + Set<DexType> knownClasses; + ClassProvider<T> classProvider; + synchronized (classes) { - for (Value<T> value : classes.values()) { - // NOTE: value mutations are NOT synchronized on `classes`, here we actually - // can see value which is not ready yet. Since everything that exists should - // be guaranteed by the caller to be loaded at this point, this can only happen - // if the code references classes that do not exist. Therefore, if the value is - // not ready here, we know that the loaded value will be 'null' once it is ready. - if (value.ready && value.clazz != null) { - loadedClasses.add(value.clazz); - } + classProvider = this.classProvider; + if (classProvider == null) { + return; } + + // Collects the types which might be represented in fully loaded class map. + knownClasses = Sets.newIdentityHashSet(); + knownClasses.addAll(classes.keySet()); } - return loadedClasses; - } - /** Forces loading of all the classes satisfying the criteria specified. */ - public void forceLoad(Predicate<DexType> load) { - if (classProvider != null) { - Set<DexType> loaded = Sets.newIdentityHashSet(); - synchronized (classes) { - loaded.addAll(classes.keySet()); + // Add all types the class provider provides. Note that it may take time for class + // provider to collect these types, so ve do it outside synchronized context. + knownClasses.addAll(classProvider.collectTypes()); + + // Make sure all the types in `knownClasses` are loaded. + // + // We just go and touch every class, thus triggering their loading if they + // are not loaded so far. In case the class has already been loaded, + // touching the class will be a no-op with minimal overhead. + for (DexType type : knownClasses) { + if (load.test(type)) { + get(type); } - Collection<DexType> types = classProvider.collectTypes(); - for (DexType type : types) { - if (load.test(type) && !loaded.contains(type)) { - get(type); // force-load type. + } + + synchronized (classes) { + if (this.classProvider == null) { + return; // Has been force-loaded concurrently. + } + + // We avoid calling get() on a class supplier unless we know it was loaded. + // At this time `classes` may have more types then `knownClasses`, but for + // all extra classes we expect the supplier to return 'null' after loading. + Iterator<Map.Entry<DexType, Supplier<T>>> iterator = classes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<DexType, Supplier<T>> e = iterator.next(); + + if (knownClasses.contains(e.getKey())) { + // Get the class (it is expected to be loaded by this time). + T clazz = e.getValue().get(); + if (clazz != null) { + // Since the class is already loaded, get rid of possible wrapping suppliers. + assert clazz.type == e.getKey(); + e.setValue(getTransparentSupplier(clazz)); + continue; + } } + + // If the type is not in `knownClasses` or resolves to `null`, + // just remove the record from the map. + iterator.remove(); } + + // Mark the class map as fully loaded. + this.classProvider = null; } } - // Represents a value in the class map. - final static class Value<T> { - volatile boolean ready; - T clazz; - - Value() { - ready = false; - clazz = null; + // Supplier implementing a thread-safe loader for a class loaded from a + // class provider. Helps avoid synchronizing on the whole class map + // when loading a class. + private static class ConcurrentClassLoader<T extends DexClass> implements Supplier<T> { + private ClassMap<T> classMap; + private ClassProvider<T> provider; + private DexType type; + + private T clazz = null; + private volatile boolean ready = false; + + ConcurrentClassLoader(ClassMap<T> classMap, ClassProvider<T> provider, DexType type) { + this.classMap = classMap; + this.provider = provider; + this.type = type; } - Value(T clazz) { - this.clazz = clazz; - this.ready = true; + @Override + public T get() { + if (ready) { + return clazz; + } + + synchronized (this) { + if (!ready) { + assert classMap != null && provider != null && type != null; + provider.collectClass(type, createdClass -> { + assert createdClass != null; + assert classMap.getClassKind().isOfKind(createdClass); + assert !ready; + + if (createdClass.type != type) { + throw new CompilationError( + "Class content provided for type descriptor " + type.toSourceString() + + " actually defines class " + createdClass.type.toSourceString()); + } + + if (clazz == null) { + clazz = createdClass; + } else { + // The class resolution *may* generate a compilation error as one of + // possible resolutions. In this case we leave `value` in (false, null) + // state so in rare case of another thread trying to get the same class + // before this error is propagated it will get the same conflict. + T oldClass = clazz; + clazz = null; + clazz = classMap.resolveClassConflict(oldClass, createdClass); + } + }); + + classMap = null; + provider = null; + type = null; + ready = true; + } + } + + assert ready; + assert classMap == null && provider == null && type == null; + return clazz; } } } diff --git a/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java index ee00c3976..45002d003 100644 --- a/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java +++ b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java @@ -6,6 +6,7 @@ package com.android.tools.r8.utils; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.graph.ClassKind; import com.android.tools.r8.graph.DexClasspathClass; +import java.util.function.Supplier; /** Represents a collection of classpath classes. */ public class ClasspathClassCollection extends ClassMap<DexClasspathClass> { @@ -19,6 +20,11 @@ public class ClasspathClassCollection extends ClassMap<DexClasspathClass> { } @Override + Supplier<DexClasspathClass> getTransparentSupplier(DexClasspathClass clazz) { + return clazz; + } + + @Override ClassKind getClassKind() { return ClassKind.CLASSPATH; } 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 9d6cfa5ea..7155a13df 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableSet; import java.nio.file.Path; import java.util.List; import java.util.function.BiFunction; +import java.util.function.Function; public class InternalOptions { @@ -140,8 +141,7 @@ public class InternalOptions { } public static class TestingOptions { - - public BiFunction<List<DexEncodedMethod>, CallGraph.Leaves, List<DexEncodedMethod>> irOrdering; + public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering; } public static class AttributeRemovalOptions { @@ -251,15 +251,18 @@ public class InternalOptions { public void ensureValid(boolean isMinifying) { if (innerClasses && !enclosingMethod) { - throw new CompilationError("Attribute InnerClasses implies EnclosingMethod attribute. " + - "Check -keepattributes directive."); + throw new CompilationError("Attribute InnerClasses requires EnclosingMethod attribute. " + + "Check -keepattributes directive."); } else if (!innerClasses && enclosingMethod) { - throw new CompilationError("Attribute EnclosingMethod requires InnerClasses attribute. " + - "Check -keepattributes directive."); + throw new CompilationError("Attribute EnclosingMethod requires InnerClasses attribute. " + + "Check -keepattributes directive."); + } else if (signature && !innerClasses) { + throw new CompilationError("Attribute Signature requires InnerClasses attribute. Check " + + "-keepattributes directive."); } else if (signature && isMinifying) { // TODO(38188583): Allow this once we can minify signatures. - throw new CompilationError("Attribute Signature cannot be kept when minifying. " + - "Check -keepattributes directive."); + throw new CompilationError("Attribute Signature cannot be kept when minifying. " + + "Check -keepattributes directive."); } } } diff --git a/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java b/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java index e11e660d3..c94d7beba 100644 --- a/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java +++ b/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java @@ -9,6 +9,7 @@ import com.android.tools.r8.graph.ClassKind; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexLibraryClass; import com.android.tools.r8.logging.Log; +import java.util.function.Supplier; /** Represents a collection of library classes. */ public class LibraryClassCollection extends ClassMap<DexLibraryClass> { @@ -31,6 +32,11 @@ public class LibraryClassCollection extends ClassMap<DexLibraryClass> { } @Override + Supplier<DexLibraryClass> getTransparentSupplier(DexLibraryClass clazz) { + return clazz; + } + + @Override ClassKind getClassKind() { return ClassKind.LIBRARY; } diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java index a37eb92b0..590ba834a 100644 --- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java +++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java @@ -11,25 +11,20 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.desugar.LambdaRewriter; import java.util.IdentityHashMap; import java.util.List; +import java.util.function.Supplier; /** Represents a collection of library classes. */ public class ProgramClassCollection extends ClassMap<DexProgramClass> { public static ProgramClassCollection create(List<DexProgramClass> classes) { // We have all classes preloaded, but not necessarily without conflicts. - IdentityHashMap<DexType, Value<DexProgramClass>> map = new IdentityHashMap<>(); + IdentityHashMap<DexType, Supplier<DexProgramClass>> map = new IdentityHashMap<>(); for (DexProgramClass clazz : classes) { - Value<DexProgramClass> value = map.get(clazz.type); - if (value == null) { - value = new Value<>(clazz); - map.put(clazz.type, value); - } else { - value.clazz = resolveClassConflictImpl(value.clazz, clazz); - } + map.merge(clazz.type, clazz, (a, b) -> resolveClassConflictImpl(a.get(), b.get())); } return new ProgramClassCollection(map); } - private ProgramClassCollection(IdentityHashMap<DexType, Value<DexProgramClass>> classes) { + private ProgramClassCollection(IdentityHashMap<DexType, Supplier<DexProgramClass>> classes) { super(classes, null); } @@ -44,6 +39,11 @@ public class ProgramClassCollection extends ClassMap<DexProgramClass> { } @Override + Supplier<DexProgramClass> getTransparentSupplier(DexProgramClass clazz) { + return clazz; + } + + @Override ClassKind getClassKind() { return ClassKind.PROGRAM; } diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java index 74f9ef9ed..8fdcfa58c 100644 --- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java +++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java @@ -12,7 +12,6 @@ import com.android.tools.r8.Resource; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.ir.conversion.CallGraph; import com.android.tools.r8.shaking.ProguardRuleParserException; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.OutputMode; @@ -29,7 +28,7 @@ import org.junit.Test; public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase { - public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods, CallGraph.Leaves leaves) { + public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods) { Collections.shuffle(methods); return methods; } diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java b/src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java deleted file mode 100644 index d5f70cce6..000000000 --- a/src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) 2017, 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.ir.deterministic; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -import com.android.tools.r8.CompilationException; -import com.android.tools.r8.R8Command; -import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.ir.conversion.CallGraph; -import com.android.tools.r8.shaking.ProguardRuleParserException; -import com.android.tools.r8.smali.SmaliTestBase; -import com.android.tools.r8.utils.AndroidApp; -import com.android.tools.r8.utils.DexInspector; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.ExecutionException; -import org.junit.Test; - -public class DeterministicProcessingTest extends SmaliTestBase { - public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods, CallGraph.Leaves leaves) { - Collections.shuffle(methods); - return methods; - } - - // This test will process the code a number of times each time shuffling the order in which - // the methods are processed. It does not do a exhaustive probing of all permutations, so if - // this fails it might not fail consistently, but under all circumstances a failure does reveal - // non-determinism in the IR-processing. - @Test - public void shuffleOrderTest() - throws IOException, ExecutionException, ProguardRuleParserException, CompilationException { - final int ITERATIONS = 25; - R8Command.Builder builder = - R8Command.builder() - .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class)) - .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar())); - byte[] expectedDex = null; - for (int i = 0; i < ITERATIONS; i++) { - AndroidApp result = - ToolHelper.runR8( - builder.build(), options -> { - // For this test just do random shuffle. - options.testing.irOrdering = this::shuffle; - // Only use one thread to process to process in the order decided by the callback. - options.numberOfThreads = 1; - }); - List<byte[]> dex = result.writeToMemory(); - assertEquals(1, dex.size()); - if (i == 0) { - assert expectedDex == null; - expectedDex = dex.get(0); - } else { - assertArrayEquals(expectedDex, dex.get(0)); - } - } - } - - // Global variables used by the shuffler callback. - int iteration = 0; - Class testClass = null; - - public List<DexEncodedMethod> permutationsOfTwo( - List<DexEncodedMethod> methods, CallGraph.Leaves leaves) { - if (!leaves.hasBrokeCycles()) { - return methods; - } - methods.sort(Comparator.comparing(DexEncodedMethod::qualifiedName)); - assertEquals(2, methods.size()); - String className = testClass.getTypeName(); - // Check that we are permutating the expected methods. - assertEquals(className + ".a", methods.get(0).qualifiedName()); - assertEquals(className + ".b", methods.get(1).qualifiedName()); - if (iteration == 1) { - Collections.swap(methods, 0, 1); - } - return methods; - } - - public void runTest(Class clazz, boolean inline) throws Exception { - final int ITERATIONS = 2; - testClass = clazz; - R8Command.Builder builder = - R8Command.builder() - .addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)) - .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar())); - List<byte[]> results = new ArrayList<>(); - for (iteration = 0; iteration < ITERATIONS; iteration++) { - AndroidApp result = - ToolHelper.runR8( - builder.build(), options -> { - options.inlineAccessors = inline; - // Callback to determine IR processing order. - options.testing.irOrdering = this::permutationsOfTwo; - // Only use one thread to process to process in the order decided by the callback. - options.numberOfThreads = 1; - }); - List<byte[]> dex = result.writeToMemory(); - DexInspector x = new DexInspector(result); - assertEquals(1, dex.size()); - results.add(dex.get(0)); - } - for (int i = 0; i < ITERATIONS - 1; i++) { - assertArrayEquals(results.get(i), results.get(i + 1)); - } - } - - @Test - public void testReturnArguments() throws Exception { - runTest(TestClassReturnsArgument.class, false); - } - - @Test - public void testReturnConstant() throws Exception { - runTest(TestClassReturnsConstant.class, false); - } - - @Test - public void testInline() throws Exception { - runTest(TestClassInline.class, true); - } -} diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/TestClass.java b/src/test/java/com/android/tools/r8/ir/deterministic/TestClass.java deleted file mode 100644 index 5bbfa5199..000000000 --- a/src/test/java/com/android/tools/r8/ir/deterministic/TestClass.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2017, 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.ir.deterministic; - -public class TestClass { - - // This will be inlined, causing some of the calls below to go away providing more - // inlining opportunities. - public static boolean alwaysFalse() { - return false; - } - - public static int a0() { - return b0(); - } - - public static int b0() { - if (alwaysFalse()) { - a0(); - return 0; - } - return 1; - } - - public static int a1() { - return b1(); - } - - public static int b1() { - if (alwaysFalse()) { - a1(); - return 0; - } - return 1; - } - - public static int a2() { - return b2(); - } - - public static int b2() { - return c2(); - } - - public static int c2() { - if (alwaysFalse()) { - a2(); - return 0; - } - return 1; - } - - public static int a3() { - return b3(); - } - - public static int b3() { - return c3(); - } - - public static int c3() { - if (alwaysFalse()) { - a3(); - return 0; - } - return 1; - } -} diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassInline.java b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassInline.java deleted file mode 100644 index aa50f0297..000000000 --- a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassInline.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2017, 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.ir.deterministic; - -public class TestClassInline { - - public static boolean alwaysFalse() { - return false; - } - - public static int a() { - return b(); - } - - public static int b() { - if (alwaysFalse()) { - a(); - return 0; - } - return 1; - } -} diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsArgument.java b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsArgument.java deleted file mode 100644 index ac70401bd..000000000 --- a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsArgument.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2017, 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.ir.deterministic; - -import java.util.Random; - -public class TestClassReturnsArgument { - - public static int a(int x) { - return b(new Random().nextInt()); - } - - public static int b(int x) { - a(1); - return x; - } -} diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsConstant.java b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsConstant.java deleted file mode 100644 index 7d63f60a6..000000000 --- a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsConstant.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2017, 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.ir.deterministic; - -import java.util.Random; - -public class TestClassReturnsConstant { - - public static int a(int x) { - return b(new Random().nextInt()); - } - - public static int b(int x) { - a(x); - return 1; - } -} diff --git a/third_party/gradle/gradle.tar.gz.sha1 b/third_party/gradle/gradle.tar.gz.sha1 index 008d84c90..c9f900a5e 100644 --- a/third_party/gradle/gradle.tar.gz.sha1 +++ b/third_party/gradle/gradle.tar.gz.sha1 @@ -1 +1 @@ -1d118f48ff995b7eca95f3655293ea959779a6c0
\ No newline at end of file +2d9bb7b50771ad8252cdcf83a1a9648af7eee837
\ No newline at end of file diff --git a/tools/gradle.py b/tools/gradle.py index 488dc87b8..14b5f3401 100755 --- a/tools/gradle.py +++ b/tools/gradle.py @@ -14,6 +14,7 @@ import utils GRADLE_DIR = os.path.join(utils.REPO_ROOT, 'third_party', 'gradle') GRADLE_SHA1 = os.path.join(GRADLE_DIR, 'gradle.tar.gz.sha1') +GRADLE_TGZ = os.path.join(GRADLE_DIR, 'gradle.tar.gz') if utils.IsWindows(): GRADLE = os.path.join(GRADLE_DIR, 'gradle', 'bin', 'gradle.bat') else: @@ -27,9 +28,12 @@ def PrintCmd(s): sys.stdout.flush() def EnsureGradle(): - if not os.path.exists(GRADLE): - # Bootstrap gradle, everything else is controlled using gradle. + if not os.path.exists(GRADLE) or os.path.getmtime(GRADLE_TGZ) < os.path.getmtime(GRADLE_SHA1): + # Bootstrap or update gradle, everything else is controlled using gradle. utils.DownloadFromGoogleCloudStorage(GRADLE_SHA1) + # Update the mtime of the tar file to make sure we do not run again unless + # there is an update. + os.utime(GRADLE_TGZ, None) else: print 'gradle.py: Gradle binary present' diff --git a/tools/run_on_app.py b/tools/run_on_app.py index d7db7fb16..a091e1ca7 100755 --- a/tools/run_on_app.py +++ b/tools/run_on_app.py @@ -4,6 +4,7 @@ # BSD-style license that can be found in the LICENSE file. from __future__ import print_function +from glob import glob import optparse import os import sys @@ -74,14 +75,18 @@ def ParseOptions(): 'the run.') result.add_option('--print-runtimeraw', metavar='BENCHMARKNAME', - help='Prints the line \'<BENCHMARKNAME>(RunTimeRaw):' + - ' <elapsed> ms\' at the end where <elapsed> is' + - ' the elapsed time in milliseconds.') + help='Print the line \'<BENCHMARKNAME>(RunTimeRaw):' + + ' <elapsed> ms\' at the end where <elapsed> is' + + ' the elapsed time in milliseconds.') result.add_option('--print-memoryuse', metavar='BENCHMARKNAME', - help='Prints the line \'<BENCHMARKNAME>(MemoryUse):' + - ' <mem>\' at the end where <mem> is the peak' + - ' peak resident set size (VmHWM) in bytes.') + help='Print the line \'<BENCHMARKNAME>(MemoryUse):' + + ' <mem>\' at the end where <mem> is the peak' + + ' peak resident set size (VmHWM) in bytes.') + result.add_option('--print-dexsegments', + metavar='BENCHMARKNAME', + help='Print the sizes of individual dex segments as ' + + '\'<BENCHMARKNAME>-<segment>(CodeSize): <bytes>\'') return result.parse_args() # Most apps have the -printmapping and -printseeds in the Proguard @@ -207,5 +212,9 @@ def main(): print('{}(RunTimeRaw): {} ms' .format(options.print_runtimeraw, 1000.0 * (time.time() - t0))) + if options.print_dexsegments: + dex_files = glob(os.path.join(outdir, '*.dex')) + utils.print_dexsegments(options.print_dexsegments, dex_files) + if __name__ == '__main__': sys.exit(main()) diff --git a/tools/run_proguard_dx_on_gmscore.py b/tools/run_proguard_dx_on_gmscore.py index de716b082..06f56f90b 100755 --- a/tools/run_proguard_dx_on_gmscore.py +++ b/tools/run_proguard_dx_on_gmscore.py @@ -6,6 +6,7 @@ # Run ProGuard and the DX or CompatDX (= D8) tool on GmsCore V10. from __future__ import print_function +from glob import glob from os import makedirs from os.path import exists, join from subprocess import check_call @@ -37,18 +38,22 @@ def parse_arguments(): default = os.getcwd()) parser.add_argument('--print-runtimeraw', metavar = 'BENCHMARKNAME', - help = 'Prints the line \'<BENCHMARKNAME>(RunTimeRaw): <elapsed>' + + help = 'Print the line \'<BENCHMARKNAME>(RunTimeRaw): <elapsed>' + ' ms\' at the end where <elapsed> is the elapsed time in' + ' milliseconds.') parser.add_argument('--print-memoryuse', metavar='BENCHMARKNAME', - help='Prints the line \'<BENCHMARKNAME>(MemoryUse):' + + help='Print the line \'<BENCHMARKNAME>(MemoryUse):' + ' <mem>\' at the end where <mem> is the peak' + ' peak resident set size (VmHWM) in bytes.') parser.add_argument('--compatdx', help = 'Use CompatDx (D8) instead of DX.', default = False, action = 'store_true') + parser.add_argument('--print-dexsegments', + metavar = 'BENCHMARKNAME', + help = 'Print the sizes of individual dex segments as ' + + '\'<BENCHMARKNAME>-<segment>(CodeSize): <bytes>\'') return parser.parse_args() def Main(): @@ -113,5 +118,9 @@ def Main(): print('{}(RunTimeRaw): {} ms' .format(options.print_runtimeraw, 1000.0 * (time.time() - t0))) + if options.print_dexsegments: + dex_files = glob(os.path.join(outdir, '*.dex')) + utils.print_dexsegments(options.print_dexsegments, dex_files) + if __name__ == '__main__': sys.exit(Main()) diff --git a/tools/test_android_cts.py b/tools/test_android_cts.py index 606611d82..4ef8039b1 100755 --- a/tools/test_android_cts.py +++ b/tools/test_android_cts.py @@ -246,8 +246,7 @@ def Main(): print('Comparing test results to baseline:\n') passing_tests = consistently_passing_tests_from_test_results([results_xml]) - baseline_results = \ - [f for f in glob(join(CTS_BASELINE_FILES_DIR, '*.xml'))] + baseline_results = glob(join(CTS_BASELINE_FILES_DIR, '*.xml')) assert len(baseline_results) != 0 passing_tests_in_baseline = \ diff --git a/tools/test_framework.py b/tools/test_framework.py index 57279bfb4..04a12d326 100755 --- a/tools/test_framework.py +++ b/tools/test_framework.py @@ -35,9 +35,6 @@ GOYT_EXE = os.path.join('third_party', 'goyt', 'goyt_160525751') FRAMEWORK_JAR = os.path.join('third_party', 'framework', 'framework_160115954.jar') -DEX_SEGMENTS_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', - 'dexsegments.jar') -DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)') MIN_SDK_VERSION = '24' def parse_arguments(): @@ -61,27 +58,6 @@ def parse_arguments(): action = 'store_true') return parser.parse_args() -# Return a dictionary: {segment_name -> segments_size} -def getDexSegmentSizes(dex_files): - assert len(dex_files) > 0 - cmd = ['java', '-jar', DEX_SEGMENTS_JAR] - cmd.extend(dex_files) - utils.PrintCmd(cmd) - output = subprocess.check_output(cmd) - - matches = DEX_SEGMENTS_RESULT_PATTERN.findall(output) - - if matches is None or len(matches) == 0: - raise Exception('DexSegments failed to return any output for' \ - ' these files: {}'.format(dex_files)) - - result = {} - - for match in matches: - result[match[0]] = int(match[1]) - - return result - def Main(): args = parse_arguments() @@ -136,9 +112,7 @@ def Main(): print('{}-Total(CodeSize): {}' .format(args.name, code_size)) - for segment_name, size in getDexSegmentSizes(dex_files).items(): - print('{}-{}(CodeSize): {}' - .format(args.name, segment_name, size)) + utils.print_dexsegments(args.name, dex_files) if __name__ == '__main__': sys.exit(Main()) diff --git a/tools/utils.py b/tools/utils.py index 2564adc2e..dfc35d7aa 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -15,6 +15,9 @@ import tempfile TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..'))) REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..')) MEMORY_USE_TMP_FILE = 'memory_use.tmp' +DEX_SEGMENTS_JAR = os.path.join(REPO_ROOT, 'build', 'libs', + 'dexsegments.jar') +DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)') def PrintCmd(s): if type(s) is list: @@ -140,4 +143,30 @@ def grep_memoryuse(logfile): .format(unit)) if result is None: raise Exception('No memory usage found in log: {}'.format(logfile)) - return result
\ No newline at end of file + return result + +# Return a dictionary: {segment_name -> segments_size} +def getDexSegmentSizes(dex_files): + assert len(dex_files) > 0 + cmd = ['java', '-jar', DEX_SEGMENTS_JAR] + cmd.extend(dex_files) + PrintCmd(cmd) + output = subprocess.check_output(cmd) + + matches = DEX_SEGMENTS_RESULT_PATTERN.findall(output) + + if matches is None or len(matches) == 0: + raise Exception('DexSegments failed to return any output for' \ + ' these files: {}'.format(dex_files)) + + result = {} + + for match in matches: + result[match[0]] = int(match[1]) + + return result + +def print_dexsegments(prefix, dex_files): + for segment_name, size in getDexSegmentSizes(dex_files).items(): + print('{}-{}(CodeSize): {}' + .format(prefix, segment_name, size)) |