aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2017-07-19 07:24:29 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2017-07-19 07:24:29 +0000
commitbe69fcefad315b2068a9903019108f879ca4db56 (patch)
tree9749176d782b4a256f9d71fb30007b81dceca872
parent5a59612a39e9a2b27cbacc2e4a252227be9aed35 (diff)
parent8c5f281882ee39c2c31608cc1267c57e3f403949 (diff)
downloadr8-oreo-mr1-vts-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
-rw-r--r--build.gradle77
-rw-r--r--src/main/java/com/android/tools/r8/compatdx/CompatDx.java9
-rw-r--r--src/main/java/com/android/tools/r8/graph/Code.java6
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexAnnotation.java72
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java9
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexApplication.java16
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexClass.java12
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexClasspathClass.java8
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexItemFactory.java19
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexLibraryClass.java8
-rw-r--r--src/main/java/com/android/tools/r8/graph/DexProgramClass.java8
-rw-r--r--src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java6
-rw-r--r--src/main/java/com/android/tools/r8/ir/code/IRCode.java12
-rw-r--r--src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java190
-rw-r--r--src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java27
-rw-r--r--src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java51
-rw-r--r--src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java116
-rw-r--r--src/main/java/com/android/tools/r8/ir/optimize/Inliner.java4
-rw-r--r--src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java12
-rw-r--r--src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java3
-rw-r--r--src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java109
-rw-r--r--src/main/java/com/android/tools/r8/naming/Minifier.java3
-rw-r--r--src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java18
-rw-r--r--src/main/java/com/android/tools/r8/utils/ClassMap.java245
-rw-r--r--src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java6
-rw-r--r--src/main/java/com/android/tools/r8/utils/InternalOptions.java19
-rw-r--r--src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java6
-rw-r--r--src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java18
-rw-r--r--src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java3
-rw-r--r--src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java130
-rw-r--r--src/test/java/com/android/tools/r8/ir/deterministic/TestClass.java70
-rw-r--r--src/test/java/com/android/tools/r8/ir/deterministic/TestClassInline.java24
-rw-r--r--src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsArgument.java19
-rw-r--r--src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsConstant.java19
-rw-r--r--third_party/gradle/gradle.tar.gz.sha12
-rwxr-xr-xtools/gradle.py8
-rwxr-xr-xtools/run_on_app.py21
-rwxr-xr-xtools/run_proguard_dx_on_gmscore.py13
-rwxr-xr-xtools/test_android_cts.py3
-rwxr-xr-xtools/test_framework.py28
-rw-r--r--tools/utils.py31
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))