Merge commit '9e7ce5ac7cde29d9c9e806143cc45b1bd9c0302b' into dev-release
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE
index adf291d..2cedeee 100644
--- a/LIBRARY-LICENSE
+++ b/LIBRARY-LICENSE
@@ -26,11 +26,6 @@
license: Apache License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.html
url: http://fasutil.di.unimi.it/
-- artifact: net.sf.jopt-simple:jopt-simple:+
- name: JOpt Simple
- license: The MIT License
- licenseUrl: http://www.opensource.org/licenses/mit-license.php
- url: http://pholser.github.com/jopt-simple
- artifact: org.ow2.asm:asm-commons:+
name: ASM Commons
copyrightHolder: INRIA, France Telecom
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index b916f47..a329c0f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -33,10 +33,11 @@
continue
diff = check_output(
['git', 'diff', '--no-prefix', '-U0', branch, '--', path])
+
proc = Popen(FMT_CMD, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
(stdout, stderr) = proc.communicate(input=diff)
if len(stdout) > 0:
- results.append(output_api.PresubmitError(stdout))
+ results.append(output_api.PresubmitError(stdout.decode('utf-8')))
if len(results) > 0:
results.append(output_api.PresubmitError(
"""Please fix the formatting by running:
@@ -69,7 +70,7 @@
if not path.endswith('InternalOptions.java'):
continue
diff = check_output(
- ['git', 'diff', '--no-prefix', '-U0', branch, '--', path])
+ ['git', 'diff', '--no-prefix', '-U0', branch, '--', path]).decode('utf-8')
if 'DETERMINISTIC_DEBUGGING' in diff:
return [output_api.PresubmitError(diff)]
return []
diff --git a/build.gradle b/build.gradle
index 5df3bb4..35cc8df 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,14 +11,6 @@
import tasks.DownloadDependency
import tasks.GetJarsFromConfiguration
-buildscript {
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
plugins {
id "net.ltgt.errorprone" version "2.0.2"
}
@@ -27,6 +19,8 @@
apply plugin: 'idea'
ext {
+ // When updating dependencies also update and run
+ // tools/create_local_maven_with_dependencies.py
androidSupportVersion = '25.4.0'
asmVersion = '9.5' // When updating update tools/asmifier.py, build.src and Toolhelper as well.
javassistVersion = '3.29.2-GA'
@@ -46,8 +40,9 @@
}
repositories {
- google()
- mavenCentral()
+ maven {
+ url uri('file:third_party/dependencies')
+ }
}
if (project.hasProperty('with_code_coverage')) {
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 85815bd..dc6f3be 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -5,8 +5,9 @@
apply plugin: 'idea'
repositories {
- google()
- mavenCentral()
+ maven {
+ url uri('file:../third_party/dependencies')
+ }
}
ext {
diff --git a/commonBuildSrc/settings.gradle.kts b/commonBuildSrc/settings.gradle.kts
index 75c6675..f346f85 100644
--- a/commonBuildSrc/settings.gradle.kts
+++ b/commonBuildSrc/settings.gradle.kts
@@ -4,15 +4,23 @@
pluginManagement {
repositories {
- gradlePluginPortal()
+ maven {
+ url = uri("file:../third_party/dependencies_new")
+ }
+ maven {
+ url = uri("file:../third_party/dependencies")
+ }
}
}
dependencyResolutionManagement {
repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
+ maven {
+ url = uri("file:../third_party/dependencies_new")
+ }
+ maven {
+ url = uri("file:../third_party/dependencies")
+ }
}
}
diff --git a/d8_r8/keepanno/settings.gradle.kts b/d8_r8/keepanno/settings.gradle.kts
index 7b4d459..c214cca 100644
--- a/d8_r8/keepanno/settings.gradle.kts
+++ b/d8_r8/keepanno/settings.gradle.kts
@@ -4,15 +4,24 @@
pluginManagement {
repositories {
- gradlePluginPortal()
+ maven {
+ url = uri("file:../../third_party/dependencies")
+ }
+ maven {
+ url = uri("file:../../third_party/dependencies_new")
+ }
}
}
dependencyResolutionManagement {
- repositories {
- mavenCentral()
- gradlePluginPortal()
+ repositories {
+ maven {
+ url= uri("file:../../third_party/dependencies")
}
+ maven {
+ url= uri("file:../../third_party/dependencies_new")
+ }
+ }
}
rootProject.name = "keepanno"
diff --git a/d8_r8/main/settings.gradle.kts b/d8_r8/main/settings.gradle.kts
index 2d727f0..347b7a4 100644
--- a/d8_r8/main/settings.gradle.kts
+++ b/d8_r8/main/settings.gradle.kts
@@ -4,15 +4,24 @@
pluginManagement {
repositories {
- gradlePluginPortal()
+ maven {
+ url = uri("file:../../third_party/dependencies")
+ }
+ maven {
+ url = uri("file:../../third_party/dependencies_new")
+ }
}
}
dependencyResolutionManagement {
- repositories {
- mavenCentral()
- gradlePluginPortal()
+ repositories {
+ maven {
+ url= uri("file:../../third_party/dependencies")
}
+ maven {
+ url= uri("file:../../third_party/dependencies_new")
+ }
+ }
}
rootProject.name = "r8"
diff --git a/d8_r8/settings.gradle.kts b/d8_r8/settings.gradle.kts
index 2feb93c..9d2dfa1 100644
--- a/d8_r8/settings.gradle.kts
+++ b/d8_r8/settings.gradle.kts
@@ -6,19 +6,67 @@
pluginManagement {
repositories {
- gradlePluginPortal()
+ maven {
+ url = uri("file:../../third_party/dependencies")
+ }
+ maven {
+ url = uri("file:../../third_party/dependencies_new")
+ }
}
}
dependencyResolutionManagement {
repositories {
- mavenCentral()
- gradlePluginPortal()
+ maven {
+ url= uri("file:../third_party/dependencies")
+ }
+ maven {
+ url= uri("file:../third_party/dependencies_new")
+ }
}
}
rootProject.name = "d8-r8"
+// Bootstrap building by downloading dependencies.
+fun String.execute() =
+ org.codehaus.groovy.runtime.ProcessGroovyMethods.execute(this)
+
+fun Process.out() =
+ String(
+ this.getInputStream().readAllBytes(),
+ java.nio.charset.StandardCharsets.UTF_8)
+fun Process.err() =
+ String(
+ this.getErrorStream().readAllBytes(),
+ java.nio.charset.StandardCharsets.UTF_8)
+
+val dependencies_bucket = "r8-deps"
+val dependencies_sha1_file = "third_party/dependencies.tar.gz.sha1"
+var cmd =
+ ("download_from_google_storage.py --extract"
+ + " --bucket ${dependencies_bucket}"
+ + " --sha1_file ${dependencies_sha1_file}")
+var process = cmd.execute()
+process.waitFor()
+if (process.exitValue() != 0) {
+ throw GradleException(
+ "Bootstrapping dependencies download failed:"
+ + "\n${process.err()}\n${process.out()}")
+}
+val dependencies_new_sha1_file = "third_party/dependencies_new.tar.gz.sha1"
+cmd =
+ ("download_from_google_storage.py --extract"
+ + " --bucket ${dependencies_bucket}"
+ + " --sha1_file ${dependencies_new_sha1_file}")
+process = cmd.execute()
+process.waitFor()
+if (process.exitValue() != 0) {
+ throw GradleException(
+ "Bootstrapping dependencies_new download failed:"
+ + "\n${process.err()}\n${process.out()}")
+}
+
// This project is temporarily located in d8_r8. When moved to root, the parent
// folder should just be removed.
includeBuild(rootProject.projectDir.parentFile.resolve("commonBuildSrc"))
diff --git a/d8_r8/test/settings.gradle.kts b/d8_r8/test/settings.gradle.kts
index 3138d52..5946a12 100644
--- a/d8_r8/test/settings.gradle.kts
+++ b/d8_r8/test/settings.gradle.kts
@@ -2,13 +2,28 @@
// 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.
-dependencyResolutionManagement {
+pluginManagement {
repositories {
- mavenCentral()
- gradlePluginPortal()
+ maven {
+ url = uri("file:../../third_party/dependencies")
+ }
+ maven {
+ url = uri("file:../../third_party/dependencies_new")
+ }
}
}
+dependencyResolutionManagement {
+ repositories {
+ maven {
+ url= uri("file:../third_party/dependencies")
+ }
+ maven {
+ url= uri("file:../third_party/dependencies_new")
+ }
+ }
+}
+
rootProject.name = "r8-tests"
val root = rootProject.projectDir.parentFile
diff --git a/d8_r8/test_modules/tests_java_8/settings.gradle.kts b/d8_r8/test_modules/tests_java_8/settings.gradle.kts
index 290b820..6ab7fac 100644
--- a/d8_r8/test_modules/tests_java_8/settings.gradle.kts
+++ b/d8_r8/test_modules/tests_java_8/settings.gradle.kts
@@ -3,15 +3,24 @@
// BSD-style license that can be found in the LICENSE file.
pluginManagement {
- repositories {
- gradlePluginPortal()
- }
+ repositories {
+ maven {
+ url = uri("file:../../../third_party/dependencies")
+ }
+ maven {
+ url = uri("file:../../../third_party/dependencies_new")
+ }
+ }
}
dependencyResolutionManagement {
repositories {
- mavenCentral()
- gradlePluginPortal()
+ maven {
+ url= uri("file:../third_party/dependencies")
+ }
+ maven {
+ url= uri("file:../third_party/dependencies_new")
+ }
}
}
diff --git a/library-licensing/checkerframework.txt b/library-licensing/checkerframework.txt
new file mode 100644
index 0000000..9837c6b
--- /dev/null
+++ b/library-licensing/checkerframework.txt
@@ -0,0 +1,22 @@
+Checker Framework qualifiers
+Copyright 2004-present by the Checker Framework developers
+
+MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/library-licensing/jopt-simple.txt b/library-licensing/jopt-simple.txt
deleted file mode 100644
index 2077257..0000000
--- a/library-licensing/jopt-simple.txt
+++ /dev/null
@@ -1,22 +0,0 @@
- The MIT License
-
- Copyright (c) 2004-2016 Paul R. Holser, Jr.
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/settings.gradle b/settings.gradle
index 9924d92..969cb9c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,5 +2,24 @@
// 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
+
rootProject.name = 'r8'
+// Bootstrap building by downloading dependencies.
+def dependencies_bucket = "r8-deps"
+def dependencies_sha1_file = "third_party/dependencies.tar.gz.sha1"
+def extension = OperatingSystem.current().isWindows() ? ".bat" : ".py"
+
+def cmd =
+ ("download_from_google_storage${extension} --extract"
+ + " --bucket ${dependencies_bucket}"
+ + " --sha1_file ${dependencies_sha1_file}")
+def process = cmd.execute()
+process.waitFor()
+if (process.exitValue() != 0) {
+ throw new GradleException(
+ "Bootstrapping dependencies download failed:\n"
+ + "STDOUT:\n${process.in.text}\n"
+ + "STDERR:\n${process.err.text}")
+}
\ No newline at end of file
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 8f9378e..7636cbd 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
@@ -30,7 +30,7 @@
import com.android.tools.r8.ir.conversion.passes.BinopRewriter;
import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination;
import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter;
-import com.android.tools.r8.ir.conversion.passes.SplitBranchOnKnownBoolean;
+import com.android.tools.r8.ir.conversion.passes.SplitBranch;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter;
@@ -115,7 +115,7 @@
protected final InternalOptions options;
public final CodeRewriter codeRewriter;
public final CommonSubexpressionElimination commonSubexpressionElimination;
- private final SplitBranchOnKnownBoolean splitBranchOnKnownBoolean;
+ private final SplitBranch splitBranch;
public final AssertionErrorTwoArgsConstructorRewriter assertionErrorTwoArgsConstructorRewriter;
private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover();
public final MemberValuePropagation<?> memberValuePropagation;
@@ -155,6 +155,7 @@
// Use AtomicBoolean to satisfy TSAN checking (see b/153714743).
AtomicBoolean seenNotNeverMergePrefix = new AtomicBoolean();
AtomicBoolean seenNeverMergePrefix = new AtomicBoolean();
+ String conflictingPrefixesErrorMessage = null;
/**
* The argument `appView` is used to determine if whole program optimizations are allowed or not
@@ -167,7 +168,7 @@
this.options = appView.options();
this.codeRewriter = new CodeRewriter(appView);
this.commonSubexpressionElimination = new CommonSubexpressionElimination(appView);
- this.splitBranchOnKnownBoolean = new SplitBranchOnKnownBoolean(appView);
+ this.splitBranch = new SplitBranch(appView);
this.assertionErrorTwoArgsConstructorRewriter =
appView.options().desugarState.isOn()
? new AssertionErrorTwoArgsConstructorRewriter(appView)
@@ -777,7 +778,7 @@
timing.end();
}
timing.end();
- splitBranchOnKnownBoolean.run(code.context(), code, timing);
+ splitBranch.run(code.context(), code, timing);
if (options.enableRedundantConstNumberOptimization) {
timing.begin("Remove const numbers");
codeRewriter.redundantConstNumberRemoval(code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
index 532c129..172e90a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -195,64 +195,68 @@
// TODO(b/168001352): Consider requiring that no 'never merge' prefix is ever seen as a
// passthrough object.
if (seenNeverMergePrefix.get() && seenNotNeverMergePrefix.get()) {
- StringBuilder message = new StringBuilder();
- message
- .append("Merging DEX file containing classes with prefix")
- .append(neverMerge.getPrefixes().size() > 1 ? "es " : " ");
- for (int i = 0; i < neverMerge.getPrefixes().size(); i++) {
- message
- .append("'")
- .append(neverMerge.getPrefixes().get(i).toString().substring(1).replace('/', '.'))
- .append("'")
- .append(i < neverMerge.getPrefixes().size() - 1 ? ", " : "");
- }
- if (!neverMerge.getExceptionPrefixes().isEmpty()) {
- message
- .append(" with other classes, except classes with prefix")
- .append(neverMerge.getExceptionPrefixes().size() > 1 ? "es " : " ");
- for (int i = 0; i < neverMerge.getExceptionPrefixes().size(); i++) {
- message
- .append("'")
- .append(
- neverMerge
- .getExceptionPrefixes()
- .get(i)
- .toString()
- .substring(1)
- .replace('/', '.'))
- .append("'")
- .append(i < neverMerge.getExceptionPrefixes().size() - 1 ? ", " : "");
- }
- message.append(",");
- } else {
- message.append(" with classes with any other prefixes");
- }
- message.append(" is not allowed: ");
- boolean first = true;
- int limit = 11;
- for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
- if (!clazz.type.descriptor.startsWith(neverMergePrefix)) {
- if (hasExceptionPrefix(clazz)) {
- continue;
- }
- if (limit-- < 0) {
- message.append("..");
- break;
- }
- if (first) {
- first = false;
- } else {
- message.append(", ");
- }
- message.append(clazz.type);
- }
- }
- message.append(".");
- throw new CompilationError(message.toString());
+ throw new CompilationError(getOrComputeConflictingPrefixesErrorMessage(neverMergePrefix));
}
}
}
+ private synchronized String getOrComputeConflictingPrefixesErrorMessage(
+ DexString neverMergePrefix) {
+ if (conflictingPrefixesErrorMessage != null) {
+ return conflictingPrefixesErrorMessage;
+ }
+ StringBuilder message = new StringBuilder();
+ message
+ .append("Merging DEX file containing classes with prefix")
+ .append(neverMerge.getPrefixes().size() > 1 ? "es " : " ");
+ for (int i = 0; i < neverMerge.getPrefixes().size(); i++) {
+ message
+ .append("'")
+ .append(neverMerge.getPrefixes().get(i).toString().substring(1).replace('/', '.'))
+ .append("'")
+ .append(i < neverMerge.getPrefixes().size() - 1 ? ", " : "");
+ }
+ if (!neverMerge.getExceptionPrefixes().isEmpty()) {
+ message
+ .append(" with other classes, except classes with prefix")
+ .append(neverMerge.getExceptionPrefixes().size() > 1 ? "es " : " ");
+ for (int i = 0; i < neverMerge.getExceptionPrefixes().size(); i++) {
+ message
+ .append("'")
+ .append(
+ neverMerge.getExceptionPrefixes().get(i).toString().substring(1).replace('/', '.'))
+ .append("'")
+ .append(i < neverMerge.getExceptionPrefixes().size() - 1 ? ", " : "");
+ }
+ message.append(",");
+ } else {
+ message.append(" with classes with any other prefixes");
+ }
+ message.append(" is not allowed: ");
+ boolean first = true;
+ int limit = 11;
+ for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+ if (!clazz.type.descriptor.startsWith(neverMergePrefix)) {
+ if (hasExceptionPrefix(clazz)) {
+ continue;
+ }
+ if (limit-- < 0) {
+ message.append("..");
+ break;
+ }
+ if (first) {
+ first = false;
+ } else {
+ message.append(", ");
+ }
+ message.append(clazz.type);
+ }
+ }
+ message.append(".");
+ conflictingPrefixesErrorMessage = message.toString();
+ return conflictingPrefixesErrorMessage;
+ }
+
private boolean hasExceptionPrefix(DexProgramClass clazz) {
for (DexString exceptionPrefix : neverMerge.getExceptionPrefixes()) {
if (clazz.type.descriptor.startsWith(exceptionPrefix)) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranchOnKnownBoolean.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
similarity index 68%
rename from src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranchOnKnownBoolean.java
rename to src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
index 134d2bc..9a3a024 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranchOnKnownBoolean.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
@@ -23,17 +24,17 @@
import java.util.Map;
import java.util.Set;
-public class SplitBranchOnKnownBoolean extends CodeRewriterPass<AppInfo> {
+public class SplitBranch extends CodeRewriterPass<AppInfo> {
private static final boolean ALLOW_PARTIAL_REWRITE = true;
- public SplitBranchOnKnownBoolean(AppView<?> appView) {
+ public SplitBranch(AppView<?> appView) {
super(appView);
}
@Override
String getTimingId() {
- return "SplitBranchOnKnownBoolean";
+ return "SplitBranch";
}
@Override
@@ -89,17 +90,31 @@
private Map<Goto, BasicBlock> findGotosToRetarget(List<BasicBlock> candidates) {
Map<Goto, BasicBlock> newTargets = new LinkedHashMap<>();
for (BasicBlock block : candidates) {
- // We need to verify any instruction in between the if and the chain of phis is empty (we
- // could duplicate instruction, but the common case is empty).
+ // We need to verify any instruction in between the if and the chain of phis is empty or just
+ // a constant used in the If instruction (we could duplicate instruction, but the common case
+ // is empty).
// Then we can redirect any known value. This can lead to dead code.
If theIf = block.exit().asIf();
- Set<Phi> allowedPhis = getAllowedPhis(theIf.lhs().asPhi());
+ Set<Phi> allowedPhis = getAllowedPhis(nonConstNumberOperand(theIf).asPhi());
Set<Phi> foundPhis = Sets.newIdentityHashSet();
WorkList.newIdentityWorkList(block)
.process(
(current, workList) -> {
if (current.getInstructions().size() > 1) {
- return;
+ // We allow a single instruction, which is the constant used exclusively in the
+ // if. This is run before constant canonicalization.
+ if (theIf.isZeroTest()
+ || current.getInstructions().size() != 2
+ || !current.entry().isConstNumber()) {
+ return;
+ }
+ Value value = current.entry().outValue();
+ if (value.hasPhiUsers()
+ || value.uniqueUsers().size() > 1
+ || (value.uniqueUsers().size() == 1
+ && value.uniqueUsers().iterator().next() != theIf)) {
+ return;
+ }
}
if (current != block && !current.exit().isGoto()) {
return;
@@ -135,30 +150,61 @@
return newTargets;
}
+ private boolean isNumberAgainstConstNumberIf(If theIf) {
+ if (!(theIf.lhs().getType().isInt() || theIf.lhs().getType().isFloat())) {
+ return false;
+ }
+ if (theIf.isZeroTest()) {
+ return true;
+ }
+ assert theIf.lhs().getType() == theIf.rhs().getType();
+ return theIf.lhs().isConstNumber() || theIf.rhs().isConstNumber();
+ }
+
+ private Value nonConstNumberOperand(If theIf) {
+ return theIf.isZeroTest()
+ ? theIf.lhs()
+ : (theIf.lhs().isConstNumber() ? theIf.rhs() : theIf.lhs());
+ }
+
private List<BasicBlock> computeCandidates(IRCode code) {
List<BasicBlock> candidates = new ArrayList<>();
- for (BasicBlock block : ListUtils.filter(code.blocks, block -> block.entry().isIf())) {
+ for (BasicBlock block : ListUtils.filter(code.blocks, block -> block.exit().isIf())) {
If theIf = block.exit().asIf();
- if (theIf.isZeroTest()
- && theIf.lhs().getType().isInt()
- && theIf.lhs().isPhi()
- && theIf.lhs().hasSingleUniqueUser()
- && !theIf.lhs().hasPhiUsers()) {
+ if (!isNumberAgainstConstNumberIf(theIf)) {
+ continue;
+ }
+ Value nonConstNumberOperand = nonConstNumberOperand(theIf);
+ if (isNumberAgainstConstNumberIf(theIf)
+ && nonConstNumberOperand.isPhi()
+ && nonConstNumberOperand.hasSingleUniqueUser()
+ && !nonConstNumberOperand.hasPhiUsers()) {
candidates.add(block);
}
}
return candidates;
}
+ private BasicBlock targetFromCondition(If theIf, ConstNumber constForPhi) {
+ if (theIf.isZeroTest()) {
+ return theIf.targetFromCondition(constForPhi);
+ }
+ if (theIf.lhs().isConstNumber()) {
+ return theIf.targetFromCondition(
+ theIf.lhs().getConstInstruction().asConstNumber(), constForPhi);
+ }
+ assert theIf.rhs().isConstNumber();
+ return theIf.targetFromCondition(
+ constForPhi, theIf.rhs().getConstInstruction().asConstNumber());
+ }
+
private void recordNewTargetForGoto(
Value value, BasicBlock basicBlock, If theIf, Map<Goto, BasicBlock> newTargets) {
// The GoTo at the end of basicBlock should target the phiBlock, and should target instead
// the correct if destination.
assert basicBlock.exit().isGoto();
assert value.isConstant();
- assert value.getType().isInt();
- assert theIf.isZeroTest();
- BasicBlock newTarget = theIf.targetFromCondition(value.getConstInstruction().asConstNumber());
+ BasicBlock newTarget = targetFromCondition(theIf, value.getConstInstruction().asConstNumber());
Goto aGoto = basicBlock.exit().asGoto();
newTargets.put(aGoto, newTarget);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index c0d3e6b..26da1f1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -2263,7 +2264,7 @@
}
}
- private FilledArrayCandidate computeFilledArrayCandiate(
+ private FilledArrayCandidate computeFilledArrayCandidate(
Instruction instruction, RewriteArrayOptions options) {
NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
if (newArrayEmpty == null) {
@@ -2284,9 +2285,57 @@
if (!encodeAsFilledNewArray && !canUseFilledArrayData(arrayType, size, options)) {
return null;
}
+ // Check that all arguments to the array is the array type or that the array is type Object[].
+ if (!options.canUseSubTypesInFilledNewArray()
+ && arrayType != dexItemFactory.objectArrayType
+ && !arrayType.isPrimitiveArrayType()) {
+ DexType elementType = arrayType.toArrayElementType(dexItemFactory);
+ for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) {
+ if (uniqueUser.isArrayPut()
+ && uniqueUser.asArrayPut().array() == newArrayEmpty.outValue()
+ && !checkTypeOfArrayPut(uniqueUser.asArrayPut(), elementType)) {
+ return null;
+ }
+ }
+ }
return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
}
+ private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) {
+ TypeElement valueType = arrayPut.value().getType();
+ if (!valueType.isPrimitiveType() && elementType == dexItemFactory.objectType) {
+ return true;
+ }
+ if (valueType.isNullType() && !elementType.isPrimitiveType()) {
+ return true;
+ }
+ if (elementType.isArrayType()) {
+ if (valueType.isNullType()) {
+ return true;
+ }
+ ArrayTypeElement arrayTypeElement = valueType.asArrayType();
+ if (arrayTypeElement == null
+ || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) {
+ return false;
+ }
+ valueType = arrayTypeElement.getBaseType();
+ elementType = elementType.toBaseType(dexItemFactory);
+ }
+ assert !valueType.isArrayType();
+ assert !elementType.isArrayType();
+ if (valueType.isPrimitiveType() && !elementType.isPrimitiveType()) {
+ return false;
+ }
+ if (valueType.isPrimitiveType()) {
+ return true;
+ }
+ DexClass clazz = appView.definitionFor(elementType);
+ if (clazz == null) {
+ return false;
+ }
+ return clazz.isInterface() || valueType.isClassType(elementType);
+ }
+
private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
if (size < options.minSizeForFilledNewArray) {
return false;
@@ -2393,7 +2442,7 @@
RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
InstructionListIterator it = block.listIterator(code);
while (it.hasNext()) {
- FilledArrayCandidate candidate = computeFilledArrayCandiate(it.next(), rewriteOptions);
+ FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions);
if (candidate == null) {
continue;
}
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 2f66e6f..b2ce050 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1519,6 +1519,13 @@
return hasFeaturePresentFrom(AndroidApiLevel.N);
}
+ // When adding support for emitting filled-new-array for sub-types, ART 13 (Api-level 33) had
+ // issues. See b/283715197.
+ public boolean canUseSubTypesInFilledNewArray() {
+ assert isGeneratingDex();
+ return !canHaveBugPresentUntil(AndroidApiLevel.U);
+ }
+
// Dalvik doesn't handle new-filled-array with arrays as values. It fails with:
// W(629880) VFY: [Ljava/lang/Integer; is not instance of Ljava/lang/Integer; (dalvikvm)
public boolean canUseFilledNewArrayOfArrays() {
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 7e64f85..c792710 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -112,6 +112,14 @@
return false;
}
+ public boolean canUseFilledNewArrayOnNonStringObjects() {
+ return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ public boolean canUseSubTypesInFilledNewArray() {
+ return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
+ }
+
public boolean runtimeWithClassValue() {
assert isCfRuntime() || isDexRuntime();
return isCfRuntime() || getDexRuntimeVersion().isNewerThanOrEqual(DexVm.Version.V14_0_0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondCstTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondCstTest.java
new file mode 100644
index 0000000..7c5ed88
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondCstTest.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2023, 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.optimize.ifs;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.AlwaysInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DoubleDiamondCstTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DoubleDiamondCstTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableAlwaysInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "1", "5", "5", "1", "1", "5", "5", "1", "5", "5", "1", "1", "1", "5");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ for (FoundMethodSubject method : inspector.clazz(Main.class).allMethods()) {
+ if (!method.getOriginalName().equals("main")) {
+ long count = method.streamInstructions().filter(InstructionSubject::isIf).count();
+ assertEquals(method.getOriginalName().contains("Double") ? 2 : 1, count);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(indirectTest(2, 6));
+ System.out.println(indirectTest(3, 3));
+
+ System.out.println(indirectTestNegated(2, 6));
+ System.out.println(indirectTestNegated(3, 3));
+
+ System.out.println(indirectCmp(2, 6));
+ System.out.println(indirectCmp(7, 3));
+
+ System.out.println(indirectCmpNegated(2, 6));
+ System.out.println(indirectCmpNegated(7, 3));
+
+ System.out.println(indirectDoubleTest(2, 6, 6));
+ System.out.println(indirectDoubleTest(7, 7, 3));
+ System.out.println(indirectDoubleTest(1, 1, 1));
+
+ System.out.println(indirectDoubleTestNegated(2, 6, 6));
+ System.out.println(indirectDoubleTestNegated(2, 2, 6));
+ System.out.println(indirectDoubleTestNegated(7, 7, 7));
+ }
+
+ @AlwaysInline
+ public static int doubleTest(int i, int j, int k) {
+ if (i != j) {
+ return 1;
+ }
+ if (j == k) {
+ return 2;
+ }
+ return 3;
+ }
+
+ @NeverInline
+ public static int indirectDoubleTest(int i, int j, int k) {
+ if (doubleTest(i, j, k) == 2) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectDoubleTestNegated(int i, int j, int k) {
+ if (doubleTest(i, j, k) != 2) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @AlwaysInline
+ public static int test(int i, int j) {
+ return i == j ? 1 : 2;
+ }
+
+ @NeverInline
+ public static int indirectTest(int i, int j) {
+ if (test(i, j) == 2) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectTestNegated(int i, int j) {
+ if (test(i, j) != 2) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @AlwaysInline
+ public static int cmp(int i, int j) {
+ return i <= j ? 1 : 2;
+ }
+
+ @NeverInline
+ public static int indirectCmp(int i, int j) {
+ if (cmp(i, j) < 2) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectCmpNegated(int i, int j) {
+ if (cmp(i, j) > 1) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondFloatTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondFloatTest.java
new file mode 100644
index 0000000..b433e5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/DoubleDiamondFloatTest.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2023, 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.optimize.ifs;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.AlwaysInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DoubleDiamondFloatTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DoubleDiamondFloatTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableAlwaysInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "5", "1", "1", "5", "5", "5", "1", "1", "1", "5", "5", "5", "1", "1", "1", "5");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ for (FoundMethodSubject method : inspector.clazz(Main.class).allMethods()) {
+ if (!method.getOriginalName().equals("main")) {
+ long count = method.streamInstructions().filter(InstructionSubject::isIf).count();
+ assertEquals(method.getOriginalName().contains("Double") ? 2 : 1, count);
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(indirectEquals(2.0f, 6.0f));
+ System.out.println(indirectEquals(3.0f, 3.0f));
+
+ System.out.println(indirectEqualsNegated(2.0f, 6.0f));
+ System.out.println(indirectEqualsNegated(3.0f, 3.0f));
+
+ System.out.println(indirectDoubleEquals(2.0f, 6.0f, 6.0f));
+ System.out.println(indirectDoubleEquals(7.0f, 7.0f, 3.0f));
+ System.out.println(indirectDoubleEquals(1.0f, 1.0f, 1.0f));
+
+ System.out.println(indirectDoubleEqualsNegated(2.0f, 6.0f, 6.0f));
+ System.out.println(indirectDoubleEqualsNegated(2.0f, 2.0f, 6.0f));
+ System.out.println(indirectDoubleEqualsNegated(7.0f, 7.0f, 7.0f));
+
+ System.out.println(indirectDoubleEqualsSplit(2.0f, 6.0f, 6.0f));
+ System.out.println(indirectDoubleEqualsSplit(7.0f, 7.0f, 3.0f));
+ System.out.println(indirectDoubleEqualsSplit(1.0f, 1.0f, 1.0f));
+
+ System.out.println(indirectDoubleEqualsSplitNegated(2.0f, 6.0f, 6.0f));
+ System.out.println(indirectDoubleEqualsSplitNegated(2.0f, 2.0f, 6.0f));
+ System.out.println(indirectDoubleEqualsSplitNegated(7.0f, 7.0f, 7.0f));
+ }
+
+ @AlwaysInline
+ public static boolean doubleEqualsSplit(float i, float j, float k) {
+ if (i != j) {
+ return false;
+ }
+ return j == k;
+ }
+
+ @NeverInline
+ public static int indirectDoubleEqualsSplit(float i, float j, float k) {
+ if (doubleEqualsSplit(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectDoubleEqualsSplitNegated(float i, float j, float k) {
+ if (!doubleEqualsSplit(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @AlwaysInline
+ public static boolean doubleEquals(float i, float j, float k) {
+ return i == j && j == k;
+ }
+
+ @NeverInline
+ public static int indirectDoubleEquals(float i, float j, float k) {
+ if (doubleEquals(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectDoubleEqualsNegated(float i, float j, float k) {
+ if (!doubleEquals(i, j, k)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @AlwaysInline
+ public static boolean equals(float i, float j) {
+ return i == j;
+ }
+
+ @NeverInline
+ public static int indirectEquals(float i, float j) {
+ if (equals(i, j)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+
+ @NeverInline
+ public static int indirectEqualsNegated(float i, float j) {
+ if (!equals(i, j)) {
+ return 1;
+ } else {
+ return 5;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/proguard/RemovedAndroidApiTest.java b/src/test/java/com/android/tools/r8/proguard/RemovedAndroidApiTest.java
new file mode 100644
index 0000000..7e4a3b2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/RemovedAndroidApiTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2023, 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.proguard;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// See b/37324358 for reference.
+@RunWith(Parameterized.class)
+public class RemovedAndroidApiTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
+ .addProgramClassFileData(getClassUsingRemovedApi())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatMatches(
+ CoreMatchers.anyOf(
+ containsString("java.lang.ClassNotFoundException: android.util.FloatMath"),
+ containsString(
+ "java.lang.ClassNotFoundException: Didn't find class"
+ + " \"android.util.FloatMath\"")));
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ parameters.assumeR8TestParameters();
+ try {
+ testForProguard(ProguardVersion.V7_0_0)
+ .addProgramClassFileData(getClassUsingRemovedApi())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
+ .addKeepMainRule(TestClass.class)
+ .compile();
+ } catch (CompilationFailedException e) {
+ assertThat(
+ e.getMessage(),
+ containsString(
+ "can't find referenced method 'float floor(float)'"
+ + " in library class android.util.FloatMath"));
+ }
+ }
+
+ private byte[] getClassUsingRemovedApi() throws IOException {
+ return transformer(TestClass.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("floor")) {
+ // The class android.util.FloatMath is still in android.jar, but without any
+ // methods.
+ // Methods where removed from API level 23.
+ visitor.visitMethodInsn(
+ opcode, "android/util/FloatMath", "floor", descriptor, isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ static class TestClass {
+ public static float floor(float f) {
+ throw new RuntimeException("Stub");
+ }
+
+ public static void main(String[] args) {
+ System.out.println(floor(1.234f));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataRemoveCheckCastTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataRemoveCheckCastTest.java
new file mode 100644
index 0000000..71639f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataRemoveCheckCastTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2023, 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.rewrite.arrays;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a reproduction of b/283715197. It actually does not reproduce on our headless VMs, so we
+ * cannot assert an error - only the existences of the instruction that causes verification error.
+ */
+@RunWith(Parameterized.class)
+public class FilledArrayDataRemoveCheckCastTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .addKeepClassAndMembersRules(Main.class)
+ .addKeepClassAndMembersRules(Base.class)
+ .addKeepClassRulesWithAllowObfuscation(Sub1.class, Sub2.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello", "World", "Hello", "World")
+ .inspect(
+ inspector -> {
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertThat(mainClass, isPresent());
+ MethodSubject iterateSubClasses =
+ mainClass.uniqueMethodWithOriginalName("iterateSubClasses");
+ assertThat(iterateSubClasses, isPresent());
+ Optional<InstructionSubject> filledNewArrayInIterateSubClasses =
+ iterateSubClasses
+ .streamInstructions()
+ .filter(InstructionSubject::isFilledNewArray)
+ .findFirst();
+ MethodSubject iterateBaseClasses =
+ mainClass.uniqueMethodWithOriginalName("iterateBaseClasses");
+ assertThat(iterateBaseClasses, isPresent());
+ Optional<InstructionSubject> filledNewArrayInIterateBaseClasses =
+ iterateBaseClasses
+ .streamInstructions()
+ .filter(InstructionSubject::isFilledNewArray)
+ .findFirst();
+ assertEquals(
+ parameters.canUseFilledNewArrayOnNonStringObjects(),
+ filledNewArrayInIterateBaseClasses.isPresent());
+ assertEquals(
+ parameters.canUseFilledNewArrayOnNonStringObjects()
+ && parameters.canUseSubTypesInFilledNewArray(),
+ filledNewArrayInIterateSubClasses.isPresent());
+ });
+ }
+
+ public abstract static class Base {
+
+ public abstract String toString();
+ }
+
+ public static class Sub1 extends Base {
+
+ @Override
+ public String toString() {
+ return "Hello";
+ }
+ }
+
+ public static class Sub2 extends Base {
+
+ @Override
+ public String toString() {
+ return "World";
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ iterateBaseClasses(new Sub1(), new Sub2());
+ iterateSubClasses();
+ }
+
+ public static void iterateBaseClasses(Base b1, Base b2) {
+ Base[] arr = new Base[] {b1, b2};
+ iterate(arr);
+ }
+
+ public static void iterateSubClasses() {
+ Base[] arr = new Base[] {new Sub1(), new Sub2()};
+ iterate(arr);
+ }
+
+ public static void iterate(Object[] arr) {
+ for (Object b : arr) {
+ System.out.println(b.toString());
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
index 38870bd..76fcda9 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -170,6 +170,8 @@
mainClass.uniqueMethodWithOriginalName("referenceArraysNoCasts");
MethodSubject referenceArraysWithSubclasses =
mainClass.uniqueMethodWithOriginalName("referenceArraysWithSubclasses");
+ MethodSubject referenceArraysWithInterfaceImplementations =
+ mainClass.uniqueMethodWithOriginalName("referenceArraysWithInterfaceImplementations");
MethodSubject interfaceArrayWithRawObject =
mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
@@ -220,18 +222,23 @@
if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
assertArrayTypes(referenceArraysNoCasts, DexNewArray.class);
assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+ assertArrayTypes(referenceArraysWithInterfaceImplementations, DexNewArray.class);
assertArrayTypes(phiFilledNewArray, DexNewArray.class);
assertArrayTypes(objectArraysFilledNewArrayRange, DexNewArray.class);
assertArrayTypes(twoDimensionalArrays, DexNewArray.class);
assertArrayTypes(assumedValues, DexNewArray.class);
} else {
assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class);
- // TODO(b/246971330): Add support for arrays with subtypes.
- if (isR8) {
+ if (isR8 && parameters.canUseSubTypesInFilledNewArray()) {
assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class);
} else {
assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
}
+ if (isR8) {
+ assertArrayTypes(referenceArraysWithInterfaceImplementations, DexFilledNewArray.class);
+ } else {
+ assertArrayTypes(referenceArraysWithInterfaceImplementations, DexNewArray.class);
+ }
// TODO(b/246971330): Add support for arrays whose values have conditionals.
// assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
@@ -298,6 +305,7 @@
stringArrays();
referenceArraysNoCasts();
referenceArraysWithSubclasses();
+ referenceArraysWithInterfaceImplementations();
interfaceArrayWithRawObject();
phiFilledNewArray();
intsThatUseFilledNewArray();
@@ -338,9 +346,13 @@
}
@NeverInline
- private static void referenceArraysWithSubclasses() {
+ private static void referenceArraysWithInterfaceImplementations() {
Serializable[] interfaceArr = {1, null, 2};
System.out.println(Arrays.toString(interfaceArr));
+ }
+
+ @NeverInline
+ private static void referenceArraysWithSubclasses() {
Number[] objArray = {1, null, 2};
System.out.println(Arrays.toString(objArray));
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 23684e1..45d6202 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -380,6 +380,11 @@
}
@Override
+ public boolean isFilledNewArray() {
+ return false;
+ }
+
+ @Override
public boolean isNewArray() {
return instruction instanceof CfNewArray;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 16a41b1..38421a2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -49,6 +49,7 @@
import com.android.tools.r8.dex.code.DexDivIntLit8;
import com.android.tools.r8.dex.code.DexDivLong;
import com.android.tools.r8.dex.code.DexDivLong2Addr;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
import com.android.tools.r8.dex.code.DexGoto;
import com.android.tools.r8.dex.code.DexIfEq;
import com.android.tools.r8.dex.code.DexIfEqz;
@@ -624,6 +625,11 @@
}
@Override
+ public boolean isFilledNewArray() {
+ return instruction instanceof DexFilledNewArray;
+ }
+
+ @Override
public int size() {
return instruction.getSize();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 7fd166e..6d1bd94 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -158,6 +158,8 @@
boolean isMonitorExit();
+ boolean isFilledNewArray();
+
int size();
InstructionOffsetSubject getOffset(MethodSubject methodSubject);
diff --git a/third_party/dependencies.tar.gz.sha1 b/third_party/dependencies.tar.gz.sha1
new file mode 100644
index 0000000..cf12fc2
--- /dev/null
+++ b/third_party/dependencies.tar.gz.sha1
@@ -0,0 +1 @@
+76c51489d87c284cea0e73646c5cc45a9ffc3665
\ No newline at end of file
diff --git a/third_party/dependencies_new.tar.gz.sha1 b/third_party/dependencies_new.tar.gz.sha1
new file mode 100644
index 0000000..54a7c72
--- /dev/null
+++ b/third_party/dependencies_new.tar.gz.sha1
@@ -0,0 +1 @@
+08dbd497e182be658628252f0bb890894cc88dcc
\ No newline at end of file
diff --git a/tools/create_local_maven_with_dependencies.py b/tools/create_local_maven_with_dependencies.py
new file mode 100755
index 0000000..296cdf0
--- /dev/null
+++ b/tools/create_local_maven_with_dependencies.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import argparse
+import os.path
+import subprocess
+import shutil
+import sys
+
+import utils
+
+REPOSITORIES = [
+ 'Maven Central=https://repo1.maven.org/maven2/',
+ 'Google=https://maven.google.com/',
+ "Gradle Plugins=https://plugins.gradle.org/m2/",
+]
+
+ANDRDID_SUPPORT_VERSION = '25.4.0'
+ASM_VERSION = '9.5'
+ESPRESSO_VERSION = '3.0.0'
+FASTUTIL_VERSION = '7.2.1'
+KOTLIN_METADATA_VERSION = '0.6.0'
+KOTLIN_VERSION = '1.8.0'
+GUAVA_VERSION = '31.1-jre'
+GSON_VERSION = '2.7'
+JAVASSIST_VERSION = '3.29.2-GA'
+JUNIT_VERSION = '4.13-beta-2'
+MOCKITO_VERSION = '2.10.0'
+SMALI_VERSION = '3.0.3'
+ERROR_PRONE_VERSION = '2.18.0'
+TESTNG_VERSION = '6.10'
+
+
+
+BUILD_DEPENDENCIES = [
+ 'com.google.code.gson:gson:{version}'.format(version = GSON_VERSION),
+ 'com.google.guava:guava:{version}'.format(version = GUAVA_VERSION),
+ 'it.unimi.dsi:fastutil:{version}'.format(version = FASTUTIL_VERSION),
+ 'org.jetbrains.kotlinx:kotlinx-metadata-jvm:{version}'.format(version = KOTLIN_METADATA_VERSION),
+ 'org.ow2.asm:asm:{version}'.format(version = ASM_VERSION),
+ 'org.ow2.asm:asm-util:{version}'.format(version = ASM_VERSION),
+ 'org.ow2.asm:asm-commons:{version}'.format(version = ASM_VERSION),
+]
+
+TEST_DEPENDENCIES = [
+ 'junit:junit:{version}'.format(version = JUNIT_VERSION),
+ 'com.android.support:support-v4:{version}'.format(version = ANDRDID_SUPPORT_VERSION),
+ 'com.android.support.test.espresso:espresso-core:{version}'.format(version = ESPRESSO_VERSION),
+ 'com.android.tools.smali:smali:{version}'.format(version = SMALI_VERSION),
+ 'com.google.errorprone:error_prone_core:{version}'.format(version = ERROR_PRONE_VERSION),
+ 'org.javassist:javassist:{version}'.format(version = JAVASSIST_VERSION),
+ 'org.jetbrains.kotlin:kotlin-stdlib:{version}'.format(version = KOTLIN_VERSION),
+ 'org.jetbrains.kotlin:kotlin-reflect:{version}'.format(version = KOTLIN_VERSION),
+ 'org.mockito:mockito-core:{version}'.format(version = MOCKITO_VERSION),
+ 'org.testng:testng:{version}'.format(version = TESTNG_VERSION),
+]
+
+NEW_DEPENDENCIES = [
+ 'org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:4.0.6',
+ 'org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.8.10',
+ 'org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.8.10',
+ 'org.jetbrains.kotlin:kotlin-reflect:1.6.10',
+ 'org.jetbrains.kotlin:kotlin-reflect:1.8.10',
+ 'org.jetbrains.kotlin:kotlin-script-runtime:1.8.10',
+ 'org.jetbrains.kotlin:kotlin-tooling-core:1.8.10',
+ 'net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:3.0.1'
+]
+
+def dependencies_tar(dependencies_path):
+ return os.path.join(
+ os.path.dirname(dependencies_path),
+ os.path.basename(dependencies_path) + '.tar.gz')
+
+def dependencies_tar_sha1(dependencies_path):
+ return os.path.join(
+ os.path.dirname(dependencies_path),
+ os.path.basename(dependencies_path) + '.tar.gz.sha1')
+
+def remove_local_maven_repository(dependencies_path):
+ if os.path.exists(dependencies_path):
+ shutil.rmtree(dependencies_path)
+ tar = dependencies_tar(dependencies_path)
+ if os.path.exists(tar):
+ os.remove(tar)
+ sha1 = dependencies_tar_sha1(dependencies_path)
+ if os.path.exists(sha1):
+ os.remove(sha1)
+
+def create_local_maven_repository(args, dependencies_path, repositories, dependencies):
+ with utils.ChangedWorkingDirectory(args.studio):
+ cmd = [
+ os.path.join('tools', 'base', 'bazel', 'bazel'),
+ 'run',
+ '//tools/base/bazel:local_maven_repository_generator_cli',
+ '--',
+ '--repo-path',
+ dependencies_path,
+ '--fetch']
+ for repository in repositories:
+ cmd.extend(['--remote-repo', repository])
+ for dependency in dependencies:
+ cmd.append(dependency)
+ subprocess.check_call(cmd)
+
+def parse_options():
+ result = argparse.ArgumentParser(
+ description='Create local Maven repository woth dependencies')
+ result.add_argument('--studio',
+ metavar=('<path>'),
+ required=True,
+ help='Path to a studio-main checkout (to get the tool '
+ '//tools/base/bazel:local_maven_repository_generator_cli)')
+ return result.parse_args()
+
+
+def main():
+ args = parse_options()
+
+ dependencies_path = os.path.join(utils.THIRD_PARTY, 'dependencies')
+ print("Downloading to " + dependencies_path)
+ remove_local_maven_repository(dependencies_path)
+ create_local_maven_repository(
+ args, dependencies_path, REPOSITORIES, BUILD_DEPENDENCIES + TEST_DEPENDENCIES)
+
+ dependencies_new_path = os.path.join(utils.THIRD_PARTY, 'dependencies_new')
+ print("Downloading to " + dependencies_new_path)
+ remove_local_maven_repository(dependencies_new_path)
+ create_local_maven_repository(
+ args, dependencies_new_path, REPOSITORIES, NEW_DEPENDENCIES)
+
+ print('Now run')
+ print(' (cd {third_party};'
+ ' upload_to_google_storage.py -a --bucket r8-deps {dependencies};'
+ ' upload_to_google_storage.py -a --bucket r8-deps {dependencies_new})'
+ .format(
+ third_party = utils.THIRD_PARTY,
+ dependencies = 'dependencies',
+ dependencies_new = 'dependencies_new'))
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 0e32525..dc47fa2 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -161,13 +161,16 @@
return subprocess.check_call(cmd)
-def update_prebuilds(r8_checkout, version, checkout):
+def update_prebuilds(r8_checkout, version, checkout, keepanno=False):
path = os.path.join(r8_checkout, 'tools', 'update_prebuilds_in_android.py')
commit_arg = '--commit_hash=' if len(version) == 40 else '--version='
- subprocess.check_call([path, '--targets=lib', '--maps', commit_arg + version, checkout])
+ cmd = [path, '--targets=lib', '--maps', commit_arg + version, checkout]
+ if keepanno:
+ cmd.append("--keepanno")
+ subprocess.check_call(cmd)
-def release_studio_or_aosp(r8_checkout, path, options, git_message):
+def release_studio_or_aosp(r8_checkout, path, options, git_message, keepanno=False):
with utils.ChangedWorkingDirectory(path):
if not options.use_existing_work_branch:
subprocess.call(['repo', 'abandon', 'update-r8'])
@@ -180,7 +183,7 @@
with utils.ChangedWorkingDirectory(prebuilts_r8):
subprocess.check_call(['repo', 'start', 'update-r8'])
- update_prebuilds(r8_checkout, options.version, path)
+ update_prebuilds(r8_checkout, options.version, path, keepanno)
with utils.ChangedWorkingDirectory(prebuilts_r8):
if not options.use_existing_work_branch:
@@ -193,7 +196,8 @@
# Don't upload if requested not to, or if changes are not committed due
# to --use-existing-work-branch
if not options.no_upload and not options.use_existing_work_branch:
- process = subprocess.Popen(['repo', 'upload', '.', '--verify'],
+ process = subprocess.Popen(['repo', 'upload', '.', '--verify',
+ '--current-branch'],
stdin=subprocess.PIPE)
return process.communicate(input=b'y\n')[0]
@@ -217,7 +221,7 @@
Test: TARGET_PRODUCT=aosp_arm64 m -j core-oj"""
% (args.version, args.version, args.version))
return release_studio_or_aosp(
- utils.REPO_ROOT, args.aosp, options, git_message)
+ utils.REPO_ROOT, args.aosp, options, git_message, keepanno=True)
return release_aosp
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index 6f8da19..4dfbb7d 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -21,6 +21,7 @@
}
OTHER_TARGETS = ["LICENSE"]
+KEEPANNO_JAR = 'keepanno-annotations.jar'
def parse_arguments():
parser = argparse.ArgumentParser(
@@ -42,6 +43,11 @@
help="Download proguard maps for jars, use only with '--target lib'.",
)
parser.add_argument(
+ '--keepanno',
+ action='store_true',
+ help="Download keepanno-annotations library.",
+ )
+ parser.add_argument(
'--java-max-memory-size',
'--java_max_memory_size',
help='Use a custom max memory size for the gradle java instance, eg, 4g')
@@ -86,7 +92,7 @@
print('Downloading: ' + url + ' -> ' + download_path)
utils.download_file_from_cloud_storage(url, download_path, quiet=quiet)
-def main_download(hash, maps, targets, target_root, version):
+def main_download(hash, maps, targets, target_root, version, keepanno=False):
jar_targets = JAR_TARGETS_MAP[targets]
final_targets = list(map((lambda t: t[0] + '.jar'), jar_targets)) + OTHER_TARGETS
with utils.TempDir() as root:
@@ -95,13 +101,19 @@
download_hash(root, hash, target)
if maps and target not in OTHER_TARGETS:
download_hash(root, hash, target + '.map')
+ if keepanno:
+ download_hash(root, hash, KEEPANNO_JAR)
else:
assert version
download_version(root, version, target)
if maps and target not in OTHER_TARGETS:
download_version(root, version, target + '.map')
+ if keepanno:
+ download_version(root, version, KEEPANNO_JAR)
copy_jar_targets(root, target_root, jar_targets, maps)
copy_other_targets(root, target_root)
+ if keepanno:
+ copy_targets(root, target_root, [KEEPANNO_JAR], [KEEPANNO_JAR])
def main_build(maps, max_memory_size, targets, target_root):
jar_targets = JAR_TARGETS_MAP[targets]
@@ -120,7 +132,8 @@
main_build(args.maps, args.java_max_memory_size, args.targets, target_root)
else:
assert args.commit_hash == None or args.version == None
- main_download(args.commit_hash, args.maps, args.targets, target_root, args.version)
+ main_download(
+ args.commit_hash, args.maps, args.targets, target_root, args.version, args.keepanno)
if __name__ == '__main__':
sys.exit(main(parse_arguments()))