Merge "Use a pool for DexDebugEvents."
diff --git a/build.gradle b/build.gradle
index 4caf5fa..695e898 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,12 @@
}
output.resourcesDir = 'build/classes/debugTestResources'
}
+ debugTestResourcesJava8 {
+ java {
+ srcDirs = ['src/test/debugTestResourcesJava8']
+ }
+ output.resourcesDir = 'build/classes/debugTestResourcesJava8'
+ }
examples {
java {
srcDirs = ['src/test/examples']
@@ -251,6 +257,20 @@
}
}
+task D8Logger(type: Jar) {
+ from sourceSets.main.output
+ baseName 'd8logger'
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.D8Logger'
+ }
+ // In order to build without dependencies, pass the exclude_deps property using:
+ // gradle -Pexclude_deps D8Logger
+ if (!project.hasProperty('exclude_deps')) {
+ // Also include dependencies
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ }
+}
+
task disasm(type: Jar) {
from sourceSets.main.output
baseName 'disasm'
@@ -295,13 +315,12 @@
task createArtTests(type: Exec) {
def outputDir = "build/generated/test/java/com/android/tools/r8/art"
- def createArtTestsScript = "scripts/create-art-tests.sh"
+ def createArtTestsScript = "tools/create_art_tests.py"
inputs.file "tests/art.tar.gz"
inputs.file createArtTestsScript
outputs.dir outputDir
dependsOn downloadDeps
- executable "bash"
- args "${projectDir}/${createArtTestsScript}"
+ commandLine "python", createArtTestsScript
workingDir = projectDir
}
@@ -352,7 +371,6 @@
}
task buildDebugTestResourcesJars {
- dependsOn downloadDeps
def resourcesDir = file("src/test/debugTestResources")
def hostJar = "debug_test_resources.jar"
task "compile_debugTestResources"(type: JavaCompile) {
@@ -369,7 +387,25 @@
from "build/test/debugTestResources/classes"
include "**/*.class"
}
+ def java8ResourcesDir = file("src/test/debugTestResourcesJava8")
+ def java8HostJar = "debug_test_resources_java8.jar"
+ task "compile_debugTestResourcesJava8"(type: JavaCompile) {
+ source = fileTree(dir: java8ResourcesDir, include: '**/*.java')
+ destinationDir = file("build/test/debugTestResourcesJava8/classes")
+ classpath = sourceSets.main.compileClasspath
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ options.compilerArgs += ["-g", "-Xlint:-options"]
+ }
+ task "jar_debugTestResourcesJava8"(type: Jar, dependsOn: "compile_debugTestResourcesJava8") {
+ archiveName = java8HostJar
+ destinationDir = file("build/test/")
+ from "build/test/debugTestResourcesJava8/classes"
+ include "**/*.class"
+ }
+ dependsOn downloadDeps
dependsOn jar_debugTestResources
+ dependsOn jar_debugTestResourcesJava8
}
task buildExampleJars {
diff --git a/scripts/aosp_helper.sh b/scripts/aosp_helper.sh
new file mode 100755
index 0000000..d375953
--- /dev/null
+++ b/scripts/aosp_helper.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# 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.
+
+# Helper script for tools/test_android_cts.py
+
+readonly AOSP_PRESET="$1"
+shift
+readonly TASK="$1"
+shift
+
+. build/envsetup.sh
+lunch "$AOSP_PRESET"
+
+if [[ "$TASK" == "make" ]]; then
+ make "$@"
+elif [[ "$TASK" == "emulator" ]]; then
+ emulator "$@"
+elif [[ "$TASK" == "run-cts" ]]; then
+ adb wait-for-device
+ adb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82'
+
+ echo "exit" | \
+ ANDROID_BUILD_TOP= \
+ "$@"
+else
+ echo "Invalid task: '$TASK'" >&2
+ exit 1
+fi
diff --git a/scripts/create-art-tests.sh b/scripts/create-art-tests.sh
deleted file mode 100755
index dc6d6d2..0000000
--- a/scripts/create-art-tests.sh
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/bin/bash
-#
-# Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-function generate_test() {
- local name=$1
- local testClassName=$2
- local testGeneratingToolchain=$3
- # The bash uppercase substitution ^^ is not supported on the bash version on Mac OS.
- local testGeneratingToolchainEnum=$(echo $testGeneratingToolchain | tr /a-z/ /A-Z/)
- local fileName=$4
- local compilerUnderTest=$5
- local compilerUnderTestEnum=$(echo ${compilerUnderTest} | tr /a-z/ /A-Z/)
-
- cat <<EOF > $fileName
-// Copyright (c) 2016, 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.art.${testGeneratingToolchain}.${compilerUnderTest};
-
-import static com.android.tools.r8.R8RunArtTestsTest.DexTool.${testGeneratingToolchainEnum};
-
-import com.android.tools.r8.R8RunArtTestsTest;
-import org.junit.Test;
-
-/**
- * Auto-generated test for the art ${name} test using the ${testGeneratingToolchain} toolchain.
- *
- * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN scripts/create-art-tests.sh INSTEAD!
- */
-public class ${testClassName} extends R8RunArtTestsTest {
-
- public ${testClassName}() {
- super("${name}", ${testGeneratingToolchainEnum});
- }
-
- @Test
- public void run${testClassName}() throws Throwable {
- // For testing with other Art VMs than the default pass the VM version as a argument to
- // runArtTest, e.g. runArtTest(ToolHelper.ART_4_4_4).
- runArtTest(CompilerUnderTest.${compilerUnderTestEnum});
- }
-}
-EOF
-}
-
-TESTDIR="tests/art"
-TOOLCHAINS=("dx" "jack" "none")
-DESTINATIONDIR="build/generated/test/java/com/android/tools/r8/art"
-
-if [ ! -e $TESTDIR ]; then
- echo "Missing art tests in $TESTDIR."
- exit
-fi
-
-for TOOLCHAIN in ${TOOLCHAINS[@]}; do
- for d in $DESTINATIONDIR/$TOOLCHAIN/r8 $DESTINATIONDIR/$TOOLCHAIN/d8; do
- rm -rf $d
- mkdir -p $d
- done
- # class files are found in the dx directory.
- if [ "$TOOLCHAIN" == "none" ]; then
- SOURCEDIR=${TESTDIR}/dx
- else
- SOURCEDIR=${TESTDIR}/${TOOLCHAIN}
- fi
- for TEST in ${SOURCEDIR}/*; do
- TESTNAME=$(basename $TEST)
- TESTCLASSNAME="Art${TESTNAME//-/_}Test"
- generate_test $TESTNAME $TESTCLASSNAME ${TOOLCHAIN} $DESTINATIONDIR/$TOOLCHAIN/r8/$TESTCLASSNAME.java r8
- generate_test $TESTNAME $TESTCLASSNAME ${TOOLCHAIN} $DESTINATIONDIR/$TOOLCHAIN/d8/$TESTCLASSNAME.java d8
- done
-done
diff --git a/scripts/create_d8_replay.sh b/scripts/create_d8_replay.sh
new file mode 100755
index 0000000..0b94705
--- /dev/null
+++ b/scripts/create_d8_replay.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+#
+# 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.
+#
+
+# Take a file where each line is a tab-separated list of arguments for D8
+# and create a self-contained directory with all the input files and a script
+# which replays the same D8 invocations as the original list.
+#
+# Usage:
+#
+# create_d8_replay <d8-args-script> <output-dir>
+#
+# The <d8-args-script> is a text file where each line contains tab-separated
+# arguments for a D8 call.
+# The script 'scripts/test_android_cts.sh' can log D8 invocations during an AOSP
+# build to such a file.
+set -e
+
+# This function will be called with the out_dir, input_counter and
+# arguments for the original D8 invocation. It copies the inputs
+# of the D8 call into the output directory and appends the replay
+# script with a new line which invokes D8 on the local inputs.
+function process_line {
+ local out_dir="$1"
+ shift
+ local input_counter=$1
+ shift
+
+ args=()
+ inputs=()
+ while (( "$#" )); do
+ if [[ "$1" =~ ^--output=(.*) ]]; then
+ :
+ elif [[ "$1" =~ ^(--.*) ]]; then
+ args+=("$1")
+ else
+ # copy $1 to local dir with unique_name
+ if [[ -f "$1" ]]; then
+ :
+ elif [[ -d "$1" ]]; then
+ echo "Adding directories ('$1') to the replay script is not implemented."
+ else
+ echo "The input to D8 does not exist: '$1'."
+ fi
+
+ input_file="in/${input_counter}_$(basename $1)"
+ cp "$1" "$out_dir/$input_file"
+ inputs+=("\$SCRIPT_DIR/$input_file")
+ fi
+ shift
+ done
+ echo "mkdir -p \"\$SCRIPT_DIR/out/$input_counter\"" \
+ >> "$out_dir/replay.sh"
+ echo "\$D8_COMMAND ${args[@]} \"--output=\$SCRIPT_DIR/out/$input_counter\" \"${inputs[@]}\"" \
+ >> "$out_dir/replay.sh"
+}
+
+
+### MAIN ###
+
+if (( "$#" != 2 )); then
+ echo "Usage: $0 <d8-args-script> <output-dir>" >&2
+ echo "See docs in source for details." >&2
+ exit 1
+fi
+
+input_script="$1"
+out_dir="$2"
+
+if [[ -d "$out_dir" ]]; then
+ rmdir "$out_dir" # make sure to write only to empty out dir
+fi
+mkdir -p "$out_dir/in"
+
+# write first lines of the replay script
+cat > "$out_dir/replay.sh" << EOF
+#!/bin/bash
+set -e
+readonly SCRIPT_DIR=\$(cd "\$(dirname \${BASH_SOURCE[0]})"; pwd)
+if [[ -z "\$D8_COMMAND" ]]; then
+ echo "Set D8_COMMAND to, e.g. 'java -jar d8|compatdx.jar'" >&2
+ exit 1
+fi
+rm -rf out
+EOF
+
+chmod +x "$out_dir/replay.sh"
+
+
+# process input file
+
+input_counter=1
+while IFS=$'\t' read -r -a args; do
+ process_line "$out_dir" $input_counter "${args[@]}"
+ input_counter=$((input_counter+1))
+done <"$input_script"
+
diff --git a/scripts/d8_for_aosp.sh b/scripts/d8_for_aosp.sh
deleted file mode 100755
index 422aa85..0000000
--- a/scripts/d8_for_aosp.sh
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/bin/bash
-#
-# 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.
-#
-# Replacement for 'dx' script invoked by the AOSP makefiles.
-# This script invokes 'd8' instead while providing the same API.
-#
-# Based on this file:
-#
-# repo: https://android.googlesource.com/platform/prebuilts/sdk
-# file: tools/dx
-# SHA1: 6f6b5641b531f18c8e8d314b4b0560370ffbf1ab
-#
-# or this (identical)
-#
-# repo: https://android.googlesource.com/platform/dalvik
-# file: dx/etc/dx
-# SHA1: 9fa280c2171b3a016d63e80cda7be031519b7ef7
-
-readonly ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
-
-defaultMx="-Xmx1024M"
-
-# The following will extract any initial parameters of the form
-# "-J<stuff>" from the command line and pass them to the Java
-# invocation (instead of to dx). This makes it possible for you to add
-# a command-line parameter such as "-JXmx256M" in your scripts, for
-# example. "java" (with no args) and "java -X" give a summary of
-# available options.
-
-javaOpts=""
-
-while expr "x$1" : 'x-J' >/dev/null; do
- opt=`expr "x$1" : 'x-J\(.*\)'`
- javaOpts="${javaOpts} -${opt}"
- if expr "x${opt}" : "xXmx[0-9]" >/dev/null; then
- defaultMx="no"
- fi
- shift
-done
-
-if [ "${defaultMx}" != "no" ]; then
- javaOpts="${javaOpts} ${defaultMx}"
-fi
-
-jarpath="$ROOT/build/libs/d8.jar"
-
-exec java $javaOpts -jar "$jarpath" --dex "$@"
-
diff --git a/scripts/test_android_cts.sh b/scripts/test_android_cts.sh
deleted file mode 100755
index 49175ec..0000000
--- a/scripts/test_android_cts.sh
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/bin/bash
-#
-# 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.
-#
-# Clone and build AOSP, using D8 instead of JACK or DX,
-# then run the Android-CTS on the emulator and compare results
-# to a baseline.
-#
-# This script uses the repo manifest file 'third_party/aosp_manifest.xml'
-# which is a snapshot of the aosp repo set.
-# The manifest file can be updated with the following commands:
-#
-# cd build/aosp
-# repo manifest -o ../../third_party/aosp_manifest.xml -r
-#
-# The baseline is the `test_result.xml` file which is created with an AOSP
-# build which uses the default (JACK) toolset.
-#
-# To reproduce the baseline results, follow the instructions in this script,
-# except don't set `ANDROID_COMPILE_WITH_JACK=false` for the `make` command.
-# Also, you don't need to apply either of the patches (PATCH#1 and #2, see
-# below)
-#
-
-set -e
-
-readonly R8_ROOT=$(cd "$(dirname ${BASH_SOURCE[0]})"/..; pwd)
-readonly AOSP_ROOT="$R8_ROOT/build/aosp"
-readonly CTS_BASELINE="$R8_ROOT/third_party/android_cts_baseline/test_result.xml"
-readonly D8_JAR="$R8_ROOT/build/libs/d8.jar"
-readonly J_OPTION="-j8"
-readonly OUT_IMG=out_img # output dir for android image build
-readonly OUT_CTS=out_cts # output dir for CTS build
-
-# Process an Android CTS test_result.xml file for easy comparison with a
-# baseline.
-#
-# The function transforms these lines:
-#
-# <Test result="pass|fail" name="<name>" />
-#
-# to this:
-#
-# <module-name>/<testcase-name>/<name> -> PASS|FAIL
-#
-flatten_xml() {
- local input_file="$1"
- local module
- local testcase
- local testname
- while IFS='' read -r line || [[ -n "$line" ]]; do
- if [[ $line =~ \<Module\ name=\"([^\"]*)\" ]]; then
- module=${BASH_REMATCH[1]}
- elif [[ $line =~ \<TestCase\ name=\"([^\"]*)\" ]]; then
- testcase=${BASH_REMATCH[1]}
- elif [[ $line =~ \<Test\ result=\"pass\"\ name=\"([^\"]*)\" ]]; then
- echo "$module/$testcase/${BASH_REMATCH[1]} -> PASS"
- elif [[ $line =~ \<Test\ result=\"fail\"\ name=\"([^\"]*)\" ]]; then
- echo "$module/$testcase/${BASH_REMATCH[1]} -> FAIL"
- fi
- done < "$input_file"
-}
-
-#### MAIN ####
-
-cd "$R8_ROOT"
-tools/gradle.py d8
-
-mkdir -p "$AOSP_ROOT"
-cd "$AOSP_ROOT"
-
-# Two output dirs, one for the android image and one for cts tests. The output
-# is compiled with d8 and jack, respectively.
-mkdir -p "$OUT_IMG" "$OUT_CTS"
-
-# remove dex files older than the current d8 tool
-find "$OUT_IMG" ! -newer "$R8_ROOT/build/libs/d8.jar" -name '*.dex' -exec rm {} \;
-
-# checkout AOSP source
-mkdir -p .repo/manifests
-cp "$R8_ROOT/third_party/aosp_manifest.xml" .repo/manifests
-
-repo init -u "https://android.googlesource.com/platform/manifest" -m aosp_manifest.xml
-repo sync -dq $J_OPTION
-
-# activate $OUT_CTS
-rm -rf out
-ln -s "$OUT_CTS" out
-
-. build/envsetup.sh
-lunch aosp_x86-eng
-make $J_OPTION cts
-
-# activate $OUT_IMG
-rm -rf out
-ln -s "$OUT_IMG" out
-
-. build/envsetup.sh
-lunch aosp_x86-eng
-make $J_OPTION ANDROID_COMPILE_WITH_JACK=false DX_ALT_JAR="$D8_JAR"
-
-# create sdcard image for media tests
-
-mkdir -p "$R8_ROOT/build/tmp"
-sdcard_file="$R8_ROOT/build/tmp/sdcard.img"
-rm -f "$sdcard_file"
-mksdcard 4G "$sdcard_file"
-
-emulator -partition-size 4096 -wipe-data -sdcard "$sdcard_file" &
-emulator_pid=$!
-
-adb wait-for-device
-adb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82'
-
-echo "exit" | \
- ANDROID_BUILD_TOP= \
- "$OUT_CTS/host/linux-x86/cts/android-cts/tools/cts-tradefed" run cts
-
-kill $emulator_pid
-rm -f "$sdcard_file"
-
-# find the newest test_result.xml
-
-results_dir="$OUT_CTS/host/linux-x86/cts/android-cts/results"
-timestamp="$(ls --group-directories-first -t "$results_dir" | head -1)"
-results_xml="$results_dir/$timestamp/test_result.xml"
-
-echo "Summary from current test results: $results_xml"
-grep "<Summary " "$results_xml"
-
-echo "Summary from baseline: $CTS_BASELINE"
-grep "<Summary " "$CTS_BASELINE"
-
-echo "Comparing test results to baseline"
-
-diff <(flatten_xml "$results_xml") <(flatten_xml "$CTS_BASELINE")
-exit $? # make it explicit that the result of the diff must be the result of this script
-
diff --git a/src/main/java/com/android/tools/r8/D8Logger.java b/src/main/java/com/android/tools/r8/D8Logger.java
new file mode 100644
index 0000000..25f238b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/D8Logger.java
@@ -0,0 +1,54 @@
+// 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;
+
+import com.android.tools.r8.compatdx.CompatDx;
+import com.google.common.collect.ImmutableList;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+import static java.util.Arrays.stream;
+
+public final class D8Logger {
+
+ private static final int STATUS_ERROR = 1;
+
+ private static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+ "Usage: java -jar d8logger.jar <compiler-options>",
+ " where <compiler-options> will be",
+ "",
+ " 1. forwarded to the 'd8' or 'compatdx' tool (depending on the presence of the '--dex'",
+ " option), and also",
+ " 2. appended to the file in the environment variable 'D8LOGGER_OUTPUT'",
+ "",
+ " The options will be appended as a new line with TAB characters between the arguments."));
+
+ public static void main(String[] args) throws IOException {
+ if (args.length == 0) {
+ System.err.println(USAGE_MESSAGE);
+ System.exit(STATUS_ERROR);
+ }
+ String output = System.getenv("D8LOGGER_OUTPUT");
+ if (output == null) {
+ throw new IOException("D8Logger: D8LOGGER_OUTPUT environment variable must be set.");
+ }
+
+ if (output.length() > 0) {
+ String[] absArgs = Arrays.stream(args)
+ .map(s -> s.startsWith("-") ? s : Paths.get(s).toAbsolutePath().toString())
+ .toArray(String[]::new);
+ FileWriter fw = new FileWriter(output, true);
+ fw.write(String.join("\t", absArgs) + "\n");
+ fw.close();
+ }
+
+ if (Arrays.stream(args).anyMatch(s -> s.equals("--dex"))) {
+ CompatDx.main(args);
+ } else {
+ D8.main(args);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
index e2e972c..3c3c2b9 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
@@ -7,7 +7,7 @@
private static void publicizeAllMethods(DexEncodedMethod[] methods) {
for (DexEncodedMethod method : methods) {
- method.accessFlags.promoteToPublic();
+ method.accessFlags.promoteNonPrivateToPublic();
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java b/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java
index f409c4a..a8f9526 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java
@@ -263,13 +263,18 @@
set(Constants.ACC_DECLARED_SYNCHRONIZED);
}
- public void promoteToPublic() {
+ public void promoteNonPrivateToPublic() {
if (!isPrivate()) {
flags &= ~Constants.ACC_PROTECTED;
flags |= Constants.ACC_PUBLIC;
}
}
+ public void promoteToPublic() {
+ flags &= ~Constants.ACC_PROTECTED & ~Constants.ACC_PRIVATE;
+ flags |= Constants.ACC_PUBLIC;
+ }
+
private boolean isSet(int flag) {
return (flags & flag) != 0;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
index f9c0b30..2f2523d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
@@ -17,7 +17,8 @@
@Override
protected boolean doEquivalent(Instruction a, Instruction b) {
- return a.identicalAfterRegisterAllocation(b, allocator);
+ return a.identicalAfterRegisterAllocation(b, allocator)
+ && a.getBlock().getCatchHandlers().equals(b.getBlock().getCatchHandlers());
}
@Override
@@ -32,6 +33,7 @@
hash += allocator.getRegisterForValue(inValue, instruction.getNumber());
}
}
+ hash = hash * 37 + instruction.getBlock().getCatchHandlers().hashCode();
return hash;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index f2d039d..f621c67 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -429,9 +429,48 @@
private boolean performAllocation(ArgumentReuseMode mode) {
boolean result = performAllocationWithoutMoveInsertion(mode);
insertMoves();
+ if (mode == ArgumentReuseMode.DISALLOW_ARGUMENT_REUSE) {
+ // Now that we know the max register number we can compute whether it is safe to use
+ // argument registers in place. If it is, we redo move insertion to get rid of the moves
+ // caused by splitting of the argument registers.
+ if (unsplitArguments()) {
+ removeSpillAndPhiMoves();
+ insertMoves();
+ }
+ }
return result;
}
+ // When argument register reuse is disallowed, we split argument values to make sure that
+ // we can get the argument into low enough registers at uses that require low numbers. After
+ // register allocation we can check if it is safe to just use the argument register itself
+ // for all uses and thereby avoid moving argument values around.
+ private boolean unsplitArguments() {
+ boolean argumentRegisterUnsplit = false;
+ Value current = preArgumentSentinelValue;
+ while (current != null) {
+ LiveIntervals intervals = current.getLiveIntervals();
+ assert intervals.getRegisterLimit() == Constants.U16BIT_MAX;
+ boolean canUseArgumentRegister = true;
+ for (LiveIntervals child : intervals.getSplitChildren()) {
+ if (child.getRegisterLimit() < registersUsed()) {
+ canUseArgumentRegister = false;
+ break;
+ }
+ }
+ if (canUseArgumentRegister) {
+ argumentRegisterUnsplit = true;
+ for (LiveIntervals child : intervals.getSplitChildren()) {
+ child.clearRegisterAssignment();
+ child.setRegister(intervals.getRegister());
+ child.setSpilled(false);
+ }
+ }
+ current = current.getNextConsecutive();
+ }
+ return argumentRegisterUnsplit;
+ }
+
private void removeSpillAndPhiMoves() {
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator();
diff --git a/src/test/debugTestResourcesJava8/DebugDefaultMethod.java b/src/test/debugTestResourcesJava8/DebugDefaultMethod.java
new file mode 100644
index 0000000..db216b3
--- /dev/null
+++ b/src/test/debugTestResourcesJava8/DebugDefaultMethod.java
@@ -0,0 +1,35 @@
+// 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.
+
+public class DebugDefaultMethod {
+
+ interface I {
+ default void doSomething(String msg) {
+ String name = getClass().getName();
+ System.out.println(name + ": " + msg);
+ }
+ }
+
+ static class DefaultImpl implements I {
+ }
+
+ static class OverrideImpl implements I {
+
+ @Override
+ public void doSomething(String msg) {
+ String newMsg = "OVERRIDE" + msg;
+ System.out.println(newMsg);
+ }
+ }
+
+ private static void testDefaultMethod(I i) {
+ i.doSomething("Test");
+ }
+
+ public static void main(String[] args) {
+ testDefaultMethod(new DefaultImpl());
+ testDefaultMethod(new OverrideImpl());
+ }
+
+}
diff --git a/src/test/debugTestResourcesJava8/DebugLambda.java b/src/test/debugTestResourcesJava8/DebugLambda.java
new file mode 100644
index 0000000..c3b8695
--- /dev/null
+++ b/src/test/debugTestResourcesJava8/DebugLambda.java
@@ -0,0 +1,23 @@
+// 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.
+
+public class DebugLambda {
+
+ interface I {
+ int getInt();
+ }
+
+ private static void printInt(I i) {
+ System.out.println(i.getInt());
+ }
+
+ public static void testLambda(int i, int j) {
+ printInt(() -> i + j);
+ }
+
+ public static void main(String[] args) {
+ DebugLambda.testLambda(5, 10);
+ }
+
+}
diff --git a/src/test/examples/nestedtrycatches/NestedTryCatches.java b/src/test/examples/nestedtrycatches/NestedTryCatches.java
new file mode 100644
index 0000000..00bec2a
--- /dev/null
+++ b/src/test/examples/nestedtrycatches/NestedTryCatches.java
@@ -0,0 +1,38 @@
+// 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 nestedtrycatches;
+
+public class NestedTryCatches {
+ private static void throwException() {
+ throw new RuntimeException("IGNORED");
+ }
+
+ private static void test() throws Throwable {
+ RuntimeException _primaryExc = null;
+ try {
+ throw new RuntimeException("PRIMARY");
+ } catch (RuntimeException _t) {
+ _primaryExc = _t;
+ throw _t;
+ } finally {
+ // Keep the two calls to throwException() the same line
+ if(_primaryExc!=null) {
+ try {
+ throwException();
+ } catch(Throwable _suppressed) {
+ }
+ } else {
+ throwException();
+ }
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ try {
+ test();
+ } catch (Throwable e) {
+ System.out.println("EXCEPTION: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 628036c..9e7588d 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -86,6 +86,7 @@
{"throwing.Throwing", "Throwing"},
{"trivial.Trivial", null},
{"trycatch.TryCatch", "Success!"},
+ {"nestedtrycatches.NestedTryCatches", "EXCEPTION: PRIMARY"},
{"trycatchmany.TryCatchMany", "Success!"},
{"invokeempty.InvokeEmpty", "AB"},
{"regress.Regress", null},
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 0b042a9..5f9a95c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -4,11 +4,12 @@
package com.android.tools.r8.debug;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.D8;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.OffOrAuto;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.nio.file.Path;
@@ -78,11 +79,14 @@
private static final Path DEBUGGEE_JAR = Paths
.get(ToolHelper.BUILD_DIR, "test", "debug_test_resources.jar");
+ private static final Path DEBUGGEE_JAVA8_JAR = Paths
+ .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources_java8.jar");
@ClassRule
public static TemporaryFolder temp = new TemporaryFolder();
private static Path jdwpDexD8 = null;
private static Path debuggeeDexD8 = null;
+ private static Path debuggeeJava8DexD8 = null;
@Rule
public TestName testName = new TestName();
@@ -95,7 +99,7 @@
Path jdwpJar = ToolHelper.getJdwpTestsJarPath(minSdk);
Path dexOutputDir = temp.newFolder("d8-jdwp-jar").toPath();
jdwpDexD8 = dexOutputDir.resolve("classes.dex");
- D8.run(
+ ToolHelper.runD8(
D8Command.builder()
.addProgramFiles(jdwpJar)
.setOutputPath(dexOutputDir)
@@ -106,7 +110,7 @@
{
Path dexOutputDir = temp.newFolder("d8-debuggee-jar").toPath();
debuggeeDexD8 = dexOutputDir.resolve("classes.dex");
- D8.run(
+ ToolHelper.runD8(
D8Command.builder()
.addProgramFiles(DEBUGGEE_JAR)
.setOutputPath(dexOutputDir)
@@ -114,6 +118,25 @@
.setMode(CompilationMode.DEBUG)
.build());
}
+ {
+ Path dexOutputDir = temp.newFolder("d8-debuggee-java8-jar").toPath();
+ debuggeeJava8DexD8 = dexOutputDir.resolve("classes.dex");
+ ToolHelper.runD8(
+ D8Command.builder()
+ .addProgramFiles(DEBUGGEE_JAVA8_JAR)
+ .setOutputPath(dexOutputDir)
+ .setMinApiLevel(minSdk)
+ .setMode(CompilationMode.DEBUG)
+ .build(),
+ options -> {
+ // Enable desugaring for preN runtimes
+ options.interfaceMethodDesugaring = OffOrAuto.Auto;
+ });
+ }
+ }
+
+ protected final boolean supportsDefaultMethod() {
+ return ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()) >= Constants.ANDROID_N_API;
}
protected final void runDebugTest(String debuggeeClass, JUnit3Wrapper.Command... commands)
@@ -123,6 +146,22 @@
protected final void runDebugTest(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
throws Throwable {
+ runDebugTest(debuggeeDexD8, debuggeeClass, commands);
+ }
+
+ protected final void runDebugTestJava8(String debuggeeClass, JUnit3Wrapper.Command... commands)
+ throws Throwable {
+ runDebugTestJava8(debuggeeClass, Arrays.asList(commands));
+ }
+
+ protected final void runDebugTestJava8(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
+ throws Throwable {
+ runDebugTest(debuggeeJava8DexD8, debuggeeClass, commands);
+ }
+
+ private void runDebugTest(Path debuggeeExec, String debuggeeClass,
+ List<JUnit3Wrapper.Command> commands)
+ throws Throwable {
// Skip test due to unsupported runtime.
Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
ToolHelper.artSupported());
@@ -131,7 +170,7 @@
.getDexVm(), UNSUPPORTED_ART_VERSIONS.contains(ToolHelper.getDexVm()));
// Run with ART.
- String[] paths = new String[]{jdwpDexD8.toString(), debuggeeDexD8.toString()};
+ String[] paths = new String[]{jdwpDexD8.toString(), debuggeeExec.toString()};
new JUnit3Wrapper(debuggeeClass, paths, commands).runBare();
}
@@ -172,6 +211,10 @@
return new JUnit3Wrapper.Command.StepCommand(stepDepth, stepFilter);
}
+ protected final JUnit3Wrapper.Command checkLocal(String localName) {
+ return inspect(t -> t.checkLocal(localName));
+ }
+
protected final JUnit3Wrapper.Command checkLocal(String localName, Value expectedValue) {
return inspect(t -> t.checkLocal(localName, expectedValue));
}
@@ -308,6 +351,18 @@
// Capture the context of the event suspension.
updateEventContext((EventThread) parsedEvent);
+ if (DEBUG_TESTS && debuggeeState.location != null) {
+ // Dump location
+ String classSig = getMirror().getClassSignature(debuggeeState.location.classID);
+ String methodName = getMirror()
+ .getMethodName(debuggeeState.location.classID, debuggeeState.location.methodID);
+ String methodSig = getMirror()
+ .getMethodSignature(debuggeeState.location.classID, debuggeeState.location.methodID);
+ System.out.println(String
+ .format("Suspended in %s#%s%s@%x", classSig, methodName, methodSig,
+ Long.valueOf(debuggeeState.location.index)));
+ }
+
// Handle event.
EventHandler eh = events.get(requestID);
assert eh != null;
@@ -346,6 +401,10 @@
return this.location;
}
+ public void checkLocal(String localName) {
+ getVariableAt(getLocation(), localName);
+ }
+
public void checkLocal(String localName, Value expectedValue) {
Variable localVar = getVariableAt(getLocation(), localName);
@@ -767,7 +826,27 @@
public boolean skipLocation(VmMirror mirror, Location location) {
// TODO(shertz) we also need to skip class loaders to act like IntelliJ.
// Skip synthetic methods.
- return isSyntheticMethod(mirror, location);
+ if (isLambdaMethod(mirror, location)) {
+ // Lambda methods are synthetic but we do want to stop there.
+ if (DEBUG_TESTS) {
+ System.out.println("NOT skipping lambda implementation method");
+ }
+ return false;
+ }
+ if (isInLambdaClass(mirror, location)) {
+ // Lambda classes must be skipped since they are only wrappers around lambda code.
+ if (DEBUG_TESTS) {
+ System.out.println("Skipping lambda class wrapper method");
+ }
+ return true;
+ }
+ if (isSyntheticMethod(mirror, location)) {
+ if (DEBUG_TESTS) {
+ System.out.println("Skipping synthetic method");
+ }
+ return true;
+ }
+ return false;
}
private static boolean isSyntheticMethod(VmMirror mirror, Location location) {
@@ -787,6 +866,16 @@
}
return false;
}
+
+ private static boolean isInLambdaClass(VmMirror mirror, Location location) {
+ String classSig = mirror.getClassSignature(location.classID);
+ return classSig.contains("$$Lambda$");
+ }
+
+ private boolean isLambdaMethod(VmMirror mirror, Location location) {
+ String methodName = mirror.getMethodName(location.classID, location.methodID);
+ return methodName.startsWith("lambda$");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
new file mode 100644
index 0000000..ea116a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
@@ -0,0 +1,65 @@
+// 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.debug;
+
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class DefaultMethodTest extends DebugTestBase {
+
+ @Test
+ public void testDefaultMethod() throws Throwable {
+ String debuggeeClass = "DebugDefaultMethod";
+ String parameterName = "msg";
+ String localVariableName = "name";
+
+ List<Command> commands = new ArrayList<>();
+ commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
+ commands.add(run());
+ commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
+ commands.add(checkLine(27));
+ if (!supportsDefaultMethod()) {
+ // We desugared default method. This means we're going to step through an extra (forward)
+ // method first.
+ commands.add(stepInto());
+ }
+ commands.add(stepInto());
+ commands.add(checkLocal(parameterName));
+ commands.add(stepOver());
+ commands.add(checkLocal(parameterName));
+ commands.add(checkLocal(localVariableName));
+ // TODO(shertz) check current method name ?
+ commands.add(run());
+ commands.add(run() /* resume after 2nd breakpoint */);
+
+ runDebugTestJava8(debuggeeClass, commands);
+ }
+
+ @Test
+ public void testOverrideDefaultMethod() throws Throwable {
+ String debuggeeClass = "DebugDefaultMethod";
+ String parameterName = "msg";
+ String localVariableName = "newMsg";
+
+ List<Command> commands = new ArrayList<>();
+ commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
+ commands.add(run());
+ commands.add(run() /* resume after 1st breakpoint */);
+ commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
+ commands.add(checkLine(27));
+ commands.add(stepInto());
+ commands.add(checkMethod("DebugDefaultMethod$OverrideImpl", "doSomething"));
+ commands.add(checkLocal(parameterName));
+ commands.add(stepOver());
+ commands.add(checkLocal(parameterName));
+ commands.add(checkLocal(localVariableName));
+ commands.add(run());
+
+ runDebugTestJava8(debuggeeClass, commands);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
new file mode 100644
index 0000000..467ae09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -0,0 +1,25 @@
+// 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.debug;
+
+import org.junit.Test;
+
+public class LambdaTest extends DebugTestBase {
+
+ @Test
+ public void testLambdaDebugging() throws Throwable {
+ String debuggeeClass = "DebugLambda";
+ String initialMethodName = "printInt";
+ // TODO(shertz) test local variables
+ runDebugTestJava8(debuggeeClass,
+ breakpoint(debuggeeClass, initialMethodName),
+ run(),
+ checkMethod(debuggeeClass, initialMethodName),
+ checkLine(12),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(16),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
index 229a6fb..8b5b814 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
@@ -11,23 +11,28 @@
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalResource;
+import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
-import org.junit.Ignore;
import org.junit.Test;
public class YouTubeTreeShakeJarVerificationTest extends YouTubeCompilationBase {
@Test
- @Ignore("b/35656577")
public void buildAndTreeShakeFromDeployJar()
throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
- int maxSize = 16000000;
+ int maxSize = 20000000;
AndroidApp app = runAndCheckVerification(
- CompilerUnderTest.R8, CompilationMode.RELEASE,
- BASE + APK, null, BASE + PG_CONF, BASE + DEPLOY_JAR);
+ CompilerUnderTest.R8,
+ CompilationMode.RELEASE,
+ BASE + APK,
+ null,
+ BASE + PG_CONF,
+ // Don't pass any inputs. The input will be read from the -injars in the Proguard
+ // configuration file.
+ ImmutableList.of());
int bytes = 0;
try (Closer closer = Closer.create()) {
for (InternalResource dex : app.getDexProgramResources()) {
diff --git a/tools/apk-masseur.py b/tools/apk-masseur.py
index b0c990f..6e3528a 100755
--- a/tools/apk-masseur.py
+++ b/tools/apk-masseur.py
@@ -24,6 +24,10 @@
parser.add_option('--keystore',
help='keystore file (default ~/.android/app.keystore)',
default=None)
+ parser.add_option('--install',
+ help='install the generated apk with adb options -t -r -d',
+ default=False,
+ action='store_true')
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -102,6 +106,10 @@
aligned_apk = align(signed_apk, temp)
print 'Writing result to', options.out
shutil.copyfile(aligned_apk, options.out)
+ if options.install:
+ cmd = ['adb', 'install', '-t', '-r', '-d', options.out]
+ utils.PrintCmd(cmd)
+ subprocess.check_call(cmd)
return 0
if __name__ == '__main__':
diff --git a/tools/create_art_tests.py b/tools/create_art_tests.py
new file mode 100755
index 0000000..3a83cbc
--- /dev/null
+++ b/tools/create_art_tests.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# 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.
+
+from os import makedirs, listdir
+from os.path import join, exists, isdir
+from string import Template, upper
+from sys import exit
+from shutil import rmtree
+
+OUTPUT_DIR = "build/generated/test/java/com/android/tools/r8/art"
+TEST_DIR = "tests/art"
+TOOLCHAINS = ["dx", "jack", "none"]
+TOOLS = ["r8", "d8"]
+TEMPLATE = Template(
+"""// Copyright (c) 2016, 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.art.$testGeneratingToolchain.$compilerUnderTest;
+
+import static com.android.tools.r8.R8RunArtTestsTest.DexTool.$testGeneratingToolchainEnum;
+
+import com.android.tools.r8.R8RunArtTestsTest;
+import org.junit.Test;
+
+/**
+ * Auto-generated test for the art $name test using the $testGeneratingToolchain toolchain.
+ *
+ * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN tools/create_art_tests.py INSTEAD!
+ */
+public class $testClassName extends R8RunArtTestsTest {
+
+ public $testClassName() {
+ super("$name", $testGeneratingToolchainEnum);
+ }
+
+ @Test
+ public void run$testClassName() throws Throwable {
+ // For testing with other Art VMs than the default pass the VM version as a argument to
+ // runArtTest, e.g. runArtTest(ToolHelper.ART_4_4_4).
+ runArtTest(CompilerUnderTest.$compilerUnderTestEnum);
+ }
+}
+""")
+
+def create_toolchain_dirs(toolchain):
+ toolchain_dir = join(OUTPUT_DIR, toolchain)
+ if exists(toolchain_dir):
+ rmtree(toolchain_dir)
+ makedirs(join(toolchain_dir, "d8"))
+ makedirs(join(toolchain_dir, "r8"))
+
+def write_file(toolchain, tool, class_name, contents):
+ file_name = join(OUTPUT_DIR, toolchain, tool, class_name + ".java")
+ with open(file_name, "w") as file:
+ file.write(contents)
+
+def create_tests(toolchain):
+ source_dir = join(TEST_DIR, "dx" if toolchain == "none" else toolchain)
+ dirs = [d for d in listdir(source_dir)
+ if isdir(join(source_dir, d))]
+ for dir in dirs:
+ class_name = "Art" + dir.replace("-", "_") + "Test"
+ for tool in TOOLS:
+ contents = TEMPLATE.substitute(
+ name=dir,
+ compilerUnderTestEnum=upper(tool),
+ compilerUnderTest=tool,
+ testGeneratingToolchain=toolchain,
+ testGeneratingToolchainEnum=upper(toolchain),
+ testClassName=class_name)
+ write_file(toolchain, tool, class_name, contents)
+
+
+def main():
+ for toolchain in TOOLCHAINS:
+ create_toolchain_dirs(toolchain)
+ create_tests(toolchain)
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/tools/linux/dx.tar.gz.sha1 b/tools/linux/dx.tar.gz.sha1
index f13e394..92e67ce 100644
--- a/tools/linux/dx.tar.gz.sha1
+++ b/tools/linux/dx.tar.gz.sha1
@@ -1 +1 @@
-6976e6a1768527b2388b1fdda5868dfa6b80d844
\ No newline at end of file
+da8789846188590e69dbac06f4d387762e71a616
\ No newline at end of file
diff --git a/tools/test_android_cts.py b/tools/test_android_cts.py
new file mode 100755
index 0000000..b4af1f1
--- /dev/null
+++ b/tools/test_android_cts.py
@@ -0,0 +1,294 @@
+#!/usr/bin/env python
+# 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.
+
+# Clone and build AOSP, using D8 instead of JACK or DX,
+# then run the Android-CTS on the emulator and compare results
+# to a baseline.
+#
+# This script uses the repo manifest file 'third_party/aosp_manifest.xml'
+# which is a snapshot of the aosp repo set.
+# The manifest file can be updated with the following commands:
+#
+# cd build/aosp
+# repo manifest -o ../../third_party/aosp_manifest.xml -r
+#
+# The baseline is the `test_result.xml` file which is created with an AOSP
+# build which uses the default (JACK) toolset.
+#
+# Use this script, with '--tool=jack' to reproduce the baseline results
+#
+
+from __future__ import print_function
+from glob import glob
+from itertools import chain
+from os.path import join
+from shutil import copy2
+from subprocess import check_call, Popen
+import argparse
+import os
+import re
+import sys
+
+import gradle
+import utils
+
+CTS_BASELINE = join(utils.REPO_ROOT,
+ 'third_party/android_cts_baseline/test_result.xml')
+AOSP_MANIFEST_XML = join(utils.REPO_ROOT, 'third_party',
+ 'aosp_manifest.xml')
+AOSP_HELPER_SH = join(utils.REPO_ROOT, 'scripts', 'aosp_helper.sh')
+
+D8_JAR = join(utils.REPO_ROOT, 'build/libs/d8.jar')
+COMPATDX_JAR = join(utils.REPO_ROOT, 'build/libs/compatdx.jar')
+D8LOGGER_JAR = join(utils.REPO_ROOT, 'build/libs/d8logger.jar')
+
+AOSP_ROOT = join(utils.REPO_ROOT, 'build/aosp')
+
+AOSP_MANIFEST_URL = 'https://android.googlesource.com/platform/manifest'
+AOSP_PRESET = 'aosp_x86-eng'
+
+AOSP_OUT = join(AOSP_ROOT, 'out')
+OUT_IMG = join(AOSP_ROOT, 'out_img') # output dir for android img build
+OUT_CTS = join(AOSP_ROOT, 'out_cts') # output dir for CTS build
+RESULTS_DIR_BASE = join(OUT_CTS, 'host/linux-x86/cts/android-cts/results')
+CTS_TRADEFED = join(OUT_CTS,
+ 'host/linux-x86/cts/android-cts/tools/cts-tradefed')
+
+J_OPTION = '-j8'
+
+EXIT_FAILURE = 1
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(
+ description = 'Download the AOSP source tree, build an Android image'
+ ' and the CTS targets and run CTS with the emulator on the image.')
+ parser.add_argument('--tool',
+ choices = ['jack', 'dx', 'd8'],
+ default = 'd8',
+ help='compiler tool to use')
+ parser.add_argument('--d8log',
+ metavar = 'FILE',
+ help = 'Enable logging d8 (compatdx) calls to the specified file. Works'
+ ' only with --tool=d8')
+ return parser.parse_args()
+
+# return False on error
+def remove_aosp_out():
+ if os.path.exists(AOSP_OUT):
+ if os.path.islink(AOSP_OUT):
+ os.remove(AOSP_OUT)
+ else:
+ print("The AOSP out directory ('" + AOSP_OUT + "') is expected"
+ " to be a symlink", file = sys.stderr)
+ return False
+ return True
+
+# Read the xml test result file into an in-memory tree:
+# Extract only the Module/TestCase/Test names and outcome (True|False for
+# PASS|FAIL):
+#
+# tree[module_name][testcase_name][test_name] = True|False
+#
+def read_test_result_into_tree(filename):
+ re_module = re.compile('<Module name="([^"]*)"')
+ re_testcase = re.compile('<TestCase name="([^"]*)"')
+ re_test = re.compile('<Test result="(pass|fail)" name="([^"]*)"')
+ tree = {}
+ module = None
+ testcase = None
+ with open(filename) as f:
+ for line in f:
+ m = re_module.search(line)
+ if m:
+ module_name = m.groups()[0]
+ tree[module_name] = {}
+ module = tree[module_name]
+ continue
+
+ m = re_testcase.search(line)
+ if m:
+ testcase_name = m.groups()[0]
+ module[testcase_name] = {}
+ testcase = module[testcase_name]
+ continue
+
+ m = re_test.search(line)
+ if m:
+ outcome = m.groups()[0]
+ test_name = m.groups()[1]
+ assert outcome in ["fail", "pass"]
+ testcase[test_name] = outcome == "pass"
+ return tree
+
+# Report the items with the title
+def report_key_diff(title, items, prefix = ''):
+ if len(items) > 0:
+ print(title, ":")
+ for x in items:
+ print("- {}{}".format(prefix, x))
+ print()
+
+
+def diff_sets(base_minus_result_title, result_minus_base_title,
+ base_set, result_set, prefix = ''):
+ base_minus_result = base_set - result_set
+ result_minus_base = result_set - base_set
+ report_key_diff(base_minus_result_title, base_minus_result, prefix)
+ report_key_diff(result_minus_base_title, result_minus_base, prefix)
+ return len(base_minus_result) > 0 or len(result_minus_base) > 0
+
+def diff_tree_report(baseline_tree, result_tree):
+ baseline_modules = set(baseline_tree.keys())
+ result_modules = set(result_tree.keys())
+ differ = diff_sets('Modules missing from current result',
+ 'New modules appeared in current result',
+ baseline_modules, result_modules)
+ for module in (result_modules & baseline_modules):
+ baseline_module = baseline_tree[module]
+ result_module = result_tree[module]
+ baseline_testcases = set(baseline_module.keys())
+ result_testcases = set(result_module.keys())
+ differ = diff_sets('Test cases missing from current result',
+ 'New test cases appeared in current result',
+ baseline_testcases, result_testcases, module + '/') \
+ or differ
+ for testcase in (result_testcases & baseline_testcases):
+ baseline_testcase = baseline_module[testcase]
+ result_testcase = result_module[testcase]
+ baseline_tests = set(baseline_testcase.keys())
+ result_tests = set(result_testcase.keys())
+ differ = diff_sets('Tests missing from current result',
+ 'New tests appeared in current result',
+ baseline_tests, result_tests, module + '/' + testcase + '/') \
+ or differ
+ need_newline_at_end = False
+ for test in (result_tests & baseline_tests):
+ baseline_outcome = baseline_testcase[test]
+ result_outcome = result_testcase[test]
+ if baseline_outcome != result_outcome:
+ differ = True
+ print('Test: {}/{}/{}, change: {}'.format(
+ module, testcase, test,
+ 'PASS -> FAIL' if baseline_outcome else 'FAIL -> PASS'))
+ need_newline_at_end = True
+ if need_newline_at_end:
+ print()
+ return differ
+
+def setup_and_clean():
+ # Two output dirs, one for the android image and one for cts tests.
+ # The output is compiled with d8 and jack, respectively.
+ utils.makedirs_if_needed(AOSP_ROOT)
+ utils.makedirs_if_needed(OUT_IMG)
+ utils.makedirs_if_needed(OUT_CTS)
+
+ # remove dex files older than the current d8 tool
+ d8jar_mtime = os.path.getmtime(D8_JAR)
+ dex_files = (chain.from_iterable(glob(join(x[0], '*.dex'))
+ for x in os.walk(OUT_IMG)))
+ for f in dex_files:
+ if os.path.getmtime(f) <= d8jar_mtime:
+ os.remove(f)
+
+def checkout_aosp():
+ # checkout AOSP source
+ manifests_dir = join(AOSP_ROOT, '.repo', 'manifests')
+ utils.makedirs_if_needed(manifests_dir)
+
+ copy2(AOSP_MANIFEST_XML, manifests_dir)
+ check_call(['repo', 'init', '-u', AOSP_MANIFEST_URL, '-m',
+ 'aosp_manifest.xml', '--depth=1'], cwd = AOSP_ROOT)
+
+ check_call(['repo', 'sync', '-dq', J_OPTION], cwd = AOSP_ROOT)
+
+def Main():
+ args = parse_arguments()
+
+ if args.d8log and args.tool != 'd8':
+ print("The '--d8log' option works only with '--tool=d8'.",
+ file = sys.stderr)
+ return EXIT_FAILURE
+
+ assert args.tool in ['jack', 'dx', 'd8']
+
+ jack_option = 'ANDROID_COMPILE_WITH_JACK=' \
+ + 'true' if args.tool == 'jack' else 'false'
+
+ alt_jar_option = ''
+ if args.tool == 'd8':
+ if args.d8log:
+ alt_jar_option = 'DX_ALT_JAR=' + D8LOGGER_JAR
+ os.environ['D8LOGGER_OUTPUT'] = args.d8log
+ else:
+ alt_jar_option = 'DX_ALT_JAR=' + COMPATDX_JAR
+
+ gradle.RunGradle(['d8','d8logger', 'compatdx'])
+
+ setup_and_clean()
+
+ checkout_aosp()
+
+ # activate OUT_CTS and build Android CTS
+ # AOSP has no clean way to set the output directory.
+ # In order to do incremental builds we apply the following symlink-based
+ # workaround.
+ # Note: this does not work on windows, but the AOSP
+ # doesn't build, either
+
+ if not remove_aosp_out():
+ return EXIT_FAILURE
+ os.symlink(OUT_CTS, AOSP_OUT)
+ check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, 'cts'],
+ cwd = AOSP_ROOT)
+
+ # activate OUT_IMG and build the Android image
+ if not remove_aosp_out():
+ return EXIT_FAILURE
+ os.symlink(OUT_IMG, AOSP_OUT)
+ check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, jack_option,
+ alt_jar_option], cwd = AOSP_ROOT)
+
+ emulator_proc = Popen([AOSP_HELPER_SH, AOSP_PRESET,
+ 'emulator', '-partition-size', '4096', '-wipe-data'], cwd = AOSP_ROOT)
+
+ if emulator_proc.poll() is not None:
+ print("Can't start Android Emulator.", file = sys.stderr)
+
+ check_call([AOSP_HELPER_SH, AOSP_PRESET, 'run-cts',
+ CTS_TRADEFED, 'run', 'cts'], cwd = AOSP_ROOT)
+
+ emulator_proc.terminate()
+
+ # find the newest test_result.xml
+ result_dirs = \
+ [f for f in glob(join(RESULTS_DIR_BASE, '*')) if os.path.isdir(f)]
+ if len(result_dirs) == 0:
+ print("Can't find result directories in ", RESULTS_DIR_BASE)
+ return EXIT_FAILURE
+ result_dirs.sort(key = os.path.getmtime)
+ results_xml = join(result_dirs[-1], 'test_result.xml')
+
+ # print summaries
+ re_summary = re.compile('<Summary ')
+ for (title, result_file) in [
+ ('Summary from current test results: ', results_xml),
+ ('Summary from baseline: ', CTS_BASELINE)
+ ]:
+ print(title, result_file)
+ with open(result_file) as f:
+ for line in f:
+ if re_summary.search(line):
+ print(line)
+ break
+
+ print('Comparing test results to baseline:\n')
+
+ result_tree = read_test_result_into_tree(results_xml)
+ baseline_tree = read_test_result_into_tree(CTS_BASELINE)
+
+ return EXIT_FAILURE if diff_tree_report(baseline_tree, result_tree) else 0
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/utils.py b/tools/utils.py
index d5d27ca..67cac46 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -38,6 +38,13 @@
sha1.update(chunk)
return sha1.hexdigest()
+def makedirs_if_needed(path):
+ try:
+ os.makedirs(path)
+ except OSError:
+ if not os.path.isdir(path):
+ raise
+
class TempDir(object):
def __init__(self, prefix=''):
self._temp_dir = None